@@ -3,7 +3,10 @@ import {
33 baseLayerLuminance ,
44 SwatchRGB ,
55 fillColor ,
6- neutralLayerL2
6+ neutralLayerL2 ,
7+ neutralPalette ,
8+ DesignToken ,
9+ neutralFillLayerRestDelta
710} from "/_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.lib.module.js" ;
811
912const currentThemeCookieName = "currentTheme" ;
@@ -12,6 +15,7 @@ const themeSettingDark = "Dark";
1215const themeSettingLight = "Light" ;
1316const darkThemeLuminance = 0.19 ;
1417const lightThemeLuminance = 1.0 ;
18+ const darknessLuminanceTarget = ( - 0.1 + Math . sqrt ( 0.21 ) ) / 2 ;
1519
1620/**
1721 * Updates the current theme on the site based on the specified theme
@@ -160,11 +164,97 @@ function applyTheme(theme) {
160164 setThemeOnDocument ( theme ) ;
161165}
162166
167+ /**
168+ *
169+ * @param {Palette } palette
170+ * @param {number } baseLayerLuminance
171+ * @returns {number }
172+ */
173+ function neutralLayer1Index ( palette , baseLayerLuminance ) {
174+ return palette . closestIndexOf ( SwatchRGB . create ( baseLayerLuminance , baseLayerLuminance , baseLayerLuminance ) ) ;
175+ }
176+
177+ /**
178+ *
179+ * @param {Palette } palette
180+ * @param {Swatch } reference
181+ * @param {number } baseLayerLuminance
182+ * @param {number } layerDelta
183+ * @param {number } hoverDeltaLight
184+ * @param {number } hoverDeltaDark
185+ * @returns {Swatch }
186+ */
187+ function neutralLayerHoverAlgorithm ( palette , reference , baseLayerLuminance , layerDelta , hoverDeltaLight , hoverDeltaDark ) {
188+ const baseIndex = neutralLayer1Index ( palette , baseLayerLuminance ) ;
189+ // Determine both the size of the delta (from the value passed in) and the direction (if the current color is dark,
190+ // the hover color will be a lower index (lighter); if the current color is light, the hover color will be a higher index (darker))
191+ const hoverDelta = isDark ( reference ) ? hoverDeltaDark * - 1 : hoverDeltaLight ;
192+ return palette . get ( baseIndex + ( layerDelta * - 1 ) + hoverDelta ) ;
193+ }
194+
195+ /**
196+ *
197+ * @param {Swatch } color
198+ * @returns {boolean }
199+ */
200+ function isDark ( color ) {
201+ return color . relativeLuminance <= darknessLuminanceTarget ;
202+ }
203+
204+ /**
205+ * Creates additional design tokens that are used to define the hover colors for the neutral layers
206+ * used in the site theme (neutral-layer-1 and neutral-layer-2, specifically). Unlike other -hover
207+ * variants, these are not created by the design system by default so we need to create them ourselves.
208+ * This is a lightly tweaked variant of other hover recipes used in the design system.
209+ */
210+ function createAdditionalDesignTokens ( ) {
211+ const neutralLayer1HoverLightDelta = DesignToken . create ( { name : 'neutral-layer-1-hover-light-delta' , cssCustomPropertyName : null } ) . withDefault ( 3 ) ;
212+ const neutralLayer1HoverDarkDelta = DesignToken . create ( { name : 'neutral-layer-1-hover-dark-delta' , cssCustomPropertyName : null } ) . withDefault ( 2 ) ;
213+ const neutralLayer2HoverLightDelta = DesignToken . create ( { name : 'neutral-layer-2-hover-light-delta' , cssCustomPropertyName : null } ) . withDefault ( 2 ) ;
214+ const neutralLayer2HoverDarkDelta = DesignToken . create ( { name : 'neutral-layer-2-hover-dark-delta' , cssCustomPropertyName : null } ) . withDefault ( 2 ) ;
215+
216+ const neutralLayer1HoverRecipe = DesignToken . create ( { name : 'neutral-layer-1-hover-recipe' , cssCustomPropertyName : null } ) . withDefault ( {
217+ evaluate : ( element , reference ) =>
218+ neutralLayerHoverAlgorithm (
219+ neutralPalette . getValueFor ( element ) ,
220+ reference || fillColor . getValueFor ( element ) ,
221+ baseLayerLuminance . getValueFor ( element ) ,
222+ 0 , // No layer delta since this is for neutral-layer-1
223+ neutralLayer1HoverLightDelta . getValueFor ( element ) ,
224+ neutralLayer1HoverDarkDelta . getValueFor ( element )
225+ ) ,
226+ } ) ;
227+
228+ const neutralLayer2HoverRecipe = DesignToken . create ( { name : 'neutral-layer-2-hover-recipe' , cssCustomPropertyName : null } ) . withDefault ( {
229+ evaluate : ( element , reference ) =>
230+ neutralLayerHoverAlgorithm (
231+ neutralPalette . getValueFor ( element ) ,
232+ reference || fillColor . getValueFor ( element ) ,
233+ baseLayerLuminance . getValueFor ( element ) ,
234+ // Use the same layer delta used by the base recipe to calculate layer 2
235+ neutralFillLayerRestDelta . getValueFor ( element ) ,
236+ neutralLayer2HoverLightDelta . getValueFor ( element ) ,
237+ neutralLayer2HoverDarkDelta . getValueFor ( element )
238+ ) ,
239+ } ) ;
240+
241+ // Creates the --neutral-layer-1-hover custom CSS property
242+ DesignToken . create ( 'neutral-layer-1-hover' ) . withDefault ( ( element ) =>
243+ neutralLayer1HoverRecipe . getValueFor ( element ) . evaluate ( element ) ,
244+ ) ;
245+
246+ // Creates the --neutral-layer-2-hover custom CSS property
247+ DesignToken . create ( 'neutral-layer-2-hover' ) . withDefault ( ( element ) =>
248+ neutralLayer2HoverRecipe . getValueFor ( element ) . evaluate ( element ) ,
249+ ) ;
250+ }
251+
163252function initializeTheme ( ) {
164253 const themeCookieValue = getThemeCookieValue ( ) ;
165254 const effectiveTheme = getEffectiveTheme ( themeCookieValue ) ;
166255
167256 applyTheme ( effectiveTheme ) ;
168257}
169258
259+ createAdditionalDesignTokens ( ) ;
170260initializeTheme ( ) ;
0 commit comments