@@ -184,11 +184,24 @@ const brandColorBundle = (
184184 "/* color variables from _brand.yml */" ,
185185 '// quarto-scss-analysis-annotation { "action": "push", "origin": "_brand.yml color" }' ,
186186 ] ;
187+ const colorCssVariables : string [ ] = [
188+ "/* color CSS variables from _brand.yml */" ,
189+ '// quarto-scss-analysis-annotation { "action": "push", "origin": "_brand.yml color" }' ,
190+ ":root {" ,
191+ ] ;
192+
193+ // Create `brand-` prefixed Sass and CSS variables from color.palette
187194 for ( const colorKey of Object . keys ( brand . data ?. color ?. palette ?? { } ) ) {
195+ const colorVar = colorKey . replace ( / [ ^ a - z A - Z 0 - 9 _ - ] + / , "-" ) ;
188196 colorVariables . push (
189- `$${ colorKey } : ${ brand . getColor ( colorKey ) } !default;` ,
197+ `$brand- ${ colorVar } : ${ brand . getColor ( colorKey ) } !default;` ,
190198 ) ;
199+ colorCssVariables . push (
200+ ` --brand-${ colorVar } : ${ brand . getColor ( colorKey ) } ;` ,
201+ )
191202 }
203+
204+ // Map theme colors directly to Sass variables
192205 for ( const colorKey of Object . keys ( brand . data . color ?? { } ) ) {
193206 if ( colorKey === "palette" ) {
194207 continue ;
@@ -197,6 +210,7 @@ const brandColorBundle = (
197210 `$${ colorKey } : ${ brand . getColor ( colorKey ) } !default;` ,
198211 ) ;
199212 }
213+
200214 // format-specific name mapping
201215 for ( const [ key , value ] of Object . entries ( nameMap ) ) {
202216 const resolvedValue = brand . getColor ( value ) ;
@@ -208,6 +222,7 @@ const brandColorBundle = (
208222 }
209223 // const colorEntries = Object.keys(brand.color);
210224 colorVariables . push ( '// quarto-scss-analysis-annotation { "action": "pop" }' ) ;
225+ colorCssVariables . push ( "}" , '// quarto-scss-analysis-annotation { "action": "pop" }' ) ;
211226 const colorBundle : SassBundleLayers = {
212227 key,
213228 // dependency: "bootstrap",
@@ -216,12 +231,91 @@ const brandColorBundle = (
216231 uses : "" ,
217232 functions : "" ,
218233 mixins : "" ,
219- rules : "" ,
234+ rules : colorCssVariables . join ( "\n" ) ,
220235 } ,
221236 } ;
222237 return colorBundle ;
223238} ;
224239
240+ const brandBootstrapBundle = (
241+ brand : Brand ,
242+ key : string
243+ ) : SassBundleLayers => {
244+ // Bootstrap Variables from brand.defaults.bootstrap
245+ const brandBootstrap = ( brand ?. data ?. defaults ?. bootstrap as unknown as Record <
246+ string ,
247+ Record < string , string | boolean | number | null >
248+ > ) ;
249+
250+ const bsVariables : string [ ] = [
251+ "/* Bootstrap variables from _brand.yml */" ,
252+ '// quarto-scss-analysis-annotation { "action": "push", "origin": "_brand.yml defaults.bootstrap" }' ,
253+ ] ;
254+ for ( const bsVar of Object . keys ( brandBootstrap ) ) {
255+ if ( bsVar === "version" ) {
256+ continue ;
257+ }
258+ bsVariables . push (
259+ `$${ bsVar } : ${ brandBootstrap [ bsVar ] } !default;` ,
260+ ) ;
261+ }
262+ bsVariables . push ( '// quarto-scss-analysis-annotation { "action": "pop" }' ) ;
263+
264+ // Bootstrap Colors from color.palette
265+ let bootstrapColorVariables : string [ ] = [ ] ;
266+ if ( Number ( brandBootstrap ?. version ?? 5 ) === 5 ) {
267+ // https://getbootstrap.com/docs/5.3/customize/color/#color-sass-maps
268+ bootstrapColorVariables = [
269+ "blue" ,
270+ "indigo" ,
271+ "purple" ,
272+ "pink" ,
273+ "red" ,
274+ "orange" ,
275+ "yellow" ,
276+ "green" ,
277+ "teal" ,
278+ "cyan" ,
279+ "black" ,
280+ "white" ,
281+ "gray" ,
282+ "gray-dark"
283+ ]
284+ }
285+
286+ const bsColors : string [ ] = [
287+ "/* Bootstrap color variables from _brand.yml */" ,
288+ '// quarto-scss-analysis-annotation { "action": "push", "origin": "_brand.yml color.palette" }' ,
289+ ] ;
290+
291+ if ( bootstrapColorVariables . length > 0 ) {
292+ for ( const colorKey of Object . keys ( brand . data ?. color ?. palette ?? { } ) ) {
293+ if ( ! bootstrapColorVariables . includes ( colorKey ) ) {
294+ continue ;
295+ }
296+
297+ bsColors . push (
298+ `$${ colorKey } : ${ brand . getColor ( colorKey ) } !default;` ,
299+ ) ;
300+ }
301+ }
302+
303+ bsColors . push ( '// quarto-scss-analysis-annotation { "action": "pop" }' ) ;
304+
305+ const bsBundle : SassBundleLayers = {
306+ key,
307+ // dependency: "bootstrap",
308+ quarto : {
309+ defaults : bsColors . join ( "\n" ) + "\n" + bsVariables . join ( "\n" ) ,
310+ uses : "" ,
311+ functions : "" ,
312+ mixins : "" ,
313+ rules : "" ,
314+ } ,
315+ } ;
316+ return bsBundle ;
317+ } ;
318+
225319const brandTypographyBundle = (
226320 brand : Brand ,
227321 key : string ,
@@ -450,7 +544,7 @@ const brandTypographyBundle = (
450544 return typographyBundle ;
451545} ;
452546
453- export async function brandBootstrapSassBundleLayers (
547+ export async function brandSassBundleLayers (
454548 fileName : string | undefined ,
455549 project : ProjectContext ,
456550 key : string ,
@@ -470,12 +564,33 @@ export async function brandBootstrapSassBundleLayers(
470564 return sassBundles ;
471565}
472566
567+ export async function brandBootstrapSassBundleLayers (
568+ fileName : string | undefined ,
569+ project : ProjectContext ,
570+ key : string ,
571+ nameMap : Record < string , string > = { } ,
572+ ) : Promise < SassBundleLayers [ ] > {
573+ const brand = await project . resolveBrand ( fileName ) ;
574+ const sassBundles = await brandSassBundleLayers ( fileName , project , key , nameMap ) ;
575+
576+ if ( brand ?. data ?. defaults ?. bootstrap ) {
577+ const bsBundle = brandBootstrapBundle ( brand , key ) ;
578+ if ( bsBundle ) {
579+ // Add bsBundle to the beginning of the array so that defaults appear
580+ // *after* the rest of the brand variables.
581+ sassBundles . unshift ( bsBundle ) ;
582+ }
583+ }
584+
585+ return sassBundles ;
586+ }
587+
473588export async function brandRevealSassBundleLayers (
474589 input : string | undefined ,
475590 _format : Format ,
476591 project : ProjectContext ,
477592) : Promise < SassBundleLayers [ ] > {
478- return brandBootstrapSassBundleLayers (
593+ return brandSassBundleLayers (
479594 input ,
480595 project ,
481596 "reveal-theme" ,
0 commit comments