diff --git a/news/changelog-1.6.md b/news/changelog-1.6.md index 46d48ab15d0..8738fe9a799 100644 --- a/news/changelog-1.6.md +++ b/news/changelog-1.6.md @@ -44,6 +44,15 @@ All changes included in 1.6: - Update to Reveal JS 5.1.0. - Support for a [Jump To Slide](https://revealjs.com/jump-to-slide/) menu to quickly navigate between slides. Set `jump-to-slide: false` to opt out. - Support for new [Scroll View](https://revealjs.com/scroll-view/) mode with configuration through new `scroll-view` revealjs's format configuration key. A new menu tool has been added to toggle scroll view mode on and off, associated with `R` key by default. +- Styles improvements for Callouts in Revealjs: + - SCSS variables can be used to customize the appearance of callouts in Revealjs. The following SCSS variables are available: + - Border width and scale (`$callout-border-width`, `$callout-border-scale`) + - Border colors (`$callout-color-note`, `$callout-color-tip`, `$callout-color-important`, `$callout-color-caution`, `$callout-color-warning`) + - Margins (`$callout-margin-top`, `$callout-margin-bottom`) + - Color for each callout type is now the same as in Bootstrap document `format: html`. This allows for consistent styling across formats. If you prefer other colors, you can override using the new SCSS variables + - Icon for each callout type is now using SVG like in Bootstrap document `format: html`. This allows for consistent styling across formats. + - Callouts looks better in slides made smaller and when containing code blocks. + - To see how callouts looks like in revealjs, see this example: - Prevent empty SASS built css file to be included in header. - Remove wrong `sourceMappingUrl` entry in SASS built css. - ([#7715](https://github.com/quarto-dev/quarto-cli/issues/7715)): Revealjs don't support anymore special Pandoc syntax making BulletList in Blockquotes become incremental list. This was confusing and unexpected behavior. Supported syntax for incremental list is documented at . diff --git a/src/format/reveal/format-reveal.ts b/src/format/reveal/format-reveal.ts index 701cc51ae10..b5d2f1a473c 100644 --- a/src/format/reveal/format-reveal.ts +++ b/src/format/reveal/format-reveal.ts @@ -285,7 +285,6 @@ export function revealjsFormat() { metadataOverride, templateContext, [kIncludeInHeader]: [ - formatResourcePath("html", "styles-callout.html"), stylesFile, ], html: { diff --git a/src/resources/formats/html/_quarto-rules.scss b/src/resources/formats/html/_quarto-rules.scss index 1af2efb3143..502db518caa 100644 --- a/src/resources/formats/html/_quarto-rules.scss +++ b/src/resources/formats/html/_quarto-rules.scss @@ -438,6 +438,12 @@ a { text-underline-offset: 3px; } +/* Callout styling */ + +.callout pre.sourceCode { + padding-left: 0; +} + // ansi escaping div.ansi-escaped-output { font-family: monospace; diff --git a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss index a5ab4d85f7c..2aa81b39685 100644 --- a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss +++ b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss @@ -885,11 +885,6 @@ pre.sourceCode > code.sourceCode { } } -// no border inside callouts -.callout pre.sourceCode { - padding-left: 0; -} - div.sourceCode { overflow-y: hidden; } diff --git a/src/resources/formats/revealjs/quarto.scss b/src/resources/formats/revealjs/quarto.scss index 9ba2eebfd6d..089aae4668c 100644 --- a/src/resources/formats/revealjs/quarto.scss +++ b/src/resources/formats/revealjs/quarto.scss @@ -1,5 +1,4 @@ /*-- scss:uses --*/ -@use "sass:color" as sass-color; /*-- scss:defaults --*/ @@ -23,6 +22,10 @@ $font-family-monospace-inline: $font-family-monospace !default; $link-weight: $font-weight-base !default; $link-color-bg: transparent !default; $link-decoration: inherit !default; +$font-weight-monospace: $font-weight-base !default; +$font-weight-monospace-block: $font-weight-monospace !default; +$font-weight-monospace-inline: $font-weight-monospace !default; +$code-inline-font-size: $code-font-size !default; // main colors $body-bg: #fff !default; @@ -99,6 +102,18 @@ $input-panel-border-width: $border-width !default; $input-panel-border-radius: $border-radius !default; $input-panel-bg: rgba(248, 249, 250, 1) !default; +// Callouts +$callout-border-width: 0.3rem !default; +$callout-border-scale: 0% !default; +$callout-icon-scale: 10% !default; +$callout-margin-top: 1rem !default; +$callout-margin-bottom: 1rem !default; +$callout-color-note: #0d6efd !default; +$callout-color-tip: #198754 !default; +$callout-color-important: #dc3545 !default; +$callout-color-caution: #fd7e14 !default; +$callout-color-warning: #ffc107 !default; + // alternate colors for when the background changes $light-bg-text-color: #222 !default; $dark-bg-text-color: #fff !default; @@ -114,14 +129,6 @@ $kbd-font-size: $presentation-font-size-root !default; $kbd-color: $body-color !default; $kbd-bg: $gray-100 !default; // like in bootstrap style -// variables required by _brand.yml -$font-family-monospace-block: $font-family-monospace !default; -$font-family-monospace-inline: $font-family-monospace !default; -$font-weight-monospace: $font-weight-base !default; -$font-weight-monospace-block: $font-weight-monospace !default; -$font-weight-monospace-inline: $font-weight-monospace !default; -$code-inline-font-size: $code-font-size !default; - // --- derive reveal versions of presentation variables for finer-grained override --- $revealjs-font-size-root: $presentation-font-size-root !default; @@ -217,13 +224,13 @@ $overlayElementFgColor: 0, 0, 0 !default; /*-- scss:mixins --*/ -@mixin shift_to_dark($property, $color) { +@mixin shift_to_dark($property, $colorDark, $colorLight) { @if ( - sass-color.blackness($backgroundColor) > $code-block-theme-dark-threshhold + quarto-color.blackness($backgroundColor) > $code-block-theme-dark-threshhold ) { - #{$property}: shift-color($color, 70%); + #{$property}: $colorDark; } @else { - #{$property}: $color; + #{$property}: $colorLight; } } @@ -541,7 +548,7 @@ $panel-sidebar-padding: 0.5em; } // On callout we want to make the font-size smaller too - .callout { + div.callout { font-size: #{$presentation-font-smaller}em; // But we don't want headers to change size and they are in em @@ -611,18 +618,18 @@ $panel-sidebar-padding: 0.5em; } &.has-dark-background { - color: sass-color.scale($dark-bg-text-color, $whiteness: 30%); + color: quarto-color.scale($dark-bg-text-color, $whiteness: 30%); a { - color: sass-color.scale($dark-bg-link-color, $whiteness: 30%); + color: quarto-color.scale($dark-bg-link-color, $whiteness: 30%); } } &.has-light-background { - color: sass-color.scale($light-bg-text-color, $whiteness: 30%); + color: quarto-color.scale($light-bg-text-color, $whiteness: 30%); a { - color: sass-color.scale($light-bg-link-color, $whiteness: 30%); + color: quarto-color.scale($light-bg-link-color, $whiteness: 30%); } } } @@ -631,11 +638,11 @@ $panel-sidebar-padding: 0.5em; color: $text-muted; &.has-dark-background { - color: sass-color.scale($dark-bg-text-color, $whiteness: 30%); + color: quarto-color.scale($dark-bg-text-color, $whiteness: 30%); } &.has-light-background { - color: sass-color.scale($light-bg-text-color, $whiteness: 30%); + color: quarto-color.scale($light-bg-text-color, $whiteness: 30%); } } @@ -686,36 +693,6 @@ $panel-sidebar-padding: 0.5em; z-index: 1; } -/* Callout styles */ -.reveal .callout { - &.callout-style-simple, - &.callout-style-default { - .callout-body, - div.callout-title { - font-size: inherit; - } - .callout-icon::before { - height: 2rem; - width: 2rem; - background-size: 2rem 2rem; - } - } - - &.callout-titled { - .callout-title p { - margin-top: 0.5em; - } - - .callout-icon::before { - margin-top: 1rem; - } - - .callout-body > .callout-content > :last-child { - margin-bottom: 1rem; - } - } -} - .reveal .panel-tabset [role="tab"] { padding: 0.25em 0.7em; } @@ -735,7 +712,7 @@ $panel-sidebar-padding: 0.5em; // This is a sentinel value that renderers can use to determine // whether the theme is dark or light @if ( - sass-color.blackness($backgroundColor) > $code-block-theme-dark-threshhold + quarto-color.blackness($backgroundColor) > $code-block-theme-dark-threshhold ) { /*! dark */ .reveal div.callout.callout-style-default .callout-title { @@ -913,7 +890,11 @@ kbd { font-family: $font-family-monospace; font-size: $kbd-font-size; color: $kbd-color; - @include shift_to_dark("background-color", $kbd-bg); + @include shift_to_dark( + "background-color", + shift-color($kbd-bg, 70%), + $kbd-bg + ); border: 1px solid; border-color: $code-block-border-color; border-radius: 5px; @@ -934,3 +915,249 @@ kbd { background-color: $link-color-bg; text-decoration: $link-decoration; } + +/* Callout styles */ + +.reveal div.callout { + margin-top: $callout-margin-top; + margin-bottom: $callout-margin-bottom; + border-radius: $border-radius; + overflow-wrap: break-word; + + // Rules for both styles + &.callout-style-simple, + &.callout-style-default { + border-left: $callout-border-width solid #acacac; + border-right: solid 1px $table-border-color; + border-top: solid 1px $table-border-color; + border-bottom: solid 1px $table-border-color; + + div { + &.callout-body, + &.callout-title { + // Font size is inherited from the parent div.callout + // which is scaled down like .smaller + font-size: inherit; + border-bottom: none; + font-weight: 600; + } + + &.callout-title { + p { + margin-top: 0.5em; + margin-bottom: 0.5em; + } + } + + &.callout-title { + display: flex; + align-items: center; + } + } + + .callout-icon::before { + height: 1.25em; + width: 1.25em; + background-size: 1.25em 1.25em; + } + + &.callout-titled { + .callout-body { + > .callout-content { + > :last-child { + margin-bottom: var(--r-block-margin); + } + > :last-child:not(div.sourceCode) { + padding-bottom: 0.5rem; + margin-bottom: 0; + } + } + } + .callout-icon::before { + margin-top: 0.25em; + padding-right: 0.25em; + } + } + + &.no-icon::before { + display: none !important; + } + } + + // Apply only to simple callout style which could have + // - a title or not + // - an icon or not + &.callout-style-simple { + padding: 0em 0.5em; + display: flex; + + &.callout-titled { + .callout-body { + margin-top: 0.2em; + } + &:not(.no-icon) { + .callout-content { + padding-left: 1.6em; + } + } + .callout-content { + p { + margin-top: 0; + } + } + } + + &:not(.callout-titled) { + .callout-body { + display: flex; + } + .callout-icon::before { + margin-top: var(--r-block-margin); + padding-right: 0.5em; + } + .callout-body { + > .callout-content { + // Margin needs to be added when last child is div.sourceCode + // Other code cell border is mixed with callout border + > div.sourceCode:last-child { + margin-bottom: 1rem; + } + > :first-child { + margin-top: var(--r-block-margin); + } + } + } + } + + .callout-icon::before { + display: inline-block; + content: ""; + background-repeat: no-repeat; + } + + div { + &.callout-title { + opacity: 75%; + } + &.callout-body { + font-weight: 400; + } + } + } + + // Apply only to default callout style which could have + // - a title (can't have no title) + // - an icon or not + &.callout-style-default { + &.callout-titled { + .callout-content { + p { + margin-top: 0.7em; + } + } + } + + .callout-icon::before { + display: inline-block; + content: ""; + background-repeat: no-repeat; + } + + div { + &.callout-body { + font-weight: 400; + } + &.callout-title { + opacity: 85%; + padding-left: 0.5em; + padding-right: 0.5em; + } + &.callout-content { + padding-left: 0.5em; + padding-right: 0.5em; + } + } + } + + // FIXME: There is no body-container in revealjs so remove but before find what it was suppose to do + .callout-body-container { + flex-grow: 1; + } +} + +/* Callout Types */ + +// Generate per callout type css to customize their appearance +// Define the callouts for which we should define styles +$callouts: ( + // NOTE + "note": + ( + "color": $callout-color-note, + "icon": + '', + ), + // TIP + "tip": + ( + "color": $callout-color-tip, + "icon": + '', + ), + // WARNING + "warning": + ( + "color": $callout-color-warning, + "icon": + '', + ), + // CAUTION + "caution": + ( + "color": $callout-color-caution, + "icon": + '', + ), + // IMPORTANT + "important": + ( + "color": $callout-color-important, + "icon": + '', + ) +); + +@each $name, $info in $callouts { + $shifted-color: #{shift-color( + quarto-map.get($info, "color"), + $callout-icon-scale + )}; + $shifted-color-svg: str-replace($shifted-color, "#", "%23"); + + .reveal div.callout { + &.callout-#{$name} { + border-left-color: shift-color( + quarto-map.get($info, "color"), + $callout-border-scale + ); + &.callout-style-default { + .callout-title { + @include shift_to_dark( + "background-color", + shift-color(quarto-map.get($info, "color"), 70%), + shift-color(quarto-map.get($info, "color"), -90%) + ); + } + } + .callout-icon::before { + background-image: #{"url('data:image/svg+xml," + + str-replace( + quarto-map.get($info, "icon"), + 'fill="currentColor"', + 'style="fill: #{$shifted-color-svg}"' + ) + + "');"}; + } + } + } +} diff --git a/tests/docs/playwright/revealjs/callouts/custom-colors.qmd b/tests/docs/playwright/revealjs/callouts/custom-colors.qmd new file mode 100644 index 00000000000..1744ea0fd5d --- /dev/null +++ b/tests/docs/playwright/revealjs/callouts/custom-colors.qmd @@ -0,0 +1,61 @@ +--- +title: callout colors +format: + revealjs: + theme: custom.scss +--- + +## Note + +::: {.callout-note} + +Content with inline code `1 + 1` and block + +```{.python} +1 + 1 +``` +::: + +## Warning + +::: {.callout-warning} + +Content with inline code `1 + 1` and block + +```{.python} +1 + 1 +``` +::: + +## Important + +::: {.callout-important} + +Content with inline code `1 + 1` and block + +```{.python} +1 + 1 +``` +::: + +## Tip + +::: {.callout-tip} + +Content with inline code `1 + 1` and block + +```{.python} +1 + 1 +``` +::: + +## caution + +::: {.callout-caution} + +Content with inline code `1 + 1` and block + +```{.python} +1 + 1 +``` +::: \ No newline at end of file diff --git a/tests/docs/playwright/revealjs/callouts/custom.scss b/tests/docs/playwright/revealjs/callouts/custom.scss new file mode 100644 index 00000000000..f9bd211f95c --- /dev/null +++ b/tests/docs/playwright/revealjs/callouts/custom.scss @@ -0,0 +1,9 @@ +/*-- scss:defaults --*/ + +$callout-color-note: rgb(128, 0, 128); +$callout-color-tip: rgb(255, 255, 0); +$callout-color-important: rgb(128, 128, 128); +$callout-color-caution: rgb(0, 128, 0); +$callout-color-warning: rgb(173, 216, 230); + +$callout-border-width: 10px; diff --git a/tests/integration/playwright/src/utils.ts b/tests/integration/playwright/src/utils.ts index ddaea2d33f7..e8a2f2ee79d 100644 --- a/tests/integration/playwright/src/utils.ts +++ b/tests/integration/playwright/src/utils.ts @@ -1,3 +1,5 @@ +import { expect, Locator } from "@playwright/test"; + export const getUrl = (path: string) => { return `http://127.0.0.1:8080/${path}`; }; @@ -118,3 +120,35 @@ export const checkClick = async (page: any, locator: any) => { } return !error; }; + +export type RGBColor = { + red: number; + green: number; + blue: number; +}; + +export async function checkColor(element, cssProperty, rgbColors: RGBColor) { + await expect(element).toHaveCSS(cssProperty, `rgb(${rgbColors.red}, ${rgbColors.green}, ${rgbColors.blue})`); +} + +export function asRGB(red: number, green: number, blue: number): RGBColor { + return { red, green, blue }; +} + +export async function getCSSProperty(loc: Locator, variable: string, asNumber = false): Promise { + const property = await loc.evaluate((element, variable) => + window.getComputedStyle(element).getPropertyValue(variable), + variable + ); + if (asNumber) { + return parseFloat(property); + } else { + return property; + } +} + + +export async function checkFontSizeIdentical(loc1: Locator, loc2: Locator) { + const loc1FontSize = await getCSSProperty(loc1, 'font-size', false) as string; + await expect(loc2).toHaveCSS('font-size', loc1FontSize); +} \ No newline at end of file diff --git a/tests/integration/playwright/tests/revealjs-themes.spec.ts b/tests/integration/playwright/tests/revealjs-themes.spec.ts index b8e77769ca2..7c6c4d73aee 100644 --- a/tests/integration/playwright/tests/revealjs-themes.spec.ts +++ b/tests/integration/playwright/tests/revealjs-themes.spec.ts @@ -1,21 +1,5 @@ import { test, expect, Locator } from '@playwright/test'; - -async function getCSSProperty(loc: Locator, variable: string, asNumber = false): Promise { - const property = await loc.evaluate((element, variable) => - window.getComputedStyle(element).getPropertyValue(variable), - variable - ); - if (asNumber) { - return parseFloat(property); - } else { - return property; - } -} - -async function checkFontSizeIdentical(loc1: Locator, loc2: Locator) { - const loc1FontSize = await getCSSProperty(loc1, 'font-size', false) as string; - await expect(loc2).toHaveCSS('font-size', loc1FontSize); -} +import { asRGB, checkColor, checkFontSizeIdentical, getCSSProperty, RGBColor } from '../src/utils'; async function getRevealMainFontSize(page: any): Promise { return await getCSSProperty(page.locator('body'), "--r-main-font-size", true) as number; @@ -29,6 +13,7 @@ async function getRevealCodeInlineFontSize(page: any): Promise { return await getCSSProperty(page.locator('body'), "--r-inline-code-font-size", true) as number; } + test('Code font size in callouts and smaller slide is scaled down', async ({ page }) => { await page.goto('./revealjs/code-font-size.html'); await page.locator('body').press('ArrowRight'); @@ -116,4 +101,17 @@ test('Code font size is correctly set', async ({ page }) => { expect( await getCSSProperty(page.locator("#non-highligted pre code"), 'font-size', true) ).toBeCloseTo(codeBlockFontSize); +}); + +test('Callouts can be customized using SCSS variables', async ({ page }) => { + await page.goto('./revealjs/callouts/custom-colors.html'); + async function checkCustom(loc: Locator, width: string, color: RGBColor) { + await expect(loc).toHaveCSS('border-left-width', width); + await checkColor(loc, 'border-left-color', color); + } + await checkCustom(page.locator('div.callout-note'), '10px', asRGB(128, 0, 128)); + await checkCustom(page.locator('div.callout-tip'), '10px', asRGB(255, 255, 0)); + await checkCustom(page.locator('div.callout-warning'), '10px', asRGB(173, 216, 230)); + await checkCustom(page.locator('div.callout-important'), '10px', asRGB(128, 128, 128)); + await checkCustom(page.locator('div.callout-caution'), '10px', asRGB(0, 128, 0)); }); \ No newline at end of file