From 180830327e6e97b13fef0355c36203c998a3bfc4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 25 Oct 2024 14:46:20 -0400 Subject: [PATCH] feat(theme): Branded theming via brand_yml (#1743) Co-authored-by: Carson Sievert --- CHANGELOG.md | 2 + docs/_quarto.yml | 3 + examples/brand/Monda-OFL.txt | 93 ++++++ examples/brand/Monda.ttf | Bin 0 -> 167284 bytes examples/brand/_brand.yml | 115 +++++++ examples/brand/_colors.scss | 116 +++++++ examples/brand/app-express.py | 10 + examples/brand/app.py | 342 ++++++++++++++++++++ examples/brand/requirements.txt | 3 + pyproject.toml | 6 +- shiny/_main.py | 2 + shiny/ui/_html_deps_external.py | 2 +- shiny/ui/_theme.py | 125 +++++++- shiny/ui/_theme_brand.py | 552 ++++++++++++++++++++++++++++++++ tests/pytest/test_theme.py | 4 +- 15 files changed, 1354 insertions(+), 21 deletions(-) create mode 100644 examples/brand/Monda-OFL.txt create mode 100644 examples/brand/Monda.ttf create mode 100644 examples/brand/_brand.yml create mode 100644 examples/brand/_colors.scss create mode 100644 examples/brand/app-express.py create mode 100644 examples/brand/app.py create mode 100644 examples/brand/requirements.txt create mode 100644 shiny/ui/_theme_brand.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b7b9a77..68dafcce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added [narwhals](https://posit-dev.github.io/py-narwhals) support for `@render.table`. This allows for any eager data frame supported by narwhals to be returned from a `@render.table` output method. (#1570) +* Shiny now supports theming via [brand.yml](https://posit-dev.github.io/brand-yml) with a single `_brand.yml` file. Call `ui.Theme.from_brand()` with `__file__` or the path to a `_brand.yml` file and pass the resulting theme to the `theme` argument of `express.ui.page_opts()` (Shiny Express) or `ui.page_*()` functions (Shiny Core) to apply the brand theme to the entire app. (#1743) + * `chat_ui()` and `Chat.ui()` gain a `messages` parameter for providing starting messages. (#1736) ### Other changes diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 18c79c02d..b656d926e 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -44,3 +44,6 @@ interlinks: url: https://matplotlib.org/stable/ python: url: https://docs.python.org/3/ + brand-yml: + url: https://posit-dev.github.io/brand-yml/ + inv: objects.txt diff --git a/examples/brand/Monda-OFL.txt b/examples/brand/Monda-OFL.txt new file mode 100644 index 000000000..4a58f7175 --- /dev/null +++ b/examples/brand/Monda-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Monda Project Authors (https://github.com/googlefonts/mondaFont) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/examples/brand/Monda.ttf b/examples/brand/Monda.ttf new file mode 100644 index 0000000000000000000000000000000000000000..458b865a31ecab97ff4de4726ca458092ce386e5 GIT binary patch literal 167284 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 literal 0 HcmV?d00001 diff --git a/examples/brand/_brand.yml b/examples/brand/_brand.yml new file mode 100644 index 000000000..577d62aa6 --- /dev/null +++ b/examples/brand/_brand.yml @@ -0,0 +1,115 @@ +meta: + name: + full: "Retro Arcade Brand" + short: "RetroArc" + link: + home: https://retroarc.example.com + mastodon: https://mastodon.social/@retroarc + github: https://github.com/retroarc + linkedin: https://linkedin.com/company/retroarc + twitter: https://twitter.com/retroarc + facebook: https://facebook.com/retroarc + +# logo: +# images: +# icon-light: logos/retroarc-icon-light.png +# icon-dark: logos/retroarc-icon-dark.png +# wide-light: logos/retroarc-wide-light.png +# wide-dark: logos/retroarc-wide-dark.png +# tall-light: logos/retroarc-tall-light.png +# tall-dark: logos/retroarc-tall-dark.png +# small: +# light: logos/retroarc-icon-light.png +# dark: logos/retroarc-icon-dark.png +# medium: +# light: logos/retroarc-wide-light.png +# dark: logos/retroarc-wide-dark.png +# large: +# light: logos/retroarc-tall-light.png +# dark: logos/retroarc-tall-dark.png + +color: + palette: + pink: "#E83E8C" + blue: "#007BFF" + cyan: "#17A2B8" + teal: "#20C997" + green: "#28A745" + yellow: "#FFD700" + orange: "#FF7F50" + red: "#FF3333" + purple: "#6F42C1" + indigo: "#6610F2" + black: "#1A1A1A" + white: "#F8F8F8" + foreground: black + background: white + primary: purple + success: green + info: cyan + warning: yellow + danger: orange + light: white + dark: black + +typography: + fonts: + - family: Quantico + source: google + weight: [700] + style: [normal, italic] + display: swap + - family: Monda + source: file + files: + - path: Monda.ttf + weight: 400..700 + - family: Share Tech Mono + source: bunny + weight: 400 + style: normal + display: swap + base: + family: Monda + size: 17px + weight: 400 + line-height: 1.5 + headings: + family: Quantico + weight: 400 + line-height: 1.2 + style: normal + monospace: + family: Share Tech Mono + size: 0.9em + weight: 400 + monospace-inline: + family: Share Tech Mono + # size: 0.9em + weight: 400 + color: yellow + background-color: "#1a1a1add" + monospace-block: + family: Share Tech Mono + size: 1.1em + weight: 400 + color: green + background-color: black + line-height: 1.4 + link: + weight: 400 + background-color: purple + color: white + decoration: "underline" + +defaults: + bootstrap: + defaults: + my-pink: "$brand-pink" + shiny: + theme: + preset: shiny + rules: | + .navbar-brand { color: $my-pink } + # TODO: Find an appropriate theme variable to set + # navbar-bg: $brand-purple diff --git a/examples/brand/_colors.scss b/examples/brand/_colors.scss new file mode 100644 index 000000000..618ba867a --- /dev/null +++ b/examples/brand/_colors.scss @@ -0,0 +1,116 @@ +// https://github.com/twbs/bootstrap/blob/v5.3.3/site/assets/scss/_colors.scss + +.bd-blue-100 { color: color-contrast($blue-100); background-color: $blue-100; } +.bd-blue-200 { color: color-contrast($blue-200); background-color: $blue-200; } +.bd-blue-300 { color: color-contrast($blue-300); background-color: $blue-300; } +.bd-blue-400 { color: color-contrast($blue-400); background-color: $blue-400; } +.bd-blue-500 { color: color-contrast($blue-500); background-color: $blue-500; } +.bd-blue-600 { color: color-contrast($blue-600); background-color: $blue-600; } +.bd-blue-700 { color: color-contrast($blue-700); background-color: $blue-700; } +.bd-blue-800 { color: color-contrast($blue-800); background-color: $blue-800; } +.bd-blue-900 { color: color-contrast($blue-900); background-color: $blue-900; } + +.bd-indigo-100 { color: color-contrast($indigo-100); background-color: $indigo-100; } +.bd-indigo-200 { color: color-contrast($indigo-200); background-color: $indigo-200; } +.bd-indigo-300 { color: color-contrast($indigo-300); background-color: $indigo-300; } +.bd-indigo-400 { color: color-contrast($indigo-400); background-color: $indigo-400; } +.bd-indigo-500 { color: color-contrast($indigo-500); background-color: $indigo-500; } +.bd-indigo-600 { color: color-contrast($indigo-600); background-color: $indigo-600; } +.bd-indigo-700 { color: color-contrast($indigo-700); background-color: $indigo-700; } +.bd-indigo-800 { color: color-contrast($indigo-800); background-color: $indigo-800; } +.bd-indigo-900 { color: color-contrast($indigo-900); background-color: $indigo-900; } + +.bd-purple-100 { color: color-contrast($purple-100); background-color: $purple-100; } +.bd-purple-200 { color: color-contrast($purple-200); background-color: $purple-200; } +.bd-purple-300 { color: color-contrast($purple-300); background-color: $purple-300; } +.bd-purple-400 { color: color-contrast($purple-400); background-color: $purple-400; } +.bd-purple-500 { color: color-contrast($purple-500); background-color: $purple-500; } +.bd-purple-600 { color: color-contrast($purple-600); background-color: $purple-600; } +.bd-purple-700 { color: color-contrast($purple-700); background-color: $purple-700; } +.bd-purple-800 { color: color-contrast($purple-800); background-color: $purple-800; } +.bd-purple-900 { color: color-contrast($purple-900); background-color: $purple-900; } + +.bd-pink-100 { color: color-contrast($pink-100); background-color: $pink-100; } +.bd-pink-200 { color: color-contrast($pink-200); background-color: $pink-200; } +.bd-pink-300 { color: color-contrast($pink-300); background-color: $pink-300; } +.bd-pink-400 { color: color-contrast($pink-400); background-color: $pink-400; } +.bd-pink-500 { color: color-contrast($pink-500); background-color: $pink-500; } +.bd-pink-600 { color: color-contrast($pink-600); background-color: $pink-600; } +.bd-pink-700 { color: color-contrast($pink-700); background-color: $pink-700; } +.bd-pink-800 { color: color-contrast($pink-800); background-color: $pink-800; } +.bd-pink-900 { color: color-contrast($pink-900); background-color: $pink-900; } + +.bd-red-100 { color: color-contrast($red-100); background-color: $red-100; } +.bd-red-200 { color: color-contrast($red-200); background-color: $red-200; } +.bd-red-300 { color: color-contrast($red-300); background-color: $red-300; } +.bd-red-400 { color: color-contrast($red-400); background-color: $red-400; } +.bd-red-500 { color: color-contrast($red-500); background-color: $red-500; } +.bd-red-600 { color: color-contrast($red-600); background-color: $red-600; } +.bd-red-700 { color: color-contrast($red-700); background-color: $red-700; } +.bd-red-800 { color: color-contrast($red-800); background-color: $red-800; } +.bd-red-900 { color: color-contrast($red-900); background-color: $red-900; } + +.bd-orange-100 { color: color-contrast($orange-100); background-color: $orange-100; } +.bd-orange-200 { color: color-contrast($orange-200); background-color: $orange-200; } +.bd-orange-300 { color: color-contrast($orange-300); background-color: $orange-300; } +.bd-orange-400 { color: color-contrast($orange-400); background-color: $orange-400; } +.bd-orange-500 { color: color-contrast($orange-500); background-color: $orange-500; } +.bd-orange-600 { color: color-contrast($orange-600); background-color: $orange-600; } +.bd-orange-700 { color: color-contrast($orange-700); background-color: $orange-700; } +.bd-orange-800 { color: color-contrast($orange-800); background-color: $orange-800; } +.bd-orange-900 { color: color-contrast($orange-900); background-color: $orange-900; } + +.bd-yellow-100 { color: color-contrast($yellow-100); background-color: $yellow-100; } +.bd-yellow-200 { color: color-contrast($yellow-200); background-color: $yellow-200; } +.bd-yellow-300 { color: color-contrast($yellow-300); background-color: $yellow-300; } +.bd-yellow-400 { color: color-contrast($yellow-400); background-color: $yellow-400; } +.bd-yellow-500 { color: color-contrast($yellow-500); background-color: $yellow-500; } +.bd-yellow-600 { color: color-contrast($yellow-600); background-color: $yellow-600; } +.bd-yellow-700 { color: color-contrast($yellow-700); background-color: $yellow-700; } +.bd-yellow-800 { color: color-contrast($yellow-800); background-color: $yellow-800; } +.bd-yellow-900 { color: color-contrast($yellow-900); background-color: $yellow-900; } + +.bd-green-100 { color: color-contrast($green-100); background-color: $green-100; } +.bd-green-200 { color: color-contrast($green-200); background-color: $green-200; } +.bd-green-300 { color: color-contrast($green-300); background-color: $green-300; } +.bd-green-400 { color: color-contrast($green-400); background-color: $green-400; } +.bd-green-500 { color: color-contrast($green-500); background-color: $green-500; } +.bd-green-600 { color: color-contrast($green-600); background-color: $green-600; } +.bd-green-700 { color: color-contrast($green-700); background-color: $green-700; } +.bd-green-800 { color: color-contrast($green-800); background-color: $green-800; } +.bd-green-900 { color: color-contrast($green-900); background-color: $green-900; } + +.bd-teal-100 { color: color-contrast($teal-100); background-color: $teal-100; } +.bd-teal-200 { color: color-contrast($teal-200); background-color: $teal-200; } +.bd-teal-300 { color: color-contrast($teal-300); background-color: $teal-300; } +.bd-teal-400 { color: color-contrast($teal-400); background-color: $teal-400; } +.bd-teal-500 { color: color-contrast($teal-500); background-color: $teal-500; } +.bd-teal-600 { color: color-contrast($teal-600); background-color: $teal-600; } +.bd-teal-700 { color: color-contrast($teal-700); background-color: $teal-700; } +.bd-teal-800 { color: color-contrast($teal-800); background-color: $teal-800; } +.bd-teal-900 { color: color-contrast($teal-900); background-color: $teal-900; } + +.bd-cyan-100 { color: color-contrast($cyan-100); background-color: $cyan-100; } +.bd-cyan-200 { color: color-contrast($cyan-200); background-color: $cyan-200; } +.bd-cyan-300 { color: color-contrast($cyan-300); background-color: $cyan-300; } +.bd-cyan-400 { color: color-contrast($cyan-400); background-color: $cyan-400; } +.bd-cyan-500 { color: color-contrast($cyan-500); background-color: $cyan-500; } +.bd-cyan-600 { color: color-contrast($cyan-600); background-color: $cyan-600; } +.bd-cyan-700 { color: color-contrast($cyan-700); background-color: $cyan-700; } +.bd-cyan-800 { color: color-contrast($cyan-800); background-color: $cyan-800; } +.bd-cyan-900 { color: color-contrast($cyan-900); background-color: $cyan-900; } + +.bd-gray-100 { color: color-contrast($gray-100); background-color: $gray-100; } +.bd-gray-200 { color: color-contrast($gray-200); background-color: $gray-200; } +.bd-gray-300 { color: color-contrast($gray-300); background-color: $gray-300; } +.bd-gray-400 { color: color-contrast($gray-400); background-color: $gray-400; } +.bd-gray-500 { color: color-contrast($gray-500); background-color: $gray-500; } +.bd-gray-600 { color: color-contrast($gray-600); background-color: $gray-600; } +.bd-gray-700 { color: color-contrast($gray-700); background-color: $gray-700; } +.bd-gray-800 { color: color-contrast($gray-800); background-color: $gray-800; } +.bd-gray-900 { color: color-contrast($gray-900); background-color: $gray-900; } + +.bd-white { color: color-contrast($white); background-color: $white; border: 2px solid $body-color;} +.bd-black { color: color-contrast($black); background-color: $black; } +.bd-foreground { color: $body-bg; background-color: $body-color; } +.bd-background { color: $body-color; background-color: $body-bg; border: 2px solid $body-color;} \ No newline at end of file diff --git a/examples/brand/app-express.py b/examples/brand/app-express.py new file mode 100644 index 000000000..bbebddc3e --- /dev/null +++ b/examples/brand/app-express.py @@ -0,0 +1,10 @@ +from shiny.express import input, render, ui + +ui.page_opts(theme=ui.Theme.from_brand(__file__)) + +ui.input_slider("n", "N", 0, 100, 20) + + +@render.code +def txt(): + return f"n*2 is {input.n() * 2}" diff --git a/examples/brand/app.py b/examples/brand/app.py new file mode 100644 index 000000000..1cdbb8e9b --- /dev/null +++ b/examples/brand/app.py @@ -0,0 +1,342 @@ +import os +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np + +from shiny import App, render, ui +from shiny.ui._theme_brand import bootstrap_colors + +# TODO: Move this into the test that runs this app +os.environ["SHINY_BRAND_YML_RAISE_UNMAPPED"] = "true" +theme = ui.Theme.from_brand(__file__) +# theme = ui.Theme() +theme.add_rules((Path(__file__).parent / "_colors.scss").read_text()) + +app_ui = ui.page_navbar( + ui.nav_panel( + "Input Output Demo", + ui.layout_sidebar( + ui.sidebar( + ui.input_slider("slider1", "Numeric Slider Input", 0, 11, 11), + ui.input_numeric("numeric1", "Numeric Input Widget", 30), + ui.input_date("date1", "Date Input Component", value="2024-01-01"), + ui.input_switch("switch1", "Binary Switch Input", True), + ui.input_radio_buttons( + "radio1", + "Radio Button Group", + choices=["Option A", "Option B", "Option C", "Option D"], + ), + ui.input_action_button("action1", "Action Button"), + ), + ui.layout_columns( + ui.value_box( + "Metric 1", + "100", + theme="primary", + ), + ui.value_box( + "Metric 2", + "200", + theme="secondary", + ), + ui.value_box( + "Metric 3", + "300", + theme="info", + ), + ), + ui.card( + ui.card_header("Plot Output"), + ui.output_plot("plot1"), + ), + ui.card( + ui.card_header("Text Output"), + ui.output_text_verbatim("out_text1"), + ), + ), + ), + ui.nav_panel( + "Widget Gallery", + ui.layout_column_wrap( + ui.card( + ui.card_header("Button Variants"), + ui.input_action_button("btn1", "Default"), + ui.input_action_button("btn2", "Primary", class_="btn-primary"), + ui.input_action_button("btn3", "Secondary", class_="btn-secondary"), + ui.input_action_button("btn4", "Info", class_="btn-info"), + ui.input_action_button("btn5", "Success", class_="btn-success"), + ui.input_action_button("btn6", "Warning", class_="btn-warning"), + ui.input_action_button("btn7", "Danger", class_="btn-danger"), + ), + ui.card( + ui.card_header("Radio Button Examples"), + ui.input_radio_buttons( + "radio2", + "Standard Radio Group", + ["Selection 1", "Selection 2", "Selection 3"], + ), + ui.input_radio_buttons( + "radio3", + "Inline Radio Group", + ["Option 1", "Option 2", "Option 3"], + inline=True, + ), + ), + ui.card( + ui.card_header("Checkbox Examples"), + ui.input_checkbox_group( + "check1", + "Standard Checkbox Group", + ["Item 1", "Item 2", "Item 3"], + ), + ui.input_checkbox_group( + "check2", + "Inline Checkbox Group", + ["Choice A", "Choice B", "Choice C"], + inline=True, + ), + ), + ui.card( + ui.card_header("Select Input Widgets"), + ui.input_selectize( + "select1", + "Selectize Input", + ["Selection A", "Selection B", "Selection C"], + ), + ui.input_select( + "select2", + "Multiple Select Input", + ["Item X", "Item Y", "Item Z"], + multiple=True, + ), + ), + ui.card( + ui.card_header("Text Input Widgets"), + ui.input_text("text1", "Text Input"), + ui.input_text_area( + "textarea1", + "Text Area Input", + "Default text content for the text area widget", + ), + ui.input_password("password1", "Password Input"), + ), + width=300, + heights_equal=False, + ), + ), + ui.nav_panel( + "Colors", + ui.fill.as_fill_item( + ui.div( + ui.div(ui.output_ui("ui_colors"), class_="container-sm"), + class_="overflow-y-auto", + ) + ), + ), + ui.nav_panel( + "Documentation", + ui.fill.as_fill_item( + ui.div( + ui.div( + ui.markdown( + """ + _Just in case it isn't obvious, this text was written by an LLM._ + + # Component Documentation + + The Shiny for Python framework, available at [shiny.posit.co/py](https://shiny.posit.co/py/), + provides a comprehensive set of UI components for building interactive web applications. These + components are designed with **consistency and usability** in mind, making it easier for + developers to create professional-grade applications. + + Our framework implements the `ui.page_navbar()` component as the primary navigation structure, + allowing for intuitive organization of content across multiple views. Each view can contain + various input and output elements, managed through the `ui.card()` container system. + + ## Component Architecture + + *The architecture of our application framework* emphasizes modularity and reusability. Key + components like `ui.value_box()` and `ui.layout_column_wrap()` work together to create + structured, responsive layouts that adapt to different screen sizes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComponentImplementationUse CaseStatus
Value Boxui.value_box()Metric DisplayProduction Ready
Cardui.card()Content ContainerProduction Ready
Layoutui.layout_column_wrap()Component OrganizationProduction Ready
Navigationui.page_navbar()Page StructureProduction Ready
+ + ## Implementation Best Practices + + When implementing components, maintain consistent patterns in your code. Use the + `@render` decorators for output functions and follow the reactive programming model + with `@reactive.effect` for side effects. + + Error handling should be implemented at both the UI and server levels. For input + validation, use the `req()` function to ensure all required values are present + before processing. + + ## Corporate Brand Guidelines + + Effective corporate brand guidelines should accomplish several key objectives: + + 1. **Visual Consistency**: Establish a clear color palette using our theming system. + Primary colors should be defined using `class_="btn-primary"` and similar Bootstrap + classes. + + 2. *Typography Standards*: Maintain consistent font usage across all text elements. + Headers should use the built-in styling provided by the `ui.card_header()` component. + + 3. `Component Styling`: Apply consistent styling to UI elements such as buttons, + cards, and value boxes. Use the theme parameter in components like + `ui.value_box(theme="primary")`. + + 4. **Layout Principles**: Follow a grid-based layout system using + `ui.layout_column_wrap()` with appropriate width parameters to ensure consistent + spacing and alignment. + + 5. *Responsive Design*: Implement layouts that adapt gracefully to different screen + sizes using the `fillable` parameter in page components. + + Remember that brand guidelines should serve as a framework for consistency while + remaining flexible enough to accommodate future updates and modifications to the + application interface. + """ + ), + class_="container-sm ", + ), + class_="overflow-y-auto", + ) + ), + ), + ui.nav_spacer(), + ui.nav_control(ui.input_dark_mode(id="color_mode")), + title="brand.yml Demo", + fillable=True, + theme=theme, +) + + +def server(input, output, session): + @render.plot + def plot1(): + colors = { + "foreground": theme.brand.color.foreground, + "background": theme.brand.color.background, + "primary": theme.brand.color.primary, + } + + if theme.brand.color: + colors.update(theme.brand.color.to_dict("theme")) + + if input.color_mode() == "dark": + bg = colors["foreground"] + fg = colors["background"] + colors.update({"foreground": fg, "background": bg}) + + x = np.linspace(0, input.numeric1(), 100) + y = np.sin(x) * input.slider1() + fig, ax = plt.subplots(facecolor=colors["background"]) + ax.plot(x, y, color=colors["primary"]) + ax.set_title("Sine Wave Output", color=colors["foreground"]) + ax.set_facecolor(colors["background"]) + ax.tick_params(colors=colors["foreground"]) + for spine in ax.spines.values(): + spine.set_edgecolor(colors["foreground"]) + spine.set_alpha(0.25) + return fig + + @render.text + def out_text1(): + return "\n".join( + ["def example_function():", ' return "Function output text"'] + ) + + @render.ui + def ui_colors(): + colors = [] + # Replicates: https://getbootstrap.com/docs/5.3/customize/color/#all-colors + # Source: https://github.com/twbs/bootstrap/blob/6e1f75f4/site/content/docs/5.3/customize/color.md?plain=1#L395-L409 + for color in ["gray", *bootstrap_colors]: + if color in ["white", "black"]: + continue + + colors += [ + ui.div( + ui.div(color, class_=f"p-3 mb-2 position-relative bd-{color}-500"), + *[ + ui.div(f"{color}-{r}", class_=f"p-3 bd-{color}-{r}") + for r in range(100, 1000, 100) + ], + class_="mb-3", + ) + ] + + return ui.TagList( + ui.div( + *[ + ui.div( + ui.div( + color, class_=f"p-3 mb-2 position-relative text-bg-{color}" + ), + class_="col-md-3 mb-3", + ) + for color in [ + "primary", + "secondary", + "dark", + "light", + "info", + "success", + "warning", + "danger", + ] + ], + class_="row font-monospace", + ), + ui.div( + *[ + ui.div( + ui.div(color, class_=f"p-3 mb-2 position-relative bd-{color}"), + class_="col-md-3 mb-3", + ) + for color in ["black", "white", "foreground", "background"] + ], + class_="row font-monospace", + ), + ui.layout_column_wrap(*colors, class_="font-monospace"), + ) + + +app = App(app_ui, server) diff --git a/examples/brand/requirements.txt b/examples/brand/requirements.txt new file mode 100644 index 000000000..430504d8f --- /dev/null +++ b/examples/brand/requirements.txt @@ -0,0 +1,3 @@ +shiny[theme] +matplotlib +numpy diff --git a/pyproject.toml b/pyproject.toml index 1f94a4d87..29b5d8fab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,10 @@ dependencies = [ ] [project.optional-dependencies] -theme = ["libsass>=0.23.0"] +theme = [ + "libsass>=0.23.0", + "brand_yml>=0.1.0" +] test = [ "pytest>=6.2.4", "pytest-asyncio>=0.17.2", @@ -100,6 +103,7 @@ dev = [ "Flake8-pyproject>=1.2.3", "isort>=5.10.1", "libsass>=0.23.0", + "brand_yml>=0.1.0", "pyright>=1.1.383", "pre-commit>=2.15.0", "wheel", diff --git a/shiny/_main.py b/shiny/_main.py index e004f4312..2f7ac632e 100644 --- a/shiny/_main.py +++ b/shiny/_main.py @@ -41,6 +41,8 @@ def main() -> None: "*.htm", "*.html", "*.png", + "*.yml", + "*.yaml", ) RELOAD_EXCLUDES_DEFAULT = (".*", "*.py[cod]", "__pycache__", "env", "venv") diff --git a/shiny/ui/_html_deps_external.py b/shiny/ui/_html_deps_external.py index 5205e6313..00e40c723 100644 --- a/shiny/ui/_html_deps_external.py +++ b/shiny/ui/_html_deps_external.py @@ -32,7 +32,7 @@ def shiny_page_theme_deps(theme: str | Path | Theme | ThemeProvider | None) -> T if theme is None: deps_theme = None elif isinstance(theme, Theme): - deps_theme = theme._html_dependency() + deps_theme = theme._html_dependencies() elif isinstance(theme, str) and theme.startswith(("http", "//")): deps_theme = head_content(link(rel="stylesheet", href=theme, type="text/css")) elif isinstance(theme, (str, Path)): diff --git a/shiny/ui/_theme.py b/shiny/ui/_theme.py index 27c7e772f..cba938882 100644 --- a/shiny/ui/_theme.py +++ b/shiny/ui/_theme.py @@ -5,8 +5,10 @@ import re import tempfile import textwrap -from typing import Any, Literal, Optional, Sequence, TypeVar +from typing import TYPE_CHECKING, Any, Literal, Optional, Sequence, TypeVar +if TYPE_CHECKING: + from brand_yml import Brand from htmltools import HTMLDependency from .._docstring import add_example @@ -129,12 +131,11 @@ def server(input): def __init__( self, - preset: ShinyThemePreset = "shiny", + preset: str | None = None, name: Optional[str] = None, include_paths: Optional[str | pathlib.Path | list[str | pathlib.Path]] = None, ): - check_is_valid_preset(preset) - self._preset: ShinyThemePreset = preset + self._preset: ShinyThemePreset = check_is_valid_preset(preset or "shiny") self.name = name # 2024-06-21: `version` is not exposed because we currently support only BS 5. # In the future, the Bootstrap version could be chosen by the user on init. @@ -409,7 +410,7 @@ def to_css( self._css = self._read_precompiled_css() return self._css - check_libsass_installed() + check_theme_pkg_installed("libsass", "sass") import sass args: SassCompileArgs = {} if compile_args is None else compile_args @@ -460,25 +461,29 @@ def _dep_create(self, css_path: str | pathlib.Path) -> HTMLDependency: "href": css_path.name, "data-shiny-theme": self.name or self._preset, # type: ignore }, + # Branded themes re-use this tempdir + all_files=False, ) def _html_dependency_precompiled(self) -> HTMLDependency: return self._dep_create(css_path=self._dep_css_precompiled_path()) - def _html_dependency(self) -> HTMLDependency: + def _html_dependencies(self) -> list[HTMLDependency]: """ Create an `HTMLDependency` object from the theme. Returns ------- : - An :class:`~htmltools.HTMLDependency` object representing the theme. In - most cases, you should not need to call this method directly. Instead, pass - the `Theme` object directly to the `theme` argument of any Shiny page - function. + An list of :class:`~htmltools.HTMLDependency` objects representing the + theme. In most cases, you should not need to call this method directly. + Instead, pass the `Theme` object directly to the `theme` argument of any + Shiny page function. """ + # Note: return a list so that this method can be overridden in subclasses of + # Theme that want to attach additional dependencies to the theme dependency. if self._can_use_precompiled(): - return self._html_dependency_precompiled() + return [self._html_dependency_precompiled()] css_name = self._dep_css_name() css_path = os.path.join(self._get_css_tempdir(), css_name) @@ -487,7 +492,7 @@ def _html_dependency(self) -> HTMLDependency: with open(css_path, "w") as css_file: css_file.write(self.to_css()) - return self._dep_create(css_path) + return [self._dep_create(css_path)] def tagify(self) -> None: raise SyntaxError( @@ -497,6 +502,90 @@ def tagify(self) -> None: "or any `shiny.ui.page_*()` function (Shiny Core)." ) + @classmethod + def from_brand(cls, brand: "str | pathlib.Path | Brand"): + """ + Create a custom Shiny theme from a `_brand.yml` + + Creates a custom Shiny theme for your brand using + [brand.yml](https://posit-dev.github.io/brand-yml), a single YAML file that + describes the brand's color and typography. Learn more about writing a + `_brand.yml` file for your brand at the + [brand.yml homepage](https://posit-dev.github.io/brand-yml). + + As a simple example, suppose your brand guidelines include a color palette with + custom orange and black colors. The orange is used as the primary accent color + and the black for all text. For typography, the brand also uses + [Roboto](https://fonts.google.com/specimen/Roboto?query=roboto) and + [Roboto Mono](https://fonts.google.com/specimen/Roboto+Mono?query=roboto) from + Google Fonts for text and monospace-styled text, respectively. Here's a + `_brand.yml` file for this brand: + + ```{.yaml filename="_brand.yml"} + meta: + name: brand.yml Example + + color: + palette: + orange: "#F96302" + black: "#000000" + foreground: black + primary: orange + + typography: + fonts: + - family: Roboto + source: google + - family: Roboto Mono + source: google + base: Roboto + monospace: Roboto Mono + ``` + + You can store the `_brand.yml` file next to your Shiny `app.py` or, for larger + projects, in a parent folder. To use a theme generated from the `_brand.yml` + file, call :meth:`~shiny.ui.Theme.from_brand` on `__file__` and pass the result + to the `theme` argument of :func:`~shiny.express.ui.page_opts` (Shiny Express) + or the `theme` argument of `shiny.ui.page_*` functions, like + :func:`~shiny.ui.page_sidebar`. + + ```{.python filename="app.py"} + from shiny.express import input, render, ui + + ui.page_opts(theme=ui.Theme.from_brand(__file__)) + + ui.input_slider("n", "N", 0, 100, 20) + + + @render.code + def txt(): + return f"n*2 is {input.n() * 2}" + ``` + + Parameters + ---------- + brand + A :class:`brand_yml.Brand` instance, or a path to help locate `_brand.yml`. + For a path, you can pass `__file__` or a directory containing the + `_brand.yml` or a path directly to the `_brand.yml` file. + + Returns + ------- + : + A :class:`shiny.ui.Theme` instance with a custom Shiny theme created from + the brand guidelines (see :class:`brand_yml.Brand`). + """ + check_theme_pkg_installed("brand_yml") + + from brand_yml import Brand + + from ._theme_brand import ThemeBrand # avoid circular import + + if not isinstance(brand, Brand): + brand = Brand.from_yaml(brand) + + return ThemeBrand(brand) + def path_pkg_preset(preset: ShinyThemePreset, *args: str) -> str: """ @@ -516,21 +605,23 @@ def path_pkg_preset(preset: ShinyThemePreset, *args: str) -> str: return pathlib.Path(path).as_posix() -def check_is_valid_preset(preset: ShinyThemePreset) -> None: +def check_is_valid_preset(preset: str) -> ShinyThemePreset: if preset not in shiny_theme_presets: raise ValueError( f"Invalid preset '{preset}'.\n" + f"""Expected one of: "{'", "'.join(shiny_theme_presets)}".""", ) + return preset + -def check_libsass_installed() -> None: +def check_theme_pkg_installed(pkg: str, spec: str | None = None) -> None: import importlib.util - if importlib.util.find_spec("sass") is None: + if importlib.util.find_spec(spec or pkg) is None: raise ImportError( - "The 'libsass' package is required to compile custom themes. " - 'Please install it with `pip install libsass` or `pip install "shiny[theme]"`.', + f"The '{pkg}' package is required to compile custom themes. " + 'Please install it with `pip install {pkg}` or `pip install "shiny[theme]"`.', ) diff --git a/shiny/ui/_theme_brand.py b/shiny/ui/_theme_brand.py new file mode 100644 index 000000000..5713feb34 --- /dev/null +++ b/shiny/ui/_theme_brand.py @@ -0,0 +1,552 @@ +from __future__ import annotations + +import os +import warnings +from pathlib import Path +from typing import TYPE_CHECKING, Any, Optional, Union + +if TYPE_CHECKING: + from brand_yml import Brand +from htmltools import HTMLDependency + +from .._versions import bootstrap as v_bootstrap +from ._theme import Theme + +YamlScalarType = Union[str, int, bool, float, None] + + +class ThemeBrandUnmappedFieldError(ValueError): + def __init__(self, field: str): + self.field = field + self.message = f"Unmapped brand.yml field: {field}" + super().__init__(self.message) + + def __str__(self): + return self.message + + +def warn_or_raise_unmapped_variable(unmapped: str): + if os.environ.get("SHINY_BRAND_YML_RAISE_UNMAPPED") == "true": + raise ThemeBrandUnmappedFieldError(unmapped) + else: + warnings.warn( + f"Shiny's brand.yml theme does not yet support {unmapped}.", + stacklevel=4, + ) + + +color_map: dict[str, list[str]] = { + # Bootstrap uses $gray-900 and $white for the body bg-color by default, and then + # swaps them for $gray-100 and $gray-900 in dark mode. brand.yml may end up with + # light/dark variants for foreground/background, see posit-dev/brand-yml#38. + "foreground": ["brand--foreground", "body-color", "body-bg-dark"], + "background": ["brand--background", "body-bg", "body-color-dark"], + "primary": ["primary"], + "secondary": ["secondary", "body-secondary-color", "body-secondary"], + "tertiary": ["body-tertiary-color", "body-tertiary"], + "success": ["success"], + "info": ["info"], + "warning": ["warning"], + "danger": ["danger"], + "light": ["light"], + "dark": ["dark"], +} +"""Maps brand.color fields to Bootstrap Sass variables""" + +# https://github.com/twbs/bootstrap/blob/6e1f75/scss/_variables.scss#L38-L49 +bootstrap_colors: list[str] = [ + "white", + "black", + "blue", + "indigo", + "purple", + "pink", + "red", + "orange", + "yellow", + "green", + "teal", + "cyan", +] +""" +Colors known to Bootstrap + +When these colors are named in `colors.palette`, we'll map the brand's colors to the +corresponding Bootstrap color Sass variable. + +* [Bootstrap 5 - Colors](https://getbootstrap.com/docs/5.3/customize/color/#color-sass-maps) +""" + +# TODO: test that these Sass variables exist in Bootstrap +typography_map: dict[str, dict[str, list[str]]] = { + "base": { + "family": ["font-family-base"], + "size": ["font-size-base"], # TODO: consider using $font-size-root instead + "line_height": ["line-height-base"], + "weight": ["font-weight-base"], + }, + "headings": { + "family": ["headings-font-family"], + "line_height": ["headings-line-height"], + "weight": ["headings-font-weight"], + "color": ["headings-color"], + "style": ["headings-style"], + }, + "monospace": { + "family": ["font-family-monospace"], + "size": ["code-font-size"], + "weight": ["code-font-weight"], + }, + "monospace_inline": { + "family": ["font-family-monospace-inline"], + "color": ["code-color", "code-color-dark"], + "background_color": ["code-bg"], + "size": ["code-inline-font-size"], + "weight": ["code-inline-font-weight"], + }, + "monospace_block": { + "family": ["font-family-monospace-block"], + "line_height": ["code-block-line-height"], + "color": ["pre-color"], + "background_color": ["pre-bg"], + "weight": ["code-block-font-weight"], + "size": ["code-block-font-size"], + }, + "link": { + "background_color": ["link-bg"], + "color": ["link-color", "link-color-dark"], + "weight": ["link-weight"], + "decoration": ["link-decoration"], + }, +} +"""Maps brand.typography fields to corresponding Bootstrap Sass variables""" + + +class BrandBootstrapConfigFromYaml: + """Validate a Bootstrap config from a YAML source""" + + def __init__( + self, + path: str, + version: Any = None, + preset: Any = None, + functions: Any = None, + defaults: Any = None, + mixins: Any = None, + rules: Any = None, + ): + + # TODO: Remove `path` and handle in try/except block in caller + self._path = path + self.version = version + self.preset: str | None = self._validate_str(preset, "preset") + self.functions: str | None = self._validate_str(functions, "functions") + self.defaults: dict[str, YamlScalarType] | None = self._validate_defaults( + defaults + ) + self.mixins: str | None = self._validate_str(mixins, "mixins") + self.rules: str | None = self._validate_str(rules, "rules") + + def _validate_str(self, x: Any, param: str) -> str | None: + if x is None or isinstance(x, str): + return x + + raise ValueError( + f"Invalid brand `{self._path}.{param}`. Must be a string or empty." + ) + + def _validate_defaults(self, x: Any) -> dict[str, YamlScalarType] | None: + if x is None: + return None + + if not isinstance(x, dict): + raise ValueError( + f"Invalid brand `{self._path}.defaults`, must be a dictionary." + ) + + y: dict[Any, Any] = x + + if not all([isinstance(k, str) for k in y.keys()]): + raise ValueError( + f"Invalid brand `{self._path}.defaults`, all keys must be strings." + ) + + if not all( + [v is None or isinstance(v, (str, int, float, bool)) for v in y.values()] + ): + raise ValueError( + f"Invalid brand `{self._path}.defaults`, all values must be scalar." + ) + + res: dict[str, YamlScalarType] = y + return res + + +class BrandBootstrapConfig: + """Convenience class for storing Bootstrap defaults from a brand instance""" + + def __init__( + self, + version: Any = v_bootstrap, + preset: str | None = None, + functions: str | None = None, + defaults: dict[str, YamlScalarType] | None = None, + mixins: str | None = None, + rules: str | None = None, + ): + if not isinstance(version, (str, int)): + raise ValueError( + f"Bootstrap version must be a string or integer, not {version!r}." + ) + + v_major = str(version).split(".")[0] + bs_major = str(v_bootstrap).split(".")[0] + + if v_major != bs_major: + # TODO (bootstrap-update): Assumes Shiny ships one version of Bootstrap + warnings.warn( + f"Shiny does not current support Bootstrap version {v_major}. " + f"Using Bootstrap v{bs_major} instead.", + stacklevel=4, + ) + v_major = bs_major + + self.version = v_major + self.preset = preset + self.functions = functions + self.defaults = defaults + self.mixins = mixins + self.rules = rules + + @classmethod + def from_brand(cls, brand: "Brand"): + if not brand.defaults: + return cls() + + shiny_args = {} + if "shiny" in brand.defaults and "theme" in brand.defaults["shiny"]: + shiny_args = brand.defaults["shiny"]["theme"] + + shiny = BrandBootstrapConfigFromYaml( + path="defaults.shiny.theme", + **shiny_args, + ) + + bs_args = {} + if "bootstrap" in brand.defaults: + bs_args = brand.defaults["bootstrap"] + + bootstrap = BrandBootstrapConfigFromYaml( + path="defaults.bootstrap", + **bs_args, + ) + + # now combine bootstrap and shiny config options in a way that makes sense + def join_str(x: str | None, y: str | None): + return "\n".join([z for z in [x, y] if z is not None]) + + defaults: dict[str, YamlScalarType] = {} + defaults.update(bootstrap.defaults or {}) + defaults.update(shiny.defaults or {}) + + return cls( + version=shiny.version or bootstrap.version or v_bootstrap, + preset=shiny.preset or bootstrap.preset, + functions=join_str(bootstrap.functions, shiny.functions), + defaults=defaults, + mixins=join_str(bootstrap.mixins, shiny.mixins), + rules=join_str(bootstrap.rules, shiny.rules), + ) + + +class ThemeBrand(Theme): + def __init__( + self, + brand: "Brand", + *, + include_paths: Optional[str | Path | list[str | Path]] = None, + ): + + name = self._get_theme_name(brand) + brand_bootstrap = BrandBootstrapConfig.from_brand(brand) + + # Initialize theme ------------------------------------------------------------ + super().__init__( + name=name, + preset=brand_bootstrap.preset, + include_paths=include_paths, + ) + + self.brand = brand + + # Prep Sass and CSS Variables ------------------------------------------------- + sass_vars_theme_colors, sass_vars_brand_colors, css_vars_brand = ( + ThemeBrand._prepare_color_vars(brand) + ) + sass_vars_typography = ThemeBrand._prepare_typography_vars(brand) + + # Theme ----------------------------------------------------------------------- + # Defaults are added in reverse order, so each chunk appears above the next + # layer of defaults. The intended order in the final output is: + # 1. "Brand" Color palette + # 2. "Brand" Bootstrap Sass vars + # 3. "Brand" theme colors + # 4. "Brand" typography + # 5. Gray scale variables from "Brand" fg/bg or black/white + # 6. Fallback vars needed by additional "Brand" rules + + self.add_defaults("", "// *---- brand: end of defaults ----* //", "") + self._add_sass_ensure_variables() + self._add_sass_brand_grays() + self._add_defaults_hdr("typography", **sass_vars_typography) + self._add_defaults_hdr("theme colors", **sass_vars_theme_colors) + if brand_bootstrap.defaults: + self._add_defaults_hdr("bootstrap defaults", **brand_bootstrap.defaults) + self._add_defaults_hdr("brand colors", **sass_vars_brand_colors) + + # "Brand" rules (now in forwards order) + self._add_rules_brand_colors(css_vars_brand) + self._add_sass_brand_rules() + self._add_brand_bootstrap_other(brand_bootstrap) + + def _get_theme_name(self, brand: "Brand") -> str: + if not brand.meta or not brand.meta.name: + return "brand" + + return brand.meta.name.short or brand.meta.name.full or "brand" + + @staticmethod + def _prepare_color_vars( + brand: "Brand", + ) -> tuple[dict[str, str], dict[str, str], list[str]]: + """Colors: create a dictionary of Sass variables and a list of brand CSS variables""" + if not brand.color: + return {}, {}, [] + + mapped: dict[str, str] = {} + brand_sass_vars: dict[str, str] = {} + brand_css_vars: list[str] = [] + + # Map values in colors to their Sass variable counterparts + for thm_name, thm_color in brand.color.to_dict(include="theme").items(): + if thm_name not in color_map: + warn_or_raise_unmapped_variable(f"color.{thm_name}") + continue + + for sass_var in color_map[thm_name]: + mapped[sass_var] = thm_color + + brand_color_palette = brand.color.to_dict(include="palette") + + # Map the brand color palette to Bootstrap's named colors, e.g. $red, $blue. + for pal_name, pal_color in brand_color_palette.items(): + if pal_name in bootstrap_colors: + mapped[pal_name] = pal_color + + # Create Sass and CSS variables for the brand color palette + # => Sass var: `$brand-{name}: {value}` + brand_sass_vars.update({f"brand-{pal_name}": pal_color}) + # => CSS var: `--brand-{name}: {value}` + brand_css_vars.append(f"--brand-{pal_name}: {pal_color};") + + # We keep Sass and "Brand" vars separate so we can ensure "Brand" Sass vars come + # first in the compiled Sass definitions. + return mapped, brand_sass_vars, brand_css_vars + + @staticmethod + def _prepare_typography_vars(brand: "Brand") -> dict[str, str]: + """Typography: Create a list of Bootstrap Sass variables""" + mapped: dict[str, str] = {} + + if not brand.typography: + return mapped + + brand_typography = brand.typography.model_dump( + exclude={"fonts"}, + exclude_none=True, + context={"typography_base_size_unit": "rem"}, + ) + + for field, prop in brand_typography.items(): + if field not in typography_map: + warn_or_raise_unmapped_variable(f"typography.{field}") + continue + + for prop_key, prop_value in prop.items(): + if prop_key in typography_map[field]: + typo_sass_vars = typography_map[field][prop_key] + for typo_sass_var in typo_sass_vars: + mapped[typo_sass_var] = prop_value + else: + warn_or_raise_unmapped_variable(f"typography.{field}.{prop_key}") + + return mapped + + def _add_defaults_hdr(self, header: str, **kwargs: YamlScalarType): + self.add_defaults(**kwargs) + self.add_defaults(f"\n// *---- brand: {header} ----* //") + + def _add_sass_ensure_variables(self): + """Ensure the variables we create to augment Bootstrap's variables exist""" + self._add_defaults_hdr( + "added variables", + **{ + "code-font-weight": None, + "code-inline-font-weight": None, + "code-inline-font-size": None, + "code-block-font-weight": None, + "code-block-font-size": None, + "code-block-line-height": None, + "link-bg": None, + "link-weight": None, + }, + ) + + def _add_sass_brand_grays(self): + """ + Adds functions and defaults to handle creating a gray scale palette from the + brand color palette, or the brand's foreground/background colors. + """ + self.add_functions( + """ + @function brand-choose-white-black($foreground, $background) { + $lum_fg: luminance($foreground); + $lum_bg: luminance($background); + $contrast: contrast-ratio($foreground, $background); + + @if $contrast < 4.5 { + @warn "The contrast ratio of #{$contrast} between the brand's foreground color (#{inspect($foreground)}) and background color (#{inspect($background)}) is very low. Consider picking colors with higher contrast for better readability."; + } + + $white: if($lum_fg > $lum_bg, $foreground, $background); + $black: if($lum_fg <= $lum_bg, $foreground, $background); + + // If the brand foreground/background are close enough to black/white, we + // use those values. Otherwise, we'll mix the white/black from the brand + // fg/bg with actual white and black to get something much closer. + @return ( + "white": if(contrast-ratio($white, white) <= 1.15, $white, mix($white, white, 20%)), + "black": if(contrast-ratio($black, black) <= 1.15, $black, mix($black, black, 20%)), + ); + } + """ + ) + self.add_defaults( + """ + // *---- brand: automatic gray gradient ----* // + $enable-brand-grays: true !default; + // Ensure these variables exist so that we can set them inside of @if context + // They can still be overwritten by the user, even with !default; + $white: null !default; + $black: null !default; + $gray-100: null !default; + $gray-200: null !default; + $gray-300: null !default; + $gray-400: null !default; + $gray-500: null !default; + $gray-600: null !default; + $gray-700: null !default; + $gray-800: null !default; + $gray-900: null !default; + + @if $enable-brand-grays { + @if variable-exists(brand--foreground) and variable-exists(brand--background) { + $brand-white-black: brand-choose-white-black($brand--foreground, $brand--background); + @if $white == null { + $white: map-get($brand-white-black, "white") !default; + } + @if $black == null { + $black: map-get($brand-white-black, "black") !default; + } + } + @if $white != null and $black != null { + $gray-100: mix($white, $black, 90%) !default; + $gray-200: mix($white, $black, 80%) !default; + $gray-300: mix($white, $black, 70%) !default; + $gray-400: mix($white, $black, 60%) !default; + $gray-500: mix($white, $black, 50%) !default; + $gray-600: mix($white, $black, 40%) !default; + $gray-700: mix($white, $black, 30%) !default; + $gray-800: mix($white, $black, 20%) !default; + $gray-900: mix($white, $black, 10%) !default; + } + } + """ + ) + + def _add_sass_brand_rules(self): + """Additional rules to fill in Bootstrap styles for "Brand" parameters""" + self.add_rules( + """ + // *---- brand: brand rules to augment Bootstrap rules ----* // + // https://github.com/twbs/bootstrap/blob/5c2f2e7e/scss/_root.scss#L82 + :root { + --#{$prefix}link-bg: #{$link-bg}; + --#{$prefix}link-weight: #{$link-weight}; + } + // https://github.com/twbs/bootstrap/blob/5c2f2e7e/scss/_reboot.scss#L244 + a { + background-color: var(--#{$prefix}link-bg); + font-weight: var(--#{$prefix}link-weight); + } + code { + font-weight: $code-font-weight; + } + code { + font-weight: $code-inline-font-weight; + font-size: $code-inline-font-size; + } + // https://github.com/twbs/bootstrap/blob/30e01525/scss/_reboot.scss#L287 + pre { + font-weight: $code-block-font-weight; + font-size: $code-block-font-size; + line-height: $code-block-line-height; + } + + $bslib-dashboard-design: false !default; + @if $bslib-dashboard-design and variable-exists(brand--background) { + // When brand makes dark mode, it usually hides card definition, so we add + // back card borders in dark mode. + [data-bs-theme="dark"] { + --bslib-card-border-color: RGBA(255, 255, 255, 0.15); + } + } + """ + ) + + def _add_rules_brand_colors(self, css_vars_colors: list[str]): + self.add_rules("\n// *---- brand.color.palette ----* //") + self.add_rules(":root {", *css_vars_colors, "}") + + def _add_brand_bootstrap_other(self, bootstrap: BrandBootstrapConfig): + if bootstrap.functions: + self.add_functions(bootstrap.functions) + if bootstrap.mixins: + self.add_mixins(bootstrap.mixins) + if bootstrap.rules: + self.add_rules(bootstrap.rules) + + def _html_dependencies(self) -> list[HTMLDependency]: + theme_deps = super()._html_dependencies() + + if not self.brand.typography: + return theme_deps + + # We're going to put the fonts dependency _inside_ the theme's tempdir, which + # relies on the theme's dependency having `all_files=True`. We do this because + # Theme handles the tempdir lifecycle and we want the fonts dependency to be + # handled in the same way. + temp_dir = self._get_css_tempdir() + temp_path = Path(temp_dir) / "fonts" + temp_path.mkdir(parents=True, exist_ok=True) + + fonts_dep = self.brand.typography.fonts_html_dependency( + path_dir=temp_path, + name=f"{self._dep_name()}-fonts", + version=self._version, + ) + + if fonts_dep is None: + return theme_deps + + return [fonts_dep, *theme_deps] diff --git a/tests/pytest/test_theme.py b/tests/pytest/test_theme.py index 1bfc42b87..4a9291a2c 100644 --- a/tests/pytest/test_theme.py +++ b/tests/pytest/test_theme.py @@ -214,7 +214,7 @@ def test_theme_dep_name_is_valid_path_part(): def test_theme_dependency_has_data_attribute(): theme = Theme("shiny") - assert theme._html_dependency().stylesheet[0]["data-shiny-theme"] == "shiny" # type: ignore + assert theme._html_dependencies()[0].stylesheet[0]["data-shiny-theme"] == "shiny" # type: ignore theme = Theme("shiny", name="My Fancy Theme") - assert theme._html_dependency().stylesheet[0]["data-shiny-theme"] == "My Fancy Theme" # type: ignore + assert theme._html_dependencies()[0].stylesheet[0]["data-shiny-theme"] == "My Fancy Theme" # type: ignore