diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.razor.cs
index 04b16c776b..be73ee0c5d 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.razor.cs
@@ -17,10 +17,10 @@ public partial class BitAccentColorSwitcher : BitComponentBase
/// The accent colors offered when is not set (on the
/// parameter nor on the configuration registered in DI): the six
/// hues, starting with the packaged palette's own primary.
- /// That first item is named "Default" rather than after a hue: its token is the Fluent blue the
- /// service treats as "no override", but the swatch paints the primary of whichever packaged
- /// preset is active (purple under Material, iOS blue under Cupertino - see the --bit-acs-ntr
- /// custom property in BitAccentColorSwitcher.scss), so naming it "Blue" would be wrong there.
+ /// That first item is named "Default" rather than after a hue: its token is the blue the service
+ /// treats as "no override", but the swatch paints the primary of whichever packaged preset is
+ /// active (iOS blue under Cupertino - see the --bit-acs-ntr custom property in
+ /// BitAccentColorSwitcher.scss), so naming it "Blue" would be wrong there.
///
public static readonly IReadOnlyList DefaultAccents =
[
@@ -190,12 +190,12 @@ private static string GetSwatchColor(string? color)
// The neutral accent is "the packaged palette's own primary" (see BitAccentColorService):
// picking it clears the overrides, so what it yields is whatever primary the active preset
- // ships - Fluent's blue, Material's purple, Cupertino's system blue. Its swatch therefore
- // paints through --bit-acs-ntr, which the stylesheet sets per packaged preset (the Fluent
- // blue by default; the Material and Cupertino bundles retune it), with the token's own hex
- // as the fallback for a page that loads none of the bundles. Read from a custom property
- // rather than from the theme's --bit-clr-pri, which an applied accent overrides - the point
- // of this swatch is to show the primary underneath that override.
+ // ships - this blue under Fluent, Fluent 2 and Material, Cupertino's system blue. Its swatch
+ // therefore paints through --bit-acs-ntr, which the stylesheet sets per packaged preset (this
+ // blue by default; the Cupertino bundle retunes it), with the token's own hex as the fallback
+ // for a page that loads none of the bundles. Read from a custom property rather than from the
+ // theme's --bit-clr-pri, which an applied accent overrides - the point of this swatch is to
+ // show the primary underneath that override.
if (BitAccentColorSsr.NormalizeToken(value) == BitAccentColorSsr.NormalizeToken(BitAccentColorPresets.Blue))
{
return $"var(--bit-acs-ntr, {BitAccentColorPresets.Blue})";
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.scss
index 22cbeef071..bcf62b6074 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.scss
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AccentColorSwitcher/BitAccentColorSwitcher.scss
@@ -3,21 +3,20 @@
.bit-acs {
--bit-acs-size: #{rem2(24px)};
--bit-acs-gap: #{rem2(10px)};
- // What the neutral ("Default") swatch paints: the primary the active packaged preset ships,
- // since picking that swatch clears every accent override and leaves exactly that primary. The
- // Fluent light primary here (BitAccentColorPresets.Blue - a literal, not $clr-pri, which an
- // applied accent rewrites while this swatch has to keep showing the color underneath it); the
- // two rules below retune it to the Material and Cupertino seeds. A custom preset sets it on
- // .bit-acs to its own primary.
+ // What the neutral ("Default") swatch paints: the seed blue of the active packaged preset - the
+ // color picking that swatch returns the palette to, since it clears every accent override. A
+ // literal (BitAccentColorPresets.Blue) rather than $clr-pri, because an applied accent rewrites
+ // $clr-pri while this swatch has to keep showing the color underneath it. It is the primary the
+ // Fluent and Material light schemes ship, and Fluent 2's #0F6CBD is the same blue a shade off;
+ // their dark schemes lighten that same hue (#4FA3F4), so the swatch names the palette family's
+ // blue rather than the exact primary of the scheme in use. The rule below retunes it to
+ // Cupertino's seed, the one packaged preset far enough away for the difference to show. A custom
+ // preset sets it on .bit-acs to its own seed.
--bit-acs-ntr: #1276C6;
- // The seeds the packaged Material and Cupertino palettes are generated from (M3 baseline
- // primary, iOS system blue). Matched on the prefix so the scheme suffix and a scoped preset
- // (the attribute on an ancestor rather than on :root) both count.
- [bit-theme^="material"] & {
- --bit-acs-ntr: #6750A4;
- }
-
+ // The seed the packaged Cupertino palette is generated from (iOS system blue). Matched on the
+ // prefix so the scheme suffix and a scoped preset (the attribute on an ancestor rather than on
+ // :root) both count.
[bit-theme^="cupertino"] & {
--bit-acs-ntr: #0071E3;
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-dark.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-dark.scss
index be5f6b5684..6aacbe2191 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-dark.scss
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-dark.scss
@@ -2,19 +2,19 @@
:root[bit-theme="material-dark"],
:root [bit-theme="material-dark"] {
color-scheme: dark;
- --bit-clr-pri: #917BD1;
- --bit-clr-pri-hover: #9B87D8;
- --bit-clr-pri-active: #A290DD;
- --bit-clr-pri-dark: #8572BD;
- --bit-clr-pri-dark-hover: #8975C5;
- --bit-clr-pri-dark-active: #8C79CA;
- --bit-clr-pri-light: #AEA0DF;
- --bit-clr-pri-light-hover: #B8ADE2;
- --bit-clr-pri-light-active: #BFB6E3;
+ --bit-clr-pri: #4FA3F4;
+ --bit-clr-pri-hover: #61AEFB;
+ --bit-clr-pri-active: #6EB6FF;
+ --bit-clr-pri-dark: #3E87CE;
+ --bit-clr-pri-dark-hover: #4492DD;
+ --bit-clr-pri-dark-active: #499AE7;
+ --bit-clr-pri-light: #89C3FF;
+ --bit-clr-pri-light-hover: #9FCEFF;
+ --bit-clr-pri-light-active: #AED5FF;
--bit-clr-pri-text: #141414;
- --bit-clr-pri-dis: #2F2B3C;
- --bit-clr-pri-dis-text: #7B7690;
- --bit-clr-pri-focus: #917BD1;
+ --bit-clr-pri-dis: #222F3C;
+ --bit-clr-pri-dis-text: #697D92;
+ --bit-clr-pri-focus: #4FA3F4;
--bit-clr-sec: #8C859B;
--bit-clr-sec-hover: #968FA5;
--bit-clr-sec-active: #9E97AC;
@@ -41,19 +41,19 @@
--bit-clr-ter-dis: #3B282E;
--bit-clr-ter-dis-text: #8F717A;
--bit-clr-ter-focus: #A97C8A;
- --bit-clr-inf: #ABA9B4;
- --bit-clr-inf-hover: #B6B5BE;
- --bit-clr-inf-active: #BEBCC5;
- --bit-clr-inf-dark: #908E96;
- --bit-clr-inf-dark-hover: #9A99A2;
- --bit-clr-inf-dark-active: #A2A0A9;
- --bit-clr-inf-light: #CBC9D1;
- --bit-clr-inf-light-hover: #D6D4DB;
- --bit-clr-inf-light-active: #DDDDE4;
+ --bit-clr-inf: #A4ACB4;
+ --bit-clr-inf-hover: #B0B7BE;
+ --bit-clr-inf-active: #B8BEC6;
+ --bit-clr-inf-dark: #8A9097;
+ --bit-clr-inf-dark-hover: #949BA2;
+ --bit-clr-inf-dark-active: #9BA2AA;
+ --bit-clr-inf-light: #C5CBD2;
+ --bit-clr-inf-light-hover: #D1D6DC;
+ --bit-clr-inf-light-active: #D9DFE4;
--bit-clr-inf-text: #141414;
- --bit-clr-inf-dis: #2E2C35;
- --bit-clr-inf-dis-text: #7B7983;
- --bit-clr-inf-focus: #ABA9B4;
+ --bit-clr-inf-dis: #282F35;
+ --bit-clr-inf-dis-text: #747B83;
+ --bit-clr-inf-focus: #A4ACB4;
--bit-clr-suc: #78B774;
--bit-clr-suc-hover: #86C182;
--bit-clr-suc-active: #90C98C;
@@ -106,141 +106,141 @@
--bit-clr-err-dis: #3C2825;
--bit-clr-err-dis-text: #91726D;
--bit-clr-err-focus: #E35B4C;
- --bit-clr-fg-pri: #EBE9F7;
- --bit-clr-fg-pri-hover: #F2EFFC;
- --bit-clr-fg-pri-active: #F7F5FF;
- --bit-clr-fg-pri-dark: #D8D5E2;
- --bit-clr-fg-pri-dark-hover: #E0DDEA;
- --bit-clr-fg-pri-dark-active: #E6E4F2;
- --bit-clr-fg-pri-light: #F9F7FF;
- --bit-clr-fg-pri-light-hover: #FCFBFF;
- --bit-clr-fg-pri-light-active: #FFFEFF;
- --bit-clr-fg-pri-dis: #2E292D;
- --bit-clr-fg-pri-dis-text: #77737A;
- --bit-clr-fg-pri-focus: #EBE9F7;
- --bit-clr-fg-sec: #BEBBC8;
- --bit-clr-fg-sec-hover: #C8C5D2;
- --bit-clr-fg-sec-active: #D0CDDA;
- --bit-clr-fg-sec-dark: #A8A5B2;
- --bit-clr-fg-sec-dark-hover: #B0B0BC;
- --bit-clr-fg-sec-dark-active: #B8B8C4;
- --bit-clr-fg-sec-light: #D4D2E0;
- --bit-clr-fg-sec-light-hover: #DEDCEA;
- --bit-clr-fg-sec-light-active: #E6E4F2;
- --bit-clr-fg-sec-dis: #2E292D;
- --bit-clr-fg-sec-dis-text: #77737A;
- --bit-clr-fg-sec-focus: #BEBBC8;
- --bit-clr-fg-ter: #92919D;
- --bit-clr-fg-ter-hover: #9B9AA6;
- --bit-clr-fg-ter-active: #A2A2AE;
- --bit-clr-fg-ter-dark: #7D7C87;
- --bit-clr-fg-ter-dark-hover: #868590;
- --bit-clr-fg-ter-dark-active: #8E8B98;
- --bit-clr-fg-ter-light: #A8A5B2;
- --bit-clr-fg-ter-light-hover: #B0B0BC;
- --bit-clr-fg-ter-light-active: #B8B8C4;
- --bit-clr-fg-ter-dis: #2E292D;
- --bit-clr-fg-ter-dis-text: #77737A;
- --bit-clr-fg-ter-focus: #92919D;
- --bit-clr-fg-dis: #63626D;
- --bit-clr-bg-pri: #14111A;
- --bit-clr-bg-pri-hover: #1A1822;
- --bit-clr-bg-pri-active: #1E1D27;
- --bit-clr-bg-pri-dark: #0A0912;
- --bit-clr-bg-pri-dark-hover: #120F18;
- --bit-clr-bg-pri-dark-active: #16131D;
- --bit-clr-bg-pri-light: #1D1A24;
- --bit-clr-bg-pri-light-hover: #25202B;
- --bit-clr-bg-pri-light-active: #272630;
- --bit-clr-bg-pri-dis: #0F0B0E;
- --bit-clr-bg-pri-dis-text: #5F5E64;
- --bit-clr-bg-pri-focus: #14111A;
- --bit-clr-bg-sec: #1F1E28;
- --bit-clr-bg-sec-hover: #26252F;
- --bit-clr-bg-sec-active: #2B2A35;
- --bit-clr-bg-sec-dark: #15151F;
- --bit-clr-bg-sec-dark-hover: #1E1B25;
- --bit-clr-bg-sec-dark-active: #21202A;
- --bit-clr-bg-sec-light: #2B2631;
- --bit-clr-bg-sec-light-hover: #302F3A;
- --bit-clr-bg-sec-light-active: #35343F;
- --bit-clr-bg-sec-dis: #1B171A;
- --bit-clr-bg-sec-dis-text: #66656B;
- --bit-clr-bg-sec-focus: #1F1E28;
- --bit-clr-bg-ter: #2B2A35;
- --bit-clr-bg-ter-hover: #33303C;
- --bit-clr-bg-ter-active: #373742;
- --bit-clr-bg-ter-dark: #21202A;
- --bit-clr-bg-ter-dark-hover: #2B2631;
- --bit-clr-bg-ter-dark-active: #2E2B37;
- --bit-clr-bg-ter-light: #35343F;
- --bit-clr-bg-ter-light-hover: #3C3C47;
- --bit-clr-bg-ter-light-active: #43404C;
- --bit-clr-bg-ter-dis: #242329;
- --bit-clr-bg-ter-dis-text: #6C6E75;
- --bit-clr-bg-ter-focus: #2B2A35;
- --bit-clr-bg-dis: #1B1923;
+ --bit-clr-fg-pri: #E2EDF7;
+ --bit-clr-fg-pri-hover: #E9F3FD;
+ --bit-clr-fg-pri-active: #F1F7FF;
+ --bit-clr-fg-pri-dark: #CFD9E3;
+ --bit-clr-fg-pri-dark-hover: #D7E1EB;
+ --bit-clr-fg-pri-dark-active: #DDE8F2;
+ --bit-clr-fg-pri-light: #F4F9FF;
+ --bit-clr-fg-pri-light-hover: #F9FCFF;
+ --bit-clr-fg-pri-light-active: #FEFEFF;
+ --bit-clr-fg-pri-dis: #2B2B30;
+ --bit-clr-fg-pri-dis-text: #72757C;
+ --bit-clr-fg-pri-focus: #E2EDF7;
+ --bit-clr-fg-sec: #B6BEC9;
+ --bit-clr-fg-sec-hover: #C0C9D3;
+ --bit-clr-fg-sec-active: #C7D1DB;
+ --bit-clr-fg-sec-dark: #A0A8B3;
+ --bit-clr-fg-sec-dark-hover: #A8B3BC;
+ --bit-clr-fg-sec-dark-active: #B0BBC4;
+ --bit-clr-fg-sec-light: #CBD6E0;
+ --bit-clr-fg-sec-light-hover: #D5E0EA;
+ --bit-clr-fg-sec-light-active: #DDE8F2;
+ --bit-clr-fg-sec-dis: #2B2B30;
+ --bit-clr-fg-sec-dis-text: #72757C;
+ --bit-clr-fg-sec-focus: #B6BEC9;
+ --bit-clr-fg-ter: #8A949D;
+ --bit-clr-fg-ter-hover: #939DA6;
+ --bit-clr-fg-ter-active: #9AA5AE;
+ --bit-clr-fg-ter-dark: #757F88;
+ --bit-clr-fg-ter-dark-hover: #7E8891;
+ --bit-clr-fg-ter-dark-active: #868E99;
+ --bit-clr-fg-ter-light: #A0A8B3;
+ --bit-clr-fg-ter-light-hover: #A8B3BC;
+ --bit-clr-fg-ter-light-active: #B0BBC4;
+ --bit-clr-fg-ter-dis: #2B2B30;
+ --bit-clr-fg-ter-dis-text: #72757C;
+ --bit-clr-fg-ter-focus: #8A949D;
+ --bit-clr-fg-dis: #5C656D;
+ --bit-clr-bg-pri: #0C131B;
+ --bit-clr-bg-pri-hover: #121A23;
+ --bit-clr-bg-pri-active: #171F28;
+ --bit-clr-bg-pri-dark: #050B12;
+ --bit-clr-bg-pri-dark-hover: #0A1119;
+ --bit-clr-bg-pri-dark-active: #0E151E;
+ --bit-clr-bg-pri-light: #151C25;
+ --bit-clr-bg-pri-light-hover: #1D232D;
+ --bit-clr-bg-pri-light-active: #202831;
+ --bit-clr-bg-pri-dis: #0C0C10;
+ --bit-clr-bg-pri-dis-text: #5B6065;
+ --bit-clr-bg-pri-focus: #0C131B;
+ --bit-clr-bg-sec: #182029;
+ --bit-clr-bg-sec-hover: #1F2730;
+ --bit-clr-bg-sec-active: #242C35;
+ --bit-clr-bg-sec-dark: #0F171E;
+ --bit-clr-bg-sec-dark-hover: #161D26;
+ --bit-clr-bg-sec-dark-active: #1A222B;
+ --bit-clr-bg-sec-light: #232933;
+ --bit-clr-bg-sec-light-hover: #29313A;
+ --bit-clr-bg-sec-light-active: #2E363F;
+ --bit-clr-bg-sec-dis: #18181D;
+ --bit-clr-bg-sec-dis-text: #62676C;
+ --bit-clr-bg-sec-focus: #182029;
+ --bit-clr-bg-ter: #242C35;
+ --bit-clr-bg-ter-hover: #2B333D;
+ --bit-clr-bg-ter-active: #2F3A42;
+ --bit-clr-bg-ter-dark: #1A222B;
+ --bit-clr-bg-ter-dark-hover: #232933;
+ --bit-clr-bg-ter-dark-active: #262E38;
+ --bit-clr-bg-ter-light: #2E363F;
+ --bit-clr-bg-ter-light-hover: #343F47;
+ --bit-clr-bg-ter-light-active: #3B434D;
+ --bit-clr-bg-ter-dis: #202429;
+ --bit-clr-bg-ter-dis-text: #697074;
+ --bit-clr-bg-ter-focus: #242C35;
+ --bit-clr-bg-dis: #131B24;
--bit-clr-bg-overlay: rgba(0, 0, 0, 0.6);
- --bit-clr-brd-pri: #7A7885;
- --bit-clr-brd-pri-hover: #898794;
- --bit-clr-brd-pri-active: #9593A0;
- --bit-clr-brd-pri-dark: #696774;
- --bit-clr-brd-pri-dark-hover: #777582;
- --bit-clr-brd-pri-dark-active: #83818E;
- --bit-clr-brd-pri-light: #8C8A97;
- --bit-clr-brd-pri-light-hover: #9C99A7;
- --bit-clr-brd-pri-light-active: #A8A5B3;
- --bit-clr-brd-pri-dis: #2E292D;
- --bit-clr-brd-pri-dis-text: #77737A;
- --bit-clr-brd-pri-focus: #7A7885;
- --bit-clr-brd-sec: #585663;
- --bit-clr-brd-sec-hover: #666471;
- --bit-clr-brd-sec-active: #716F7C;
- --bit-clr-brd-sec-dark: #484551;
- --bit-clr-brd-sec-dark-hover: #555360;
- --bit-clr-brd-sec-dark-active: #605E6B;
- --bit-clr-brd-sec-light: #696774;
- --bit-clr-brd-sec-light-hover: #777582;
- --bit-clr-brd-sec-light-active: #83818E;
- --bit-clr-brd-sec-dis: #2E292D;
- --bit-clr-brd-sec-dis-text: #77737A;
- --bit-clr-brd-sec-focus: #585663;
- --bit-clr-brd-ter: #3C3C47;
- --bit-clr-brd-ter-hover: #484551;
- --bit-clr-brd-ter-active: #504D59;
- --bit-clr-brd-ter-dark: #2E2B37;
- --bit-clr-brd-ter-dark-hover: #373742;
- --bit-clr-brd-ter-dark-active: #403D49;
- --bit-clr-brd-ter-light: #585663;
- --bit-clr-brd-ter-light-hover: #63616E;
- --bit-clr-brd-ter-light-active: #6D6976;
- --bit-clr-brd-ter-dis: #2E292D;
- --bit-clr-brd-ter-dis-text: #77737A;
- --bit-clr-brd-ter-focus: #3C3C47;
- --bit-clr-brd-dis: #302F3A;
+ --bit-clr-brd-pri: #717C86;
+ --bit-clr-brd-pri-hover: #808B95;
+ --bit-clr-brd-pri-active: #8C97A1;
+ --bit-clr-brd-pri-dark: #606B75;
+ --bit-clr-brd-pri-dark-hover: #6E7983;
+ --bit-clr-brd-pri-dark-active: #7A858F;
+ --bit-clr-brd-pri-light: #838E98;
+ --bit-clr-brd-pri-light-hover: #939CA8;
+ --bit-clr-brd-pri-light-active: #9FA8B4;
+ --bit-clr-brd-pri-dis: #2B2B30;
+ --bit-clr-brd-pri-dis-text: #72757C;
+ --bit-clr-brd-pri-focus: #717C86;
+ --bit-clr-brd-sec: #505A63;
+ --bit-clr-brd-sec-hover: #5E6872;
+ --bit-clr-brd-sec-active: #68737D;
+ --bit-clr-brd-sec-dark: #404852;
+ --bit-clr-brd-sec-dark-hover: #4D5660;
+ --bit-clr-brd-sec-dark-active: #58626B;
+ --bit-clr-brd-sec-light: #606B75;
+ --bit-clr-brd-sec-light-hover: #6E7983;
+ --bit-clr-brd-sec-light-active: #7A858F;
+ --bit-clr-brd-sec-dis: #2B2B30;
+ --bit-clr-brd-sec-dis-text: #72757C;
+ --bit-clr-brd-sec-focus: #505A63;
+ --bit-clr-brd-ter: #343F47;
+ --bit-clr-brd-ter-hover: #404852;
+ --bit-clr-brd-ter-active: #48505A;
+ --bit-clr-brd-ter-dark: #262E38;
+ --bit-clr-brd-ter-dark-hover: #2F3A42;
+ --bit-clr-brd-ter-dark-active: #38404A;
+ --bit-clr-brd-ter-light: #505A63;
+ --bit-clr-brd-ter-light-hover: #5B656E;
+ --bit-clr-brd-ter-light-active: #646C78;
+ --bit-clr-brd-ter-dis: #2B2B30;
+ --bit-clr-brd-ter-dis-text: #72757C;
+ --bit-clr-brd-ter-focus: #343F47;
+ --bit-clr-brd-dis: #29313A;
--bit-clr-req: #E56167;
--bit-clr-ntr-white: #FFFFFF;
--bit-clr-ntr-black: #000000;
- --bit-clr-ntr-gray10: #F9FAF6;
- --bit-clr-ntr-gray20: #F2F3EF;
- --bit-clr-ntr-gray30: #EBECE7;
- --bit-clr-ntr-gray40: #DFE0DB;
- --bit-clr-ntr-gray50: #D0D1CC;
- --bit-clr-ntr-gray60: #C6C7C2;
- --bit-clr-ntr-gray70: #BCBCB6;
- --bit-clr-ntr-gray80: #B1B1AB;
- --bit-clr-ntr-gray90: #9FA09B;
- --bit-clr-ntr-gray100: #959691;
- --bit-clr-ntr-gray110: #888984;
- --bit-clr-ntr-gray120: #777873;
- --bit-clr-ntr-gray130: #5E5F5A;
- --bit-clr-ntr-gray140: #464742;
- --bit-clr-ntr-gray150: #3A3B37;
- --bit-clr-ntr-gray160: #31322E;
- --bit-clr-ntr-gray170: #282925;
- --bit-clr-ntr-gray180: #242521;
- --bit-clr-ntr-gray190: #1F201C;
- --bit-clr-ntr-gray200: #1A1B17;
- --bit-clr-ntr-gray210: #151612;
- --bit-clr-ntr-gray220: #10110D;
+ --bit-clr-ntr-gray10: #FCF9F6;
+ --bit-clr-ntr-gray20: #F5F2EF;
+ --bit-clr-ntr-gray30: #EFEBE7;
+ --bit-clr-ntr-gray40: #E3DFDB;
+ --bit-clr-ntr-gray50: #D4D0CC;
+ --bit-clr-ntr-gray60: #CAC6C2;
+ --bit-clr-ntr-gray70: #C0BBB6;
+ --bit-clr-ntr-gray80: #B5B0AB;
+ --bit-clr-ntr-gray90: #A39F9B;
+ --bit-clr-ntr-gray100: #999591;
+ --bit-clr-ntr-gray110: #8C8884;
+ --bit-clr-ntr-gray120: #7B7773;
+ --bit-clr-ntr-gray130: #615E5A;
+ --bit-clr-ntr-gray140: #494642;
+ --bit-clr-ntr-gray150: #3C3A37;
+ --bit-clr-ntr-gray160: #33312E;
+ --bit-clr-ntr-gray170: #2A2825;
+ --bit-clr-ntr-gray180: #262421;
+ --bit-clr-ntr-gray190: #211F1C;
+ --bit-clr-ntr-gray200: #1C1A17;
+ --bit-clr-ntr-gray210: #171512;
+ --bit-clr-ntr-gray220: #12100D;
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-light.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-light.scss
index bd86e6eb33..7a04e58434 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-light.scss
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/Material/colors.material-light.scss
@@ -1,7 +1,21 @@
-// Material preset - light palette. GENERATED by the theme seed pipeline (BitThemeFactory
-// .CreateLightThemeFromSeed("#6750A4", NeutralTintChroma 0.008) with the M3 secondary/tertiary/
-// error mains refilled via BitThemeColorDerivation), so the ramps satisfy the same WCAG contract
-// as the packaged Fluent palettes. Regenerate with the same pipeline rather than editing by hand.
+// Material preset - light palette. GENERATED by the theme seed pipeline, so the ramps satisfy the
+// same WCAG contract as the packaged Fluent palettes. Regenerate with the pipeline, not by hand:
+// 1. BitThemeFactory.CreateLightThemeFromSeed("#1276C6", NeutralTintChroma 0.008) - the packaged
+// palettes' own primary rather than M3's baseline #6750A4, deliberately against what Material
+// names: every packaged preset ships the same brand blue, so the accent switcher's "Default"
+// swatch means one color whichever preset is active, and M3's purple is offered next to it as
+// an accent instead (BitAccentColorPresets.Purple). The neutral tint is what keeps the greys
+// reading as Material's tinted surfaces rather than Fluent's untinted ones - it now carries
+// the seed's blue where it used to carry the purple's.
+// 2. Secondary, tertiary and error refilled from M3's own mains - #625B71, #7D5260, #B3261E -
+// which M3 names independently of the seed, so they stay M3's. Each is refilled by running
+// the main back through the pipeline and taking its primary ladder
+// (CreateLightThemeFromSeed(main).Color.Primary, CreateDarkThemeFromSeed(main) for the dark
+// palette, which applies the packaged light-to-dark relationship to it): primary is the one
+// role the pipeline lands exactly on its seed, so a refilled role carries the same hand-solved
+// ramp as the roles the seed itself produced.
+// 3. Every role's disabled pair overwritten with BitThemeColorDerivation.FillColorRoleFromMain's,
+// which is the pair BitThemeColorDerivationTests holds every packaged palette to.
// The ":root [bit-theme=…]" descendant twins scope the palette to any element carrying the
// attribute (see colors.fluent-light.scss).
:root[bit-theme="material"],
@@ -9,19 +23,19 @@
:root[bit-theme="material-light"],
:root [bit-theme="material-light"] {
color-scheme: light;
- --bit-clr-pri: #6750A4;
- --bit-clr-pri-hover: #5B4693;
- --bit-clr-pri-active: #523D86;
- --bit-clr-pri-dark: #453373;
- --bit-clr-pri-dark-hover: #3A2A63;
- --bit-clr-pri-dark-active: #312255;
- --bit-clr-pri-light: #AC9DDF;
- --bit-clr-pri-light-hover: #A090D5;
- --bit-clr-pri-light-active: #9684CD;
+ --bit-clr-pri: #1276C6;
+ --bit-clr-pri-hover: #0B6AB4;
+ --bit-clr-pri-active: #0460A5;
+ --bit-clr-pri-dark: #005391;
+ --bit-clr-pri-dark-hover: #00487F;
+ --bit-clr-pri-dark-active: #003E70;
+ --bit-clr-pri-light: #85C1FF;
+ --bit-clr-pri-light-hover: #74B4F6;
+ --bit-clr-pri-light-active: #66A9EE;
--bit-clr-pri-text: #FFFFFF;
- --bit-clr-pri-dis: #ECE8FE;
- --bit-clr-pri-dis-text: #847F99;
- --bit-clr-pri-focus: #6750A4;
+ --bit-clr-pri-dis: #DDEDFF;
+ --bit-clr-pri-dis-text: #71869B;
+ --bit-clr-pri-focus: #1276C6;
--bit-clr-sec: #625B71;
--bit-clr-sec-hover: #575064;
--bit-clr-sec-active: #4D475A;
@@ -48,19 +62,19 @@
--bit-clr-ter-dis: #FDE3EB;
--bit-clr-ter-dis-text: #987A83;
--bit-clr-ter-focus: #7D5260;
- --bit-clr-inf: #73707B;
- --bit-clr-inf-hover: #66666F;
- --bit-clr-inf-active: #5D5C65;
- --bit-clr-inf-dark: #514F57;
- --bit-clr-inf-dark-hover: #46444B;
- --bit-clr-inf-dark-active: #3C3B42;
- --bit-clr-inf-light: #BCBAC2;
- --bit-clr-inf-light-hover: #AFAEB7;
- --bit-clr-inf-light-active: #A4A3AC;
+ --bit-clr-inf: #6B737C;
+ --bit-clr-inf-hover: #60686F;
+ --bit-clr-inf-active: #575E65;
+ --bit-clr-inf-dark: #4B5158;
+ --bit-clr-inf-dark-hover: #41464C;
+ --bit-clr-inf-dark-active: #383D42;
+ --bit-clr-inf-light: #B6BCC3;
+ --bit-clr-inf-light-hover: #A9B0B7;
+ --bit-clr-inf-light-active: #9EA5AC;
--bit-clr-inf-text: #FFFFFF;
- --bit-clr-inf-dis: #ECE9F5;
- --bit-clr-inf-dis-text: #84818C;
- --bit-clr-inf-focus: #73707B;
+ --bit-clr-inf-dis: #E3ECF7;
+ --bit-clr-inf-dis-text: #7C848D;
+ --bit-clr-inf-focus: #6B737C;
--bit-clr-suc: #228422;
--bit-clr-suc-hover: #1B771C;
--bit-clr-suc-active: #156B16;
@@ -113,141 +127,141 @@
--bit-clr-err-dis: #FFE4E0;
--bit-clr-err-dis-text: #9A7B76;
--bit-clr-err-focus: #B3261E;
- --bit-clr-fg-pri: #1A191E;
- --bit-clr-fg-pri-hover: #121115;
- --bit-clr-fg-pri-active: #0A090D;
- --bit-clr-fg-pri-dark: #0D0C10;
- --bit-clr-fg-pri-dark-hover: #08080B;
+ --bit-clr-fg-pri: #171A1E;
+ --bit-clr-fg-pri-hover: #0F1216;
+ --bit-clr-fg-pri-active: #080A0D;
+ --bit-clr-fg-pri-dark: #0B0D10;
+ --bit-clr-fg-pri-dark-hover: #06080B;
--bit-clr-fg-pri-dark-active: #000000;
- --bit-clr-fg-pri-light: #2E2D32;
- --bit-clr-fg-pri-light-hover: #26252A;
- --bit-clr-fg-pri-light-active: #1F1E23;
- --bit-clr-fg-pri-dis: #EBEAF0;
- --bit-clr-fg-pri-dis-text: #85848A;
- --bit-clr-fg-pri-focus: #1A191E;
- --bit-clr-fg-sec: #525156;
- --bit-clr-fg-sec-hover: #454449;
- --bit-clr-fg-sec-active: #38373C;
- --bit-clr-fg-sec-dark: #3D3C41;
- --bit-clr-fg-sec-dark-hover: #302F34;
- --bit-clr-fg-sec-dark-active: #242328;
- --bit-clr-fg-sec-light: #6B6A6F;
- --bit-clr-fg-sec-light-hover: #5E5D62;
- --bit-clr-fg-sec-light-active: #4D4C51;
- --bit-clr-fg-sec-dis: #EBEAF0;
- --bit-clr-fg-sec-dis-text: #85848A;
- --bit-clr-fg-sec-focus: #525156;
- --bit-clr-fg-ter: #706F74;
- --bit-clr-fg-ter-hover: #636267;
- --bit-clr-fg-ter-active: #57565B;
- --bit-clr-fg-ter-dark: #5C5B60;
- --bit-clr-fg-ter-dark-hover: #4F4E53;
- --bit-clr-fg-ter-dark-active: #424146;
- --bit-clr-fg-ter-light: #8A898F;
- --bit-clr-fg-ter-light-hover: #7D7C82;
- --bit-clr-fg-ter-light-active: #69686D;
- --bit-clr-fg-ter-dis: #EBEAF0;
- --bit-clr-fg-ter-dis-text: #85848A;
- --bit-clr-fg-ter-focus: #706F74;
- --bit-clr-fg-dis: #8F8E94;
+ --bit-clr-fg-pri-light: #2B2E32;
+ --bit-clr-fg-pri-light-hover: #23262A;
+ --bit-clr-fg-pri-light-active: #1C1F23;
+ --bit-clr-fg-pri-dis: #E7ECF0;
+ --bit-clr-fg-pri-dis-text: #82868A;
+ --bit-clr-fg-pri-focus: #171A1E;
+ --bit-clr-fg-sec: #4F5356;
+ --bit-clr-fg-sec-hover: #424649;
+ --bit-clr-fg-sec-active: #35383C;
+ --bit-clr-fg-sec-dark: #3A3E41;
+ --bit-clr-fg-sec-dark-hover: #2D3034;
+ --bit-clr-fg-sec-dark-active: #212428;
+ --bit-clr-fg-sec-light: #686C70;
+ --bit-clr-fg-sec-light-hover: #5B5F63;
+ --bit-clr-fg-sec-light-active: #4A4E51;
+ --bit-clr-fg-sec-dis: #E7ECF0;
+ --bit-clr-fg-sec-dis-text: #82868A;
+ --bit-clr-fg-sec-focus: #4F5356;
+ --bit-clr-fg-ter: #6D7175;
+ --bit-clr-fg-ter-hover: #606468;
+ --bit-clr-fg-ter-active: #54585C;
+ --bit-clr-fg-ter-dark: #595D61;
+ --bit-clr-fg-ter-dark-hover: #4C5053;
+ --bit-clr-fg-ter-dark-active: #3F4346;
+ --bit-clr-fg-ter-light: #878B8F;
+ --bit-clr-fg-ter-light-hover: #7A7E82;
+ --bit-clr-fg-ter-light-active: #666A6E;
+ --bit-clr-fg-ter-dis: #E7ECF0;
+ --bit-clr-fg-ter-dis-text: #82868A;
+ --bit-clr-fg-ter-focus: #6D7175;
+ --bit-clr-fg-dis: #8C9094;
--bit-clr-bg-pri: #FFFFFF;
- --bit-clr-bg-pri-hover: #F5F4FA;
- --bit-clr-bg-pri-active: #EBEAF0;
- --bit-clr-bg-pri-dark: #EDECF2;
- --bit-clr-bg-pri-dark-hover: #E3E2E8;
- --bit-clr-bg-pri-dark-active: #D9D8DE;
+ --bit-clr-bg-pri-hover: #F1F6FA;
+ --bit-clr-bg-pri-active: #E7ECF0;
+ --bit-clr-bg-pri-dark: #E9EEF2;
+ --bit-clr-bg-pri-dark-hover: #DFE4E8;
+ --bit-clr-bg-pri-dark-active: #D5DADE;
--bit-clr-bg-pri-light: #FFFFFF;
- --bit-clr-bg-pri-light-hover: #F5F4FA;
- --bit-clr-bg-pri-light-active: #EBEAF0;
- --bit-clr-bg-pri-dis: #F7F6FC;
- --bit-clr-bg-pri-dis-text: #8C8B91;
+ --bit-clr-bg-pri-light-hover: #F1F6FA;
+ --bit-clr-bg-pri-light-active: #E7ECF0;
+ --bit-clr-bg-pri-dis: #F3F8FC;
+ --bit-clr-bg-pri-dis-text: #898D91;
--bit-clr-bg-pri-focus: #FFFFFF;
- --bit-clr-bg-sec: #F5F4FA;
- --bit-clr-bg-sec-hover: #EDECF2;
- --bit-clr-bg-sec-active: #E6E5EB;
- --bit-clr-bg-sec-dark: #E8E7ED;
- --bit-clr-bg-sec-dark-hover: #E0DFE5;
- --bit-clr-bg-sec-dark-active: #D9D8DE;
- --bit-clr-bg-sec-light: #FCFCFF;
- --bit-clr-bg-sec-light-hover: #F7F6FC;
- --bit-clr-bg-sec-light-active: #F0EFF5;
- --bit-clr-bg-sec-dis: #EDECF2;
- --bit-clr-bg-sec-dis-text: #87868C;
- --bit-clr-bg-sec-focus: #F5F4FA;
- --bit-clr-bg-ter: #EBEAF0;
- --bit-clr-bg-ter-hover: #E3E2E8;
- --bit-clr-bg-ter-active: #DBDAE0;
- --bit-clr-bg-ter-dark: #DEDDE3;
- --bit-clr-bg-ter-dark-hover: #D6D5DB;
- --bit-clr-bg-ter-dark-active: #CFCED4;
- --bit-clr-bg-ter-light: #F5F4FA;
- --bit-clr-bg-ter-light-hover: #F0EFF5;
- --bit-clr-bg-ter-light-active: #E6E5EB;
- --bit-clr-bg-ter-dis: #E3E2E8;
- --bit-clr-bg-ter-dis-text: #807F85;
- --bit-clr-bg-ter-focus: #EBEAF0;
- --bit-clr-bg-dis: #F0EFF5;
+ --bit-clr-bg-sec: #F1F6FA;
+ --bit-clr-bg-sec-hover: #E9EEF2;
+ --bit-clr-bg-sec-active: #E2E7EB;
+ --bit-clr-bg-sec-dark: #E4E9ED;
+ --bit-clr-bg-sec-dark-hover: #DCE1E5;
+ --bit-clr-bg-sec-dark-active: #D5DADE;
+ --bit-clr-bg-sec-light: #FAFCFF;
+ --bit-clr-bg-sec-light-hover: #F3F8FC;
+ --bit-clr-bg-sec-light-active: #ECF1F5;
+ --bit-clr-bg-sec-dis: #E9EEF2;
+ --bit-clr-bg-sec-dis-text: #84888C;
+ --bit-clr-bg-sec-focus: #F1F6FA;
+ --bit-clr-bg-ter: #E7ECF0;
+ --bit-clr-bg-ter-hover: #DFE4E8;
+ --bit-clr-bg-ter-active: #D7DCE0;
+ --bit-clr-bg-ter-dark: #DADFE3;
+ --bit-clr-bg-ter-dark-hover: #D2D7DB;
+ --bit-clr-bg-ter-dark-active: #CBD0D4;
+ --bit-clr-bg-ter-light: #F1F6FA;
+ --bit-clr-bg-ter-light-hover: #ECF1F5;
+ --bit-clr-bg-ter-light-active: #E2E7EB;
+ --bit-clr-bg-ter-dis: #DFE4E8;
+ --bit-clr-bg-ter-dis-text: #7D8185;
+ --bit-clr-bg-ter-focus: #E7ECF0;
+ --bit-clr-bg-dis: #ECF1F5;
--bit-clr-bg-overlay: rgba(0, 0, 0, 0.4);
- --bit-clr-brd-pri: #706F74;
- --bit-clr-brd-pri-hover: #616065;
- --bit-clr-brd-pri-active: #525156;
- --bit-clr-brd-pri-dark: #59585D;
- --bit-clr-brd-pri-dark-hover: #4A494E;
- --bit-clr-brd-pri-dark-active: #3B3A3F;
- --bit-clr-brd-pri-light: #8C8B91;
- --bit-clr-brd-pri-light-hover: #7D7C82;
- --bit-clr-brd-pri-light-active: #69686D;
- --bit-clr-brd-pri-dis: #EBEAF0;
- --bit-clr-brd-pri-dis-text: #85848A;
- --bit-clr-brd-pri-focus: #706F74;
- --bit-clr-brd-sec: #ADACB2;
- --bit-clr-brd-sec-hover: #9E9DA3;
- --bit-clr-brd-sec-active: #8F8E94;
- --bit-clr-brd-sec-dark: #96959B;
- --bit-clr-brd-sec-dark-hover: #87868C;
- --bit-clr-brd-sec-dark-active: #78777D;
- --bit-clr-brd-sec-light: #C7C6CC;
- --bit-clr-brd-sec-light-hover: #B8B7BD;
- --bit-clr-brd-sec-light-active: #A6A5AB;
- --bit-clr-brd-sec-dis: #EBEAF0;
- --bit-clr-brd-sec-dis-text: #85848A;
- --bit-clr-brd-sec-focus: #ADACB2;
- --bit-clr-brd-ter: #E0DFE5;
- --bit-clr-brd-ter-hover: #D1D0D6;
- --bit-clr-brd-ter-active: #C2C1C7;
- --bit-clr-brd-ter-dark: #C9C8CE;
- --bit-clr-brd-ter-dark-hover: #BAB9BF;
- --bit-clr-brd-ter-dark-active: #ABAAB0;
- --bit-clr-brd-ter-light: #F2F1F7;
- --bit-clr-brd-ter-light-hover: #E8E7ED;
- --bit-clr-brd-ter-light-active: #D9D8DE;
- --bit-clr-brd-ter-dis: #EBEAF0;
- --bit-clr-brd-ter-dis-text: #85848A;
- --bit-clr-brd-ter-focus: #E0DFE5;
- --bit-clr-brd-dis: #E0DFE5;
+ --bit-clr-brd-pri: #6D7175;
+ --bit-clr-brd-pri-hover: #5E6266;
+ --bit-clr-brd-pri-active: #4F5356;
+ --bit-clr-brd-pri-dark: #565A5E;
+ --bit-clr-brd-pri-dark-hover: #474B4E;
+ --bit-clr-brd-pri-dark-active: #383C3F;
+ --bit-clr-brd-pri-light: #898D91;
+ --bit-clr-brd-pri-light-hover: #7A7E82;
+ --bit-clr-brd-pri-light-active: #666A6E;
+ --bit-clr-brd-pri-dis: #E7ECF0;
+ --bit-clr-brd-pri-dis-text: #82868A;
+ --bit-clr-brd-pri-focus: #6D7175;
+ --bit-clr-brd-sec: #A9AEB2;
+ --bit-clr-brd-sec-hover: #9A9FA3;
+ --bit-clr-brd-sec-active: #8C9094;
+ --bit-clr-brd-sec-dark: #93979B;
+ --bit-clr-brd-sec-dark-hover: #84888C;
+ --bit-clr-brd-sec-dark-active: #75797D;
+ --bit-clr-brd-sec-light: #C3C8CC;
+ --bit-clr-brd-sec-light-hover: #B4B9BD;
+ --bit-clr-brd-sec-light-active: #A2A7AB;
+ --bit-clr-brd-sec-dis: #E7ECF0;
+ --bit-clr-brd-sec-dis-text: #82868A;
+ --bit-clr-brd-sec-focus: #A9AEB2;
+ --bit-clr-brd-ter: #DCE1E5;
+ --bit-clr-brd-ter-hover: #CDD2D6;
+ --bit-clr-brd-ter-active: #BEC3C7;
+ --bit-clr-brd-ter-dark: #C5CACE;
+ --bit-clr-brd-ter-dark-hover: #B6BBBF;
+ --bit-clr-brd-ter-dark-active: #A7ACB0;
+ --bit-clr-brd-ter-light: #EEF3F7;
+ --bit-clr-brd-ter-light-hover: #E4E9ED;
+ --bit-clr-brd-ter-light-active: #D5DADE;
+ --bit-clr-brd-ter-dis: #E7ECF0;
+ --bit-clr-brd-ter-dis-text: #82868A;
+ --bit-clr-brd-ter-focus: #DCE1E5;
+ --bit-clr-brd-dis: #DCE1E5;
--bit-clr-req: #A4262C;
--bit-clr-ntr-white: #FFFFFF;
--bit-clr-ntr-black: #000000;
- --bit-clr-ntr-gray10: #F9FAF6;
- --bit-clr-ntr-gray20: #F2F3EF;
- --bit-clr-ntr-gray30: #EBECE7;
- --bit-clr-ntr-gray40: #DFE0DB;
- --bit-clr-ntr-gray50: #D0D1CC;
- --bit-clr-ntr-gray60: #C6C7C2;
- --bit-clr-ntr-gray70: #BCBCB6;
- --bit-clr-ntr-gray80: #B1B1AB;
- --bit-clr-ntr-gray90: #9FA09B;
- --bit-clr-ntr-gray100: #959691;
- --bit-clr-ntr-gray110: #888984;
- --bit-clr-ntr-gray120: #777873;
- --bit-clr-ntr-gray130: #5E5F5A;
- --bit-clr-ntr-gray140: #464742;
- --bit-clr-ntr-gray150: #3A3B37;
- --bit-clr-ntr-gray160: #31322E;
- --bit-clr-ntr-gray170: #282925;
- --bit-clr-ntr-gray180: #242521;
- --bit-clr-ntr-gray190: #1F201C;
- --bit-clr-ntr-gray200: #1A1B17;
- --bit-clr-ntr-gray210: #151612;
- --bit-clr-ntr-gray220: #10110D;
+ --bit-clr-ntr-gray10: #FCF9F6;
+ --bit-clr-ntr-gray20: #F5F2EF;
+ --bit-clr-ntr-gray30: #EFEBE7;
+ --bit-clr-ntr-gray40: #E3DFDB;
+ --bit-clr-ntr-gray50: #D4D0CC;
+ --bit-clr-ntr-gray60: #CAC6C2;
+ --bit-clr-ntr-gray70: #C0BBB6;
+ --bit-clr-ntr-gray80: #B5B0AB;
+ --bit-clr-ntr-gray90: #A39F9B;
+ --bit-clr-ntr-gray100: #999591;
+ --bit-clr-ntr-gray110: #8C8884;
+ --bit-clr-ntr-gray120: #7B7773;
+ --bit-clr-ntr-gray130: #615E5A;
+ --bit-clr-ntr-gray140: #494642;
+ --bit-clr-ntr-gray150: #3C3A37;
+ --bit-clr-ntr-gray160: #33312E;
+ --bit-clr-ntr-gray170: #2A2825;
+ --bit-clr-ntr-gray180: #262421;
+ --bit-clr-ntr-gray190: #211F1C;
+ --bit-clr-ntr-gray200: #1C1A17;
+ --bit-clr-ntr-gray210: #171512;
+ --bit-clr-ntr-gray220: #12100D;
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss
index 8fbad2c685..2a029e40d0 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.scss
@@ -3,13 +3,11 @@
// The size of the control and of its callout is driven by these variables so a single size class,
// applied to the root element and repeated on the callout, resizes every part consistently.
-// The trigger text is a line box stretched over the control, so its line-height is derived from the
-// control height minus the two borders instead of being a fixed length: a preset that raises the
-// control height (Material 40px, Cupertino 36px) then keeps the text centered next to the chevron.
+// A preset that raises the control height (Material 40px, Cupertino 36px) then resizes the trigger,
+// the callout and the rows inside it together.
@mixin drp-size-medium {
--bit-drp-fs: #{$tg-fs-sm};
--bit-drp-h: #{$siz-ctrl-md};
- --bit-drp-lh: calc(#{$siz-ctrl-md} - 2 * #{$shp-border-width});
--bit-drp-lbl-lh: #{spacing(2.75)};
--bit-drp-itm-h: #{$siz-item-md};
--bit-drp-itm-fs: #{$tg-fs-sm};
@@ -262,20 +260,33 @@
overflow: hidden;
font-weight: $tg-fw-regular;
position: relative;
- align-self: stretch;
+ align-self: center;
white-space: nowrap;
box-sizing: border-box;
text-overflow: ellipsis;
font-size: var(--bit-drp-fs);
- line-height: var(--bit-drp-lh);
+ line-height: normal;
border-radius: $shp-radius-control;
padding-inline-start: spacing(1);
+
+ // Centered as a line box of its own rather than as a line box stretched over the control: where
+ // the line box is stretched, the glyphs sit on the half-leading the font's ascent and descent
+ // produce, and no two families put the baseline in the same place - Roboto's ascent minus its
+ // descent falls a pixel short of its cap height at the control font size where Segoe UI's does
+ // not, so the Material trigger read a pixel high while the Fluent one read centered. Trimming the
+ // box down to the cap and the baseline takes the metrics out of it and centers the glyphs
+ // themselves; the symmetric padding is what the overflow clips against, so descenders survive it.
+ @supports (text-box: trim-both cap alphabetic) {
+ text-box: trim-both cap alphabetic;
+ padding-block: 0.35em;
+ }
}
.bit-drp-sch {
.bit-drp-tcn {
height: unset;
display: flex;
+ align-self: stretch;
flex-wrap: wrap;
gap: spacing(0.5);
min-height: var(--bit-drp-h);
@@ -1134,7 +1145,6 @@
.bit-drp-sm {
--bit-drp-fs: #{$tg-fs-xs};
--bit-drp-h: #{$siz-ctrl-sm};
- --bit-drp-lh: calc(#{$siz-ctrl-sm} - 2 * #{$shp-border-width});
--bit-drp-lbl-lh: #{spacing(2.5)};
--bit-drp-itm-h: #{$siz-item-sm};
--bit-drp-itm-fs: #{$tg-fs-xs};
@@ -1147,7 +1157,6 @@
.bit-drp-lg {
--bit-drp-fs: #{$tg-fs-md};
--bit-drp-h: #{$siz-ctrl-lg};
- --bit-drp-lh: calc(#{$siz-ctrl-lg} - 2 * #{$shp-border-width});
--bit-drp-lbl-lh: #{spacing(3)};
--bit-drp-itm-h: #{$siz-item-lg};
--bit-drp-itm-fs: #{$tg-fs-md};
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs
index 356bce53d0..e7af294954 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs
@@ -1419,7 +1419,7 @@ await _js.BitCalloutToggleCallout(
scrollOffset: 0,
headerId: string.Empty,
footerId: string.Empty,
- setCalloutWidth: false,
+ setCalloutWidth: true,
fixedCalloutWidth: FixedCalloutWidth,
maxWindowWidth: 0);
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss b/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss
index 6fb01a98e8..6d63dd67f3 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Notifications/Tag/BitTag.scss
@@ -11,7 +11,7 @@
border-style: $shp-border-style;
border-width: $shp-border-width;
border-radius: $shp-radius-chip;
- padding: var(--bit-tag-padding);
+ padding: var(--bit-tag-pad-y) var(--bit-tag-pad-x);
font-size: var(--bit-tag-fontsize);
min-height: var(--bit-tag-minheight);
letter-spacing: $tg-ctrl-letter-spacing;
@@ -92,20 +92,28 @@
}
+// The inset of a chip is not square: the height is set by --bit-tag-minheight, so the block pad only has
+// to keep the label off the rule, while the inline pad is what makes the chip read as a chip. It is taken
+// from the control padding of the theme rather than from a literal, since the corner is $shp-radius-chip -
+// a pill in Cupertino, 8px in Material - and a rounder corner eats into the inline inset. Half the control
+// padding is the chip's share of it: a tag is a label, not a button.
.bit-tag-sm {
--bit-tag-fontsize: #{$tg-fs-xs};
- --bit-tag-padding: #{spacing(0.250)};
+ --bit-tag-pad-y: #{spacing(0.250)};
+ --bit-tag-pad-x: calc(#{$siz-ctrl-pad-x-sm} / 2);
--bit-tag-minheight: #{spacing(2.25)};
}
.bit-tag-md {
--bit-tag-fontsize: #{$tg-fs-sm};
- --bit-tag-padding: #{spacing(0.375)};
+ --bit-tag-pad-y: #{spacing(0.375)};
+ --bit-tag-pad-x: calc(#{$siz-ctrl-pad-x-md} / 2);
--bit-tag-minheight: #{spacing(2.75)};
}
.bit-tag-lg {
--bit-tag-fontsize: #{$tg-fs-md};
- --bit-tag-padding: #{spacing(0.500)};
+ --bit-tag-pad-y: #{spacing(0.500)};
+ --bit-tag-pad-x: calc(#{$siz-ctrl-pad-x-lg} / 2);
--bit-tag-minheight: #{spacing(3.5)};
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs
index 5a5434852b..5762a3726e 100644
--- a/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Utils/Theme/BitAccentColorPresets.cs
@@ -7,9 +7,10 @@ namespace Bit.BlazorUI;
///
public static class BitAccentColorPresets
{
- // Blue is the packaged Fluent light palette's own primary; the rest are Fluent accent hues.
+ // Blue is the packaged Fluent and Material palettes' own primary - the accent that changes
+ // nothing. Purple is Material 3's baseline primary; the rest are Fluent accent hues.
public const string Blue = "#1276C6";
- public const string Purple = "#8764B8";
+ public const string Purple = "#6750A4";
public const string Green = "#107C10";
public const string Orange = "#CA5010";
public const string Teal = "#038387";
diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
index e51a9643af..d0448518e6 100644
--- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
+++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
@@ -52,8 +52,14 @@
@* bit-theme-persist / bit-theme-persist-cookie: the client keeps the chosen theme in localStorage AND
in the cookie read above, which is what lets the server render the right theme in the first place.
bit-theme-view-transition: theme toggles cross-fade via the View Transitions API (skipped
- automatically on unsupported browsers / prefers-reduced-motion). *@
-
+ automatically on unsupported browsers / prefers-reduced-motion).
+ bit-theme-light / bit-theme-dark: the site dresses in Fluent 2 by default, so the two names the
+ OS-following resolution (bit-theme-system, emitted above whenever nothing is persisted) picks
+ between are the Fluent 2 pair rather than the core light / dark Fluent ones. A visitor who has
+ already picked a design system in the header is painted from their cookie instead and never
+ reaches this. *@
+
@* First thing in the document: re-resolves bit-theme from localStorage / the preference cookie
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentGallery.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentGallery.razor
index f296d81135..e10bba55f4 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentGallery.razor
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentGallery.razor
@@ -3,86 +3,64 @@
@* The library, browsable. Two ways in - type what you are after, or pick the category it belongs
to - over one grid of cards that says what each component is for. It reads ComponentCatalog, so
it can never fall behind the nav the way the hand-written list it replaces did. *@
-
-
-
+
+
-
-
+
- @if (_groups.Count == 0)
- {
- @* A dead end is the one thing a search must never be: this says what was searched for and
+@if (_groups.Count == 0)
+{
+ @* A dead end is the one thing a search must never be: this says what was searched for and
offers the way back to everything. *@
-
-
- No component matches “@_filter”
-
- Try a shorter term, the name another library uses (“select”, “switch”, “chip”),
- or what you want it to do.
-
-
- Show all components
-
-
- }
- else
+
+
+ No component matches “@_filter”
+
+ Try a shorter term, the name another library uses (“select”, “switch”,
+ “chip”),
+ or what you want it to do.
+
+
+ Show all components
+
+
+}
+else
+{
+ @foreach (var group in _groups)
{
- @foreach (var group in _groups)
- {
-
-
+}
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoExample.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoExample.razor.scss
index b9aa68dadd..cab2e1de20 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoExample.razor.scss
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoExample.razor.scss
@@ -55,7 +55,7 @@
// the page behind it.
.example-container {
overflow: auto;
- // padding: rem2(24px);
+ padding: 1rem;
// background-color: $bit-color-background-primary;
@include lt-lg {
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.scss
index 7b30a7dd1f..712a2bd4c4 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.scss
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/DemoPage.razor.scss
@@ -19,15 +19,28 @@
align-items: center;
margin-bottom: $doc-space-md;
padding: $doc-space-2xs $doc-space-sm;
- top: calc(var(--demo-header-height) + 0.35rem + #{$bit-env-inset-top});
+
+ // Both halves of the bar's box come from app.scss rather than from the toggle inside it: the
+ // pivot bar of a multi-API page pins itself directly under this one, and it can only do that
+ // if the height it offsets by is the height this bar actually takes. A floor rather than a
+ // fixed height, so a preset that draws its switch track shorter than the label beside it
+ // still leaves the row tall enough to read.
+ top: calc(var(--demo-motion-bar-top) + #{$bit-env-inset-top});
+ min-height: var(--demo-motion-bar-height);
+
+ // The label is held to one line for the same reason: that height is derived from the switch
+ // track rather than measured, so a label wrapping on a narrow screen would make this bar
+ // taller than the value the pivot bar offsets by - and pin that bar inside this one.
+ ::deep .bit-tgl-stx {
+ white-space: nowrap;
+ }
// Drawn as one more surface in the column under it - same corner family, stroked all round -
// so it does not read as a stray rule across the page.
@include doc-surface;
- // background-color: $bit-color-background-secondary;
.bit-ios & {
- top: calc(var(--demo-header-height) + 0.35rem + env(safe-area-inset-top));
+ top: calc(var(--demo-motion-bar-top) + env(safe-area-inset-top));
}
}
}
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Extensions/IJSRuntimeExtensions.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Extensions/IJSRuntimeExtensions.cs
index 95e37243b1..70f8a512a8 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Extensions/IJSRuntimeExtensions.cs
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Extensions/IJSRuntimeExtensions.cs
@@ -57,6 +57,21 @@ public static async Task UnregisterWindowResizeListener(this IJSRuntime jsRuntim
await jsRuntime.InvokeVoid("unregisterWindowResizeListener", id);
}
+ ///
+ /// Watches the usable width of the element with the given id and reports it - immediately, then on
+ /// every change - to the named method. The iconography grid needs it to know how many icons fit on
+ /// a row, which is what lets it virtualize by row.
+ ///
+ public static async Task ObserveElementWidth(this IJSRuntime jsRuntime, string id, DotNetObjectReference dotnetObj, string methodName) where T : class
+ {
+ await jsRuntime.InvokeVoid("observeElementWidth", id, dotnetObj, methodName);
+ }
+
+ public static async Task UnobserveElementWidth(this IJSRuntime jsRuntime, string id)
+ {
+ await jsRuntime.InvokeVoid("unobserveElementWidth", id);
+ }
+
///
/// Claims Ctrl/Cmd+K (and a bare "/") for the search input inside the element with the given id.
/// Registered from JS rather than through a Blazor key handler so the shortcut works no matter
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor
index b352540975..fb622cf3c9 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor
@@ -27,39 +27,52 @@
descend from - the page's own root markup is all components. *@
-
-
-
- @if (filteredIcons.Count == 0)
- {
- No icon matches that name.
- }
- else
- {
- foreach (var icon in filteredIcons)
+ DebounceTime="300"
+ OnClear="HandleClear"
+ OnChange="HandleChange"
+ Placeholder="Search icons"
+ IconName="@BitIconName.Search"
+ Class="iconography-search-box" />
+
+ @* The icon font carries well over two thousand glyphs, which is more markup than a phone
+ can lay out in one go - so the grid is windowed. BitVirtualize windows a *vertical*
+ list, so the unit of virtualization is a row of icons rather than an icon: the cells
+ are chunked into rows of columnCount (see OnIconGridResized, which recomputes it from
+ the measured width of the scroller), and only the rows in view get rendered.
+
+ The cell is plain markup rather than a BitButton hosting a BitIcon and a BitText,
+ because at this count the per-component cost is the cost: everything the components
+ would have contributed here is a handful of rules in the stylesheet. *@
+
+
+
+
+
+ @* Empty also means "not measured yet", one frame before the observer first
+ reports a width - and that frame must not claim the search found nothing. *@
+ @if (filteredIcons.Count == 0)
{
-
-
-
-
- @icon.Value
-
-
-
+ No icon matches that name.
}
- }
-
-
+
+
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.cs
index 167e9d237a..74987e3fa4 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.cs
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.cs
@@ -5,7 +5,7 @@
namespace Bit.BlazorUI.Demo.Client.Core.Pages.Iconography;
-public partial class IconographyPage
+public partial class IconographyPage : IAsyncDisposable
{
private sealed record IconInfo(string FieldName, string Value)
{
@@ -24,6 +24,17 @@ private sealed record IconInfo(string FieldName, string Value)
private const string COPY_KEY_NAME = "name";
+ private const string GRID_ELEMENT_ID = "iconography-grid";
+
+ // The geometry of one cell of the icon grid, in px, duplicating what the stylesheet lays out -
+ // and it has to stay a duplicate. The virtualizer places its rows at absolute pixel offsets, so
+ // the row height it is handed must be exactly the height a row occupies, and the column count
+ // derived from these must be exactly how many cells a row fits, or the rows drift apart or
+ // overlap. The matching $icon-cell-* values live in IconographyPage.razor.scss.
+ private const int ICON_CELL_WIDTH = 88;
+ private const int ICON_CELL_GAP = 8;
+ private const int ICON_ROW_HEIGHT = 76;
+
private static readonly (string Label, BitColor Value)[] previewColors =
[
("Primary", BitColor.Primary),
@@ -37,10 +48,13 @@ private static readonly (string Label, BitColor Value)[] previewColors =
private List allIcons = default!;
private List filteredIcons = default!;
+ private List iconRows = [];
+ private int columnCount;
private IconInfo? selectedIcon;
private bool isIconPanelOpen;
private Dictionary? iconGlyphs;
private string? copyFeedbackKey;
+ private DotNetObjectReference? dotnetObj;
@@ -66,12 +80,16 @@ protected override void OnInitialized()
///
/// The install snippets are constant, so one Prism pass on the first render is all they need.
/// The panel's own snippets are highlighted when it opens (see ).
+ /// The grid starts being watched here too - it only exists in the DOM once it has rendered.
///
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await _js.InvokeVoid("highlightSnippet");
+
+ dotnetObj = DotNetObjectReference.Create(this);
+ await _js.ObserveElementWidth(GRID_ELEMENT_ID, dotnetObj, nameof(OnIconGridResized));
}
await base.OnAfterRenderAsync(firstRender);
@@ -79,19 +97,66 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
+ ///
+ /// The grid changed width, so the number of icons that fit across it may have changed. Rebuilding
+ /// the rows is only worth a render when it actually did - a scrollbar appearing, or any resize
+ /// smaller than a whole column, leaves the layout exactly as it was.
+ ///
+ [JSInvokable]
+ public Task OnIconGridResized(double width)
+ {
+ if (width <= 0) return Task.CompletedTask;
+
+ var columns = Math.Max(1, (int)((width + ICON_CELL_GAP) / (ICON_CELL_WIDTH + ICON_CELL_GAP)));
+
+ if (columns == columnCount) return Task.CompletedTask;
+
+ columnCount = columns;
+ BuildIconRows();
+
+ return InvokeAsync(StateHasChanged);
+ }
+
+
+
private void HandleClear()
{
filteredIcons = allIcons;
+ BuildIconRows();
}
private void HandleChange(string text)
{
- HandleClear();
- if (string.IsNullOrEmpty(text)) return;
+ filteredIcons = string.IsNullOrEmpty(text)
+ ? allIcons
+ : allIcons.FindAll(icon =>
+ icon.Value.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
+ icon.FieldName.Contains(text, StringComparison.InvariantCultureIgnoreCase));
- filteredIcons = allIcons.FindAll(icon =>
- icon.Value.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
- icon.FieldName.Contains(text, StringComparison.InvariantCultureIgnoreCase));
+ BuildIconRows();
+ }
+
+ ///
+ /// Chunks the icons currently on show into the fixed-width rows the virtualizer renders one at a
+ /// time. Nothing to chunk until the grid has been measured, which is the columnCount 0 case; the
+ /// empty list it leaves behind is what the EmptyTemplate reads as "not measured yet".
+ ///
+ private void BuildIconRows()
+ {
+ if (columnCount == 0)
+ {
+ iconRows = [];
+ return;
+ }
+
+ var rows = new List((filteredIcons.Count + columnCount - 1) / columnCount);
+
+ for (var i = 0; i < filteredIcons.Count; i += columnCount)
+ {
+ rows.Add([.. filteredIcons.GetRange(i, Math.Min(columnCount, filteredIcons.Count - i))]);
+ }
+
+ iconRows = rows;
}
private async Task OpenIconPanel(IconInfo icon)
@@ -174,4 +239,20 @@ private async Task CopyToClipboard(string text, string feedbackKey)
StateHasChanged();
}
}
+
+
+
+ public async ValueTask DisposeAsync()
+ {
+ try
+ {
+ await _js.UnobserveElementWidth(GRID_ELEMENT_ID);
+ }
+ catch (JSDisconnectedException) { } // the circuit is already gone, nothing left to unobserve
+
+ dotnetObj?.Dispose();
+ dotnetObj = null;
+
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.scss
index f9dda0eefe..90698159c2 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.scss
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Iconography/IconographyPage.razor.scss
@@ -1,5 +1,15 @@
@import '../../Styles/abstracts/_docs.scss';
+// The height of one cell of the grid and the gap that follows it. In px, and duplicated by the
+// ICON_CELL_* constants in the code-behind, because the grid is virtualized by row: the virtualizer
+// places rows at absolute pixel offsets, so the row height it is handed - cell plus gap - has to be
+// exactly the height a row occupies here. rem would let a text-zoom move one side of that agreement
+// and not the other. How WIDE a cell is is not settled here: the code-behind divides the measured
+// width by a minimum cell width to get a column count, and the row shares its width out between
+// that many equal columns.
+$icon-cell-height: 68px;
+$icon-cell-gap: 8px;
+
.icon-browser {
display: flex;
gap: $doc-space-sm;
@@ -9,30 +19,64 @@
max-width: rem2(320px);
}
- // A fixed-height scroller rather than a thousand icons in the page flow: the grid is the one
- // place on this site where the number of nodes, not the layout, is what costs.
+ // A fixed-height scroller, and the viewport the virtualizer windows against: the grid is the
+ // one place on this site where the number of nodes, not the layout, is what costs.
::deep .icon-scroller {
+ height: rem2(480px);
padding: $doc-space-sm;
@include doc-surface;
+ @include doc-scrollbar;
+ }
+
+ // One virtualized item. A grid of exactly the column count the code-behind measured, rather
+ // than a row of fixed-width cells, so the last row stays aligned with the ones above it and no
+ // slack is left over on the right - which on a phone, three columns wide, is most of a cell.
+ // Its height is the cell plus the gap that follows it, so the trailing gap sits inside the row
+ // rather than between two absolutely positioned rows, where it would have nothing to push against.
+ ::deep .icon-row {
+ display: grid;
+ gap: $icon-cell-gap;
+ padding-bottom: $icon-cell-gap;
+ grid-template-columns: repeat(var(--icon-cols, 1), minmax(0, 1fr));
}
// The label is revealed on hover and focus rather than always shown: at rest the grid should
- // read as a sheet of glyphs, which is how an icon is actually recognised.
+ // read as a sheet of glyphs, which is how an icon is actually recognised. Its box is reserved
+ // either way, so revealing it cannot move anything.
::deep .icon-box {
- margin: rem2(1px);
- height: auto;
+ gap: rem2(6px);
+ border: none;
+ display: flex;
cursor: pointer;
- min-height: unset;
- width: rem2(88px);
- padding: $doc-space-2xs;
+ color: inherit;
+ overflow: hidden;
+ text-align: center;
+ align-items: center;
+ font-family: inherit;
+ justify-content: center;
+ flex-flow: column nowrap;
+ background-color: transparent;
+ height: $icon-cell-height;
+ padding: rem2(8px) rem2(4px);
border-radius: $bit-shape-radius-surface;
transition: background-color $bit-motion-duration-short $bit-motion-easing;
+ > i {
+ flex: none;
+ font-size: rem2(20px);
+ color: $bit-color-foreground-secondary;
+ }
+
.icon-name {
opacity: 0;
- text-align: center;
+ display: block;
+ overflow: hidden;
+ max-height: rem2(26px);
+ line-height: rem2(13px);
overflow-wrap: anywhere;
+ font-size: $bit-typography-caption2-font-size;
+ font-weight: $bit-typography-caption2-font-weight;
transition: opacity $bit-motion-duration-short $bit-motion-easing;
}
@@ -40,7 +84,6 @@
&:focus-visible,
&:active,
&.selected {
- color: inherit;
background-color: $bit-color-background-secondary;
.icon-name {
@@ -48,6 +91,13 @@
}
}
+ // Inset rather than the docs' offset ring: the cells sit a gap apart, so an outward ring
+ // would draw over its neighbours.
+ &:focus-visible {
+ outline: rem2(2px) solid $bit-color-primary;
+ outline-offset: rem2(-2px);
+ }
+
&.selected {
outline: rem2(1px) solid $bit-color-primary;
}
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/OverviewPage.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/OverviewPage.razor
index fef1ae5525..871d9e1dde 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/OverviewPage.razor
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/OverviewPage.razor
@@ -118,6 +118,8 @@
<BitDatePicker Label="Select a date" />
+
+
The full getting started guide - optional packages, cache busting, MCP
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Theming/ThemingPage.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Theming/ThemingPage.razor
index 984d6ef209..7d59f9c7ce 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Theming/ThemingPage.razor
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Theming/ThemingPage.razor
@@ -1,4 +1,4 @@
-@page "/theming"
+@page "/theming"
@inherits AppComponentBase
@code {
@@ -592,7 +592,7 @@ await _bitThemeManager.SetThemeAsync(corporate);
// Sample accents (hex)
BitAccentColorPresets.Blue; // #1276C6 - the packaged palette's own primary
-BitAccentColorPresets.Purple; // #8764B8
+BitAccentColorPresets.Purple; // #6750A4 - Material 3's baseline primary
BitAccentColorPresets.Green; // #107C10
BitAccentColorPresets.Orange; // #CA5010
BitAccentColorPresets.Teal; // #038387
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Scripts/app.ts b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Scripts/app.ts
index d638cf8f6a..bb185d56d6 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Scripts/app.ts
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Scripts/app.ts
@@ -263,6 +263,39 @@ function unregisterWindowResizeListener(id: string) {
delete windowResizeListeners[id];
}
+const elementWidthObservers: { [key: string]: ResizeObserver } = {};
+
+// Reports the usable (padding-excluded) width of an element to .NET whenever it changes. The
+// iconography grid virtualizes by row, so it has to know how many cells fit across before it can
+// chunk the icons - and a window resize listener would not see the width change when it is the
+// side rail collapsing or a scrollbar appearing rather than the window that moved.
+function observeElementWidth(id: string, dotnetObj: any, methodName: string) {
+ unobserveElementWidth(id);
+
+ const element = document.getElementById(id);
+ if (element == null) return;
+
+ const report = () => {
+ const style = getComputedStyle(element);
+ const width = element.clientWidth - parseFloat(style.paddingInlineStart) - parseFloat(style.paddingInlineEnd);
+ dotnetObj.invokeMethodAsync(methodName, Math.max(0, width));
+ };
+
+ const observer = new ResizeObserver(report);
+ observer.observe(element);
+ elementWidthObservers[id] = observer;
+
+ report();
+}
+
+function unobserveElementWidth(id: string) {
+ const observer = elementWidthObservers[id];
+ if (observer == null) return;
+
+ observer.disconnect();
+ delete elementWidthObservers[id];
+}
+
declare namespace BitBlazorUI {
class Theme { static init(options: any): void; }
}
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor
index b541edd5d6..7fcb886893 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor
@@ -60,19 +60,21 @@
}
+ @* The design system picker and the light/dark toggle are both this component's; all that is
+ left here is the three things it cannot know about - the design systems this site offers
+ (see _designSystems), the theme to show before JS interop is reachable (see OnInitAsync)
+ and the native shell that follows the applied scheme. *@
+
+
-
- @* The design system picker and the light/dark toggle are both this component's; all that is
- left here is the two things it cannot know about - the theme to show before JS interop is
- reachable (see OnInitAsync) and the native shell that follows the applied scheme. *@
-
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor.cs
index a811b74ed3..de94dd74b3 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor.cs
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/AppHeader.razor.cs
@@ -10,6 +10,31 @@ public partial class AppHeader
///
private string? _initialTheme;
+ ///
+ /// The design systems the picker offers: the library's four, in the library's own order, differing from
+ /// only in how Fluent's two schemes are spelled.
+ ///
+ ///
+ /// The default list names Fluent's pair light / dark, which is the pair the switcher hands
+ /// to BitThemeManager.ToggleDarkLightAsync rather than setting outright, so that a host page stays in
+ /// charge of what those two names mean. This host points them at Fluent 2 (see App.razor), which is what
+ /// makes Fluent 2 the site's default - so left as they are, toggling the scheme while on Fluent would
+ /// switch design system. fluent-light / fluent-dark name the same two palettes outright,
+ /// leaving nothing to defer to.
+ ///
+ /// Fluent stays first, and is therefore still the fallback for a theme none of the items claims: after
+ /// the respelling those are exactly the bare light / dark names, which a visitor from
+ /// before the Fluent 2 default may still have persisted - and they are core Fluent.
+ ///
+ ///
+ private static readonly BitThemeSwitcherItem[] _designSystems =
+ [
+ new() { Text = "Fluent", Value = BitThemePresets.Fluent, LightTheme = BitThemePresets.FluentLight, DarkTheme = BitThemePresets.FluentDark },
+ new() { Text = "Fluent 2", Value = BitExtraThemePresets.Fluent2, LightTheme = BitExtraThemePresets.Fluent2Light, DarkTheme = BitExtraThemePresets.Fluent2Dark },
+ new() { Text = "Material", Value = BitExtraThemePresets.Material, LightTheme = BitExtraThemePresets.MaterialLight, DarkTheme = BitExtraThemePresets.MaterialDark },
+ new() { Text = "Cupertino", Value = BitExtraThemePresets.Cupertino, LightTheme = BitExtraThemePresets.CupertinoLight, DarkTheme = BitExtraThemePresets.CupertinoDark },
+ ];
+
///
/// The current path, lower-cased and without the base URI, kept for the active-link check.
///
@@ -51,8 +76,19 @@ protected override async Task OnInitAsync()
// Prerendering has no JS runtime to ask, so the server reads the persisted theme from its
// cookie and cascades it here; the value is then persisted into the prerendered state so the
- // interactive client comes up with the same selection instead of flashing "Fluent" first.
- _initialTheme = await PrerenderStateService.GetValue("AppHeader.Theme", () => Task.FromResult(PersistedTheme));
+ // interactive client comes up with the same selection instead of flashing another one first.
+ // Nothing persisted (a first visit, or a visitor following the OS) means the site default,
+ // which is Fluent 2 - without it the picker would show the first offered item until hydration.
+ // Resolved through the same helper the host page splats onto (see App.razor) rather
+ // than compared as a raw string: it trims, lower-cases and validates the token, so a cookie
+ // reading " System " or a tampered value lands on whatever the document is painting instead
+ // of leaving the picker on a second, different selection. No bit-theme attribute comes back
+ // exactly when the OS is being followed or the stored value was unusable.
+ var persisted = await PrerenderStateService.GetValue("AppHeader.Theme", () => Task.FromResult(PersistedTheme));
+ var rootTheme = BitThemeSsr.BuildRootThemeAttributeMap(persisted);
+ _initialTheme = rootTheme.TryGetValue(BitThemeAttributeNames.Theme, out var theme) && theme is string themeName
+ ? themeName
+ : BitExtraThemePresets.Fluent2;
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs args)
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/abstracts/_bit-css-variables.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/abstracts/_bit-css-variables.scss
index 9d4bf9c5e4..41c1e0fd54 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/abstracts/_bit-css-variables.scss
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/abstracts/_bit-css-variables.scss
@@ -356,6 +356,8 @@ $bit-size-control-lg: var(--bit-siz-ctrl-lg);
$bit-size-icon-sm: var(--bit-siz-icon-sm);
$bit-size-icon-md: var(--bit-siz-icon-md);
$bit-size-icon-lg: var(--bit-siz-icon-lg);
+$bit-size-tab: var(--bit-siz-tab);
+$bit-size-switch-h-md: var(--bit-siz-switch-h-md);
$bit-size-selection-sm: var(--bit-siz-sel-sm);
$bit-size-selection-md: var(--bit-siz-sel-md);
$bit-size-selection-lg: var(--bit-siz-sel-lg);
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/app.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/app.scss
index 462b721717..2c0c0cad0c 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/app.scss
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Styles/app.scss
@@ -29,14 +29,19 @@
// examples still use the full column.
--demo-header-height: #{$doc-header-height};
- // The height of DemoPage's reduced-motion toggle bar. Two unrelated stylesheets need to agree
- // on it: the bar sizes itself from this value, and every other sticky element on a demo page
- // offsets its own top by it so they stack instead of overlapping. It stays 0 while the bar is
- // hidden, which is every state except reduced motion.
+ // Where DemoPage's reduced-motion toggle bar pins itself, and how tall it is. Two unrelated
+ // stylesheets need to agree on both: the bar sizes and places itself from these values, and
+ // every other sticky element on a demo page stacks under them instead of overlapping. The
+ // height stays 0 while the bar is hidden, which is every state except reduced motion.
+ --demo-motion-bar-top: calc(var(--demo-header-height) + #{rem2(2px)});
--demo-motion-bar-height: 0rem;
+ // Where the multi-API pages' sticky pivot bar pins itself: directly under the motion bar, with
+ // a hairline of air between them. Derived rather than written out, because the motion bar's
+ // height is a theme decision (see the reduced-motion block below).
+ --demo-pivot-sticky-top: calc(var(--demo-motion-bar-top) + var(--demo-motion-bar-height) + #{rem2(1px)});
// How far an anchored heading has to stop short of the sticky chrome above it. The default
// clears the app header; a page that pins a second bar (the multi-API pivot) raises it.
- --demo-section-scroll-margin: #{rem2(90px)};
+ --demo-section-scroll-margin: #{rem2(120px)};
}
// The site ignores prefers-reduced-motion everywhere except the component demo pages.
@@ -62,8 +67,16 @@
--bit-mot-easing-spinner: var(--bit-mot-easing-spinner-full, cubic-bezier(0.53, 0.21, 0.29, 0.67));
--bit-mot-loop-factor: var(--bit-mot-loop-factor-full, 1);
- // The bar's own height, which the sticky pivot bar under it offsets itself by.
- --demo-motion-bar-height: #{rem2(32px)};
+ // The bar's own height, which the sticky pivot bar under it offsets itself by. It holds a
+ // single BitToggle on a doc surface, so it is that toggle's switch track plus the surface's
+ // vertical padding and its stroke on both edges. Derived rather than written out as one
+ // number, because the track is a design-system decision - Material draws it 32px and
+ // Cupertino 31px where Fluent draws it 20px - and a literal tuned to Fluent leaves the pivot
+ // bar pinned *inside* this one on the other two presets. The floor keeps the row tall enough
+ // for the label beside a switch a preset draws shorter than its own text - and stands in for
+ // the track entirely under a custom theme that ships no --bit-siz-switch-h-md, which would
+ // otherwise leave the max() invalid at computed-value time and this whole variable unusable.
+ --demo-motion-bar-height: calc(max(var(--bit-siz-switch-h-md, #{rem2(24px)}), #{rem2(24px)}) + #{$doc-space-2xs} * 2 + #{rem2(2px)});
}
// A component demo page with its ForceAnimation toggle off: exactly the library's own reduced
@@ -273,6 +286,13 @@ html, body, .site-content, pre, .table-view, .side-rail {
margin-top: $doc-space-sm;
}
+ // The rule above stops at an accordion, and what a section folds away is the same mix of prose
+ // and code boxes as what it leaves open, so the accordion's body takes the same rhythm rather
+ // than the tags that rule was written to make unnecessary.
+ .bit-acd-con > * + * {
+ margin-top: $doc-space-sm;
+ }
+
> p {
line-height: 1.65;
}
@@ -442,19 +462,30 @@ code {
position: sticky;
padding: 0.25rem 0.5rem;
box-shadow: $bit-box-shadow-callout;
- background-color: $bit-color-background-secondary;
- top: calc(rem2(75px) + #{$bit-env-inset-top} + var(--demo-motion-bar-height));
+ background-color: $bit-color-background-tertiary;
+ top: calc(var(--demo-pivot-sticky-top) + #{$bit-env-inset-top});
.bit-ios & {
- top: calc(rem2(75px) + env(safe-area-inset-top) + var(--demo-motion-bar-height));
+ top: calc(var(--demo-pivot-sticky-top) + env(safe-area-inset-top));
}
}
// A page hosting this bar has one more sticky layer over its examples, so DemoExample's anchored
-// section titles need more clearance than the default. Published as a variable on the page rather
-// than a selector into the pivot's markup, so it holds for any pivot that adopts the class.
+// section titles need more clearance than the default: everything the bar pins itself past, plus
+// the bar itself - a tab row and the padding around it - and a line of air under it. Published as a
+// variable on the page rather than a selector into the pivot's markup, so it holds for any pivot
+// that adopts the class.
+//
+// The first term is the bar's own `top` verbatim, safe-area inset included, so the clearance tracks
+// where the bar actually lands rather than where it would land on a device with no notch; the
+// default's 90px stays the floor, since with the motion bar hidden the derived stack can come out
+// under it on a short header.
.doc-main:has(.pivot-sticky-header) {
- --demo-section-scroll-margin: 11rem;
+ --demo-section-scroll-margin: calc(max(#{rem2(120px)}, var(--demo-pivot-sticky-top) + #{$bit-env-inset-top}) + #{$bit-size-tab} + #{rem2(8px)} + #{$doc-space-2xs});
+
+ .bit-ios & {
+ --demo-section-scroll-margin: calc(max(#{rem2(120px)}, var(--demo-pivot-sticky-top) + env(safe-area-inset-top)) + #{$bit-size-tab} + #{rem2(8px)} + #{$doc-space-2xs});
+ }
}
// The Color sections of the Slider, Toggle, NavBar and Pivot demos put the *Background roles on a
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/wwwroot/index.html b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/wwwroot/index.html
index c2c6feb1c3..78872b7684 100644
--- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/wwwroot/index.html
+++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/wwwroot/index.html
@@ -1,7 +1,9 @@
-
+ (skipped automatically on unsupported WebViews / prefers-reduced-motion).
+ bit-theme: the app dresses in Fluent 2 by default, same as the web host, and opens on its dark
+ scheme; the header's theme switcher takes it from there. -->
+
diff --git a/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorHeadTests.cs b/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorHeadTests.cs
index 309f0d82e1..8b95ffdbe0 100644
--- a/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorHeadTests.cs
+++ b/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorHeadTests.cs
@@ -8,6 +8,11 @@ namespace Bit.BlazorUI.Tests.Components.Extras.AccentColorSwitcher;
[TestClass]
public class BitAccentColorHeadTests : BunitTestContext
{
+ // The second of the DefaultAccents, standing in here for "a configured accent that is not the
+ // neutral one". Read off the preset rather than spelled out, so a retune of the preset cannot
+ // leave these tests asserting on a hex nothing offers any more.
+ private static readonly string PurpleToken = BitAccentColorPresets.Purple.TrimStart('#').ToLowerInvariant();
+
[TestMethod]
public void BitAccentColorHeadShouldEmitNothingByDefault()
{
@@ -64,7 +69,7 @@ public void BitAccentColorHeadInStaticCssModeShouldEmitTheInlineScriptAndInlineT
StringAssert.Contains(component.Markup, BitAccentColorSsr.InlineHeadScriptBody, StringComparison.Ordinal,
"The inline script is what personalizes a cached response pre-paint; without it the component solves nothing.");
- StringAssert.Contains(component.Find("style").TextContent, $"[{BitAccentColorNames.Attribute}=\"8764b8\"]", StringComparison.Ordinal,
+ StringAssert.Contains(component.Find("style").TextContent, $"[{BitAccentColorNames.Attribute}=\"{PurpleToken}\"]", StringComparison.Ordinal,
"With no StylesheetHref, the all-accents stylesheet must be inlined so no endpoint is required.");
Assert.AreEqual(0, component.FindAll("link").Count, "Inlined and linked stylesheets are alternatives, not companions.");
@@ -137,7 +142,7 @@ public void BitAccentColorHeadShouldEmitTheNonceOnThePrerenderStyle()
{
FirstPaintStrategy = BitAccentColorFirstPaintStrategy.StoredCss,
});
- parameters.Add(p => p.PersistedAccent, "8764b8");
+ parameters.Add(p => p.PersistedAccent, PurpleToken);
parameters.Add(p => p.Nonce, "abc123");
});
@@ -168,7 +173,7 @@ public void BitAccentColorHeadInStoredCssModeShouldEmitThePersistedAccentsPreren
{
FirstPaintStrategy = BitAccentColorFirstPaintStrategy.StoredCss,
});
- parameters.Add(p => p.PersistedAccent, "8764b8");
+ parameters.Add(p => p.PersistedAccent, PurpleToken);
});
var style = component.Find($"style[id=\"{BitAccentColorNames.StyleElementId}\"]");
@@ -188,11 +193,11 @@ public void BitAccentColorHeadInStoredCssModeShouldGuardThePrerenderStyleAgainst
FirstPaintStrategy = BitAccentColorFirstPaintStrategy.StoredCss,
Persistence = BitAccentColorPersistence.All,
});
- parameters.Add(p => p.PersistedAccent, "#8764B8");
+ parameters.Add(p => p.PersistedAccent, BitAccentColorPresets.Purple);
});
var style = component.Find($"style[id=\"{BitAccentColorNames.StyleElementId}\"]");
- Assert.AreEqual("8764b8", style.GetAttribute(BitAccentColorNames.StyleAccentAttribute),
+ Assert.AreEqual(PurpleToken, style.GetAttribute(BitAccentColorNames.StyleAccentAttribute),
"The per-request style has to say which accent it was built for, or the guard cannot tell it from the visitor's own snapshot.");
StringAssert.Contains(component.Markup, BitAccentColorSsr.PrerenderCssGuardScript, StringComparison.Ordinal,
@@ -211,7 +216,7 @@ public void BitAccentColorHeadInStoredCssModeShouldNotGuardThePrerenderStyleWith
{
FirstPaintStrategy = BitAccentColorFirstPaintStrategy.StoredCss,
});
- parameters.Add(p => p.PersistedAccent, "8764b8");
+ parameters.Add(p => p.PersistedAccent, PurpleToken);
});
Assert.IsNotNull(component.Find($"style[id=\"{BitAccentColorNames.StyleElementId}\"]"));
@@ -277,7 +282,7 @@ public void BitAccentColorHeadShouldMarkTheSwatchesOfItsOwnAccentList()
var marker = component.FindAll("style").Last().TextContent;
StringAssert.Contains(marker, $"[{BitAccentColorNames.SwatchAttribute}=\"dc143c\"]", StringComparison.Ordinal);
- Assert.IsFalse(marker.Contains("8764b8", StringComparison.Ordinal),
+ Assert.IsFalse(marker.Contains(PurpleToken, StringComparison.Ordinal),
"A custom accent list replaces the defaults here as everywhere else.");
}
@@ -295,7 +300,7 @@ public void BitAccentColorHeadShouldHonorACustomAccentList()
var css = component.Find("style").TextContent;
StringAssert.Contains(css, $"[{BitAccentColorNames.Attribute}=\"dc143c\"]", StringComparison.Ordinal);
- Assert.IsFalse(css.Contains("8764b8", StringComparison.Ordinal),
+ Assert.IsFalse(css.Contains(PurpleToken, StringComparison.Ordinal),
"A custom accent list replaces the defaults; the default palettes must not leak in.");
}
}
diff --git a/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSsrTests.cs b/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSsrTests.cs
index ed00241e6c..813d44fcc1 100644
--- a/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSsrTests.cs
+++ b/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSsrTests.cs
@@ -7,6 +7,11 @@ namespace Bit.BlazorUI.Tests.Components.Extras.AccentColorSwitcher;
[TestClass]
public sealed class BitAccentColorSsrTests
{
+ // The second of the DefaultAccents, standing in here for "a configured accent that is not the
+ // neutral one". Read off the preset rather than spelled out, so a retune of the preset cannot
+ // leave these tests asserting on a hex nothing offers any more.
+ private static readonly string PurpleToken = BitAccentColorPresets.Purple.TrimStart('#').ToLowerInvariant();
+
[TestMethod]
public void InlineHeadScriptWrapsTheBodyInAScriptElement()
{
@@ -126,7 +131,7 @@ public void BuildStaticCssHonorsACustomAccentList()
var css = BitAccentColorSsr.BuildStaticCss([new BitAccentColorItem { Name = "Crimson", Color = "#DC143C" }]);
StringAssert.Contains(css, $"[{BitAccentColorNames.Attribute}=\"dc143c\"]", StringComparison.Ordinal);
- Assert.IsFalse(css.Contains("8764b8", StringComparison.Ordinal),
+ Assert.IsFalse(css.Contains(PurpleToken, StringComparison.Ordinal),
"A custom accent list replaces the defaults; the default palettes must not leak in.");
}
diff --git a/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSwitcherTests.cs b/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSwitcherTests.cs
index e01841468b..961979acc2 100644
--- a/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSwitcherTests.cs
+++ b/src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/AccentColorSwitcher/BitAccentColorSwitcherTests.cs
@@ -8,6 +8,11 @@ namespace Bit.BlazorUI.Tests.Components.Extras.AccentColorSwitcher;
[TestClass]
public class BitAccentColorSwitcherTests : BunitTestContext
{
+ // The second of the DefaultAccents, standing in here for "a configured accent that is not the
+ // neutral one". Read off the preset rather than spelled out, so a retune of the preset cannot
+ // leave these tests asserting on a hex nothing offers any more.
+ private static readonly string PurpleToken = BitAccentColorPresets.Purple.TrimStart('#').ToLowerInvariant();
+
private void RegisterServices(System.Action? accentColor = null)
{
// The switcher resolves the scoped BitAccentColorService, which itself needs the core
@@ -214,7 +219,7 @@ public void BitAccentColorSwitcherShouldApplyWithTheConfiguredStrategyAndPersist
component.FindAll(".bit-acs-swt").First(s => s.GetAttribute("title") == "Purple").Click();
var apply = Context.JSInterop.Invocations.Single(i => i.Identifier == "BitBlazorUI.AccentColor.apply");
- Assert.AreEqual("8764b8", apply.Arguments[0]);
+ Assert.AreEqual(PurpleToken, apply.Arguments[0]);
Assert.IsNull(apply.Arguments[1], "The default None strategy keeps no palette snapshot.");
Assert.AreEqual(false, apply.Arguments[3], "The default None strategy sets no bit-accent attribute.");
Assert.AreEqual((int)BitAccentColorPersistence.LocalStorage, apply.Arguments[4],