mirror of
https://github.com/rio-labs/rio.git
synced 2026-04-27 14:42:24 -05:00
TextStyles no longer have default values, omitted values are left unchanged instead
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ComponentBase, ComponentState, DeltaState } from "./componentBase";
|
||||
import { textStyleToCss } from "../cssUtils";
|
||||
import { applyTextStyleCss, textStyleToCss } from "../cssUtils";
|
||||
|
||||
export type HeadingListItemState = ComponentState & {
|
||||
_type_: "HeadingListItem-builtin";
|
||||
@@ -15,7 +15,7 @@ export class HeadingListItemComponent extends ComponentBase<HeadingListItemState
|
||||
// Apply a style. This could be done with CSS, instead of doing it
|
||||
// individually for each component, but these are rare and this preempts
|
||||
// duplicate code.
|
||||
Object.assign(element.style, textStyleToCss("heading3"));
|
||||
applyTextStyleCss(element, textStyleToCss("heading3"));
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -9,22 +9,22 @@ import {
|
||||
import { ComponentBase, ComponentState, DeltaState } from "./componentBase";
|
||||
import { applyIcon, applyFillToSVG } from "../designApplication";
|
||||
|
||||
type IconCompatibleFill =
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| RadialGradientFill
|
||||
| ImageFill
|
||||
| Color
|
||||
| ColorSet
|
||||
| "dim";
|
||||
|
||||
export type IconState = ComponentState & {
|
||||
_type_: "Icon-builtin";
|
||||
icon: string;
|
||||
fill:
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| RadialGradientFill
|
||||
| ImageFill
|
||||
| Color
|
||||
| ColorSet
|
||||
| "dim";
|
||||
fill: IconCompatibleFill;
|
||||
};
|
||||
|
||||
export class IconComponent extends ComponentBase<IconState> {
|
||||
private svgElement: SVGSVGElement;
|
||||
|
||||
createElement(): HTMLElement {
|
||||
let element = document.createElement("div");
|
||||
element.classList.add("rio-icon");
|
||||
@@ -44,12 +44,25 @@ export class IconComponent extends ComponentBase<IconState> {
|
||||
// fill.
|
||||
let fill = deltaState.fill ?? this.state.fill;
|
||||
|
||||
applyIcon(this.element, deltaState.icon, fill);
|
||||
applyIcon(this.element, deltaState.icon, fill).then(() => {
|
||||
// The fill may have changed while the icon was loading, so
|
||||
// we'll re-apply it
|
||||
this.applyFillIfSvgElementExists(this.state.fill);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (deltaState.fill !== undefined) {
|
||||
applyFillToSVG(this.svgElement, deltaState.fill);
|
||||
this.applyFillIfSvgElementExists(deltaState.fill);
|
||||
}
|
||||
}
|
||||
|
||||
private applyFillIfSvgElementExists(fill: IconCompatibleFill): void {
|
||||
let svgRoot = this.element.querySelector("svg");
|
||||
if (svgRoot === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFillToSVG(svgRoot, fill);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { textStyleToCss } from "../cssUtils";
|
||||
import { applyTextStyleCss, textStyleToCss } from "../cssUtils";
|
||||
import { applyIcon } from "../designApplication";
|
||||
import { ComponentId, TextStyle } from "../dataModels";
|
||||
import { commitCss } from "../utils";
|
||||
import { ComponentBase, ComponentState, DeltaState } from "./componentBase";
|
||||
import { RippleEffect } from "../rippleEffect";
|
||||
import {
|
||||
@@ -120,8 +119,8 @@ export class RevealerComponent extends ComponentBase<RevealerState> {
|
||||
// Update the text style
|
||||
if (deltaState.header_style !== undefined) {
|
||||
// The text is handled by a helper function
|
||||
Object.assign(
|
||||
this.labelElement.style,
|
||||
applyTextStyleCss(
|
||||
this.labelElement,
|
||||
textStyleToCss(deltaState.header_style)
|
||||
);
|
||||
|
||||
@@ -136,7 +135,7 @@ export class RevealerComponent extends ComponentBase<RevealerState> {
|
||||
} else if (deltaState.header_style === "text") {
|
||||
headerScale = 1;
|
||||
} else {
|
||||
headerScale = deltaState.header_style.fontSize;
|
||||
headerScale = deltaState.header_style.fontSize ?? 1;
|
||||
}
|
||||
|
||||
// Adapt the header's padding
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ImageFill,
|
||||
TextStyle,
|
||||
} from "../dataModels";
|
||||
import { textfillToCss, textStyleToCss } from "../cssUtils";
|
||||
import { applyTextStyleCss, textfillToCss, textStyleToCss } from "../cssUtils";
|
||||
import { ComponentBase, ComponentState, DeltaState } from "./componentBase";
|
||||
|
||||
export type TextState = ComponentState & {
|
||||
@@ -17,13 +17,7 @@ export type TextState = ComponentState & {
|
||||
overflow: "nowrap" | "wrap" | "ellipsize";
|
||||
|
||||
font: string | null;
|
||||
fill:
|
||||
| Color
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| ImageFill
|
||||
| null
|
||||
| "not given";
|
||||
fill: Color | SolidFill | LinearGradientFill | ImageFill;
|
||||
font_size: number | null;
|
||||
italic: boolean | null;
|
||||
font_weight: "normal" | "bold" | null;
|
||||
@@ -102,10 +96,7 @@ export class TextComponent extends ComponentBase<TextState> {
|
||||
textStyleCss["font-family"] = deltaState.font;
|
||||
}
|
||||
|
||||
if (
|
||||
deltaState.fill !== undefined &&
|
||||
deltaState.fill !== "not given"
|
||||
) {
|
||||
if (deltaState.fill !== undefined) {
|
||||
Object.assign(textStyleCss, textfillToCss(deltaState.fill));
|
||||
}
|
||||
|
||||
@@ -141,7 +132,7 @@ export class TextComponent extends ComponentBase<TextState> {
|
||||
: "none";
|
||||
}
|
||||
|
||||
Object.assign(this.inner.style, textStyleCss);
|
||||
applyTextStyleCss(this.inner, textStyleCss);
|
||||
}
|
||||
|
||||
// Text content
|
||||
|
||||
+102
-81
@@ -116,12 +116,14 @@ export function fillToCss(fill: AnyFill): {
|
||||
};
|
||||
}
|
||||
|
||||
export function textfillToCss(fill: TextCompatibleFill): {
|
||||
type TextFillCss = {
|
||||
color: string;
|
||||
background: string;
|
||||
backgroundClip: string;
|
||||
textFillColor: string;
|
||||
} {
|
||||
"-webkit-background-clip": string;
|
||||
"-webkit-text-fill-color": string;
|
||||
};
|
||||
|
||||
export function textfillToCss(fill: TextCompatibleFill): TextFillCss {
|
||||
// If no fill is provided, stick to the local text color. This allows
|
||||
// the user to have their text automatically adapt to different
|
||||
// themes/contexts.
|
||||
@@ -129,8 +131,8 @@ export function textfillToCss(fill: TextCompatibleFill): {
|
||||
return {
|
||||
color: "var(--rio-local-text-color)",
|
||||
background: "var(--rio-local-text-background)",
|
||||
backgroundClip: "var(--rio-local-text-background-clip)",
|
||||
textFillColor: "var(--rio-local-text-fill-color)",
|
||||
"-webkit-background-clip": "var(--rio-local-text-background-clip)",
|
||||
"-webkit-text-fill-color": "var(--rio-local-text-fill-color)",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,8 +141,8 @@ export function textfillToCss(fill: TextCompatibleFill): {
|
||||
return {
|
||||
color: colorToCssString(fill),
|
||||
background: "none",
|
||||
backgroundClip: "unset",
|
||||
textFillColor: "unset",
|
||||
"-webkit-background-clip": "unset",
|
||||
"-webkit-text-fill-color": "unset",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,8 +151,8 @@ export function textfillToCss(fill: TextCompatibleFill): {
|
||||
return {
|
||||
color: colorToCssString(fill.color),
|
||||
background: "none",
|
||||
backgroundClip: "unset",
|
||||
textFillColor: "unset",
|
||||
"-webkit-background-clip": "unset",
|
||||
"-webkit-text-fill-color": "unset",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,44 +164,30 @@ export function textfillToCss(fill: TextCompatibleFill): {
|
||||
// doesn't do what we want. (It isn't clipped to the text, it blurs
|
||||
// everything behind the element.) This means FrostedGlassFill
|
||||
// doesn't blur the background when used on text.
|
||||
backgroundClip: "text",
|
||||
textFillColor: "transparent",
|
||||
"-webkit-background-clip": "text",
|
||||
"-webkit-text-fill-color": "transparent",
|
||||
};
|
||||
}
|
||||
|
||||
type TextStyleCss = {
|
||||
opacity?: string;
|
||||
"font-family"?: string;
|
||||
"font-size"?: string;
|
||||
"font-weight"?: string;
|
||||
"font-style"?: string;
|
||||
"text-decoration"?: string;
|
||||
"text-transform"?: string;
|
||||
} & Partial<TextFillCss>;
|
||||
|
||||
export function textStyleToCss(
|
||||
style: "heading1" | "heading2" | "heading3" | "text" | "dim" | TextStyle
|
||||
): {
|
||||
"font-family": string;
|
||||
"font-size": string;
|
||||
"font-weight": string;
|
||||
"font-style": string;
|
||||
"text-decoration": string;
|
||||
"text-transform": string;
|
||||
color: string;
|
||||
background: string;
|
||||
"-webkit-background-clip": string;
|
||||
"-webkit-text-fill-color": string;
|
||||
opacity: string;
|
||||
} {
|
||||
let fontFamily: string;
|
||||
let fontSize: string;
|
||||
let fontWeight: string;
|
||||
let fontStyle: string;
|
||||
let textDecorations: string[] = [];
|
||||
let textTransform: string;
|
||||
let color: string;
|
||||
let background: string;
|
||||
let backgroundClip: string;
|
||||
let textFillColor: string;
|
||||
let opacity: string;
|
||||
): TextStyleCss {
|
||||
let result: TextStyleCss = {};
|
||||
|
||||
// `Dim` is the same as `text`, just with some opacity
|
||||
if (style === "dim") {
|
||||
style = "text";
|
||||
opacity = "0.4";
|
||||
} else {
|
||||
opacity = "1";
|
||||
result.opacity = "0.4";
|
||||
}
|
||||
|
||||
// Predefined style from theme
|
||||
@@ -208,63 +196,96 @@ export function textStyleToCss(
|
||||
let localPrefix = `var(--rio-local-${style}-`;
|
||||
|
||||
// Text fill
|
||||
color = localPrefix + "color)";
|
||||
background = localPrefix + "background)";
|
||||
backgroundClip = localPrefix + "background-clip)";
|
||||
textFillColor = localPrefix + "fill-color)";
|
||||
result.color = localPrefix + "color)";
|
||||
result.background = localPrefix + "background)";
|
||||
result["-webkit-background-clip"] = localPrefix + "background-clip)";
|
||||
result["-webkit-text-fill-color"] = localPrefix + "fill-color)";
|
||||
|
||||
// Font weight. This is local so that buttons can make their label text
|
||||
// bold.
|
||||
fontWeight = localPrefix + "font-weight)";
|
||||
result["font-weight"] = localPrefix + "font-weight)";
|
||||
|
||||
// Others
|
||||
fontFamily = globalPrefix + "font-name)";
|
||||
fontSize = globalPrefix + "font-size)";
|
||||
fontStyle = globalPrefix + "font-style)";
|
||||
textDecorations.push(globalPrefix + "text-decoration)");
|
||||
textTransform = globalPrefix + "all-caps)";
|
||||
result["font-family"] = globalPrefix + "font-name)";
|
||||
result["font-size"] = globalPrefix + "font-size)";
|
||||
result["font-style"] = globalPrefix + "font-style)";
|
||||
result["text-decoration"] = globalPrefix + "text-decoration)";
|
||||
result["text-transform"] = globalPrefix + "all-caps)";
|
||||
}
|
||||
|
||||
// Explicitly defined style
|
||||
else {
|
||||
fontSize = style.fontSize + "em";
|
||||
fontStyle = style.italic ? "italic" : "normal";
|
||||
fontWeight = style.fontWeight;
|
||||
|
||||
if (style.underlined) {
|
||||
textDecorations.push("underline");
|
||||
if (style.fontName !== null) {
|
||||
result["font-family"] = style.fontName;
|
||||
}
|
||||
|
||||
if (style.strikethrough) {
|
||||
textDecorations.push("line-through");
|
||||
if (style.fontSize !== null) {
|
||||
result["font-size"] = style.fontSize + "rem";
|
||||
}
|
||||
|
||||
textTransform = style.allCaps ? "uppercase" : "none";
|
||||
|
||||
// If no font family is provided, stick to the theme's.
|
||||
if (style.fontName === null) {
|
||||
fontFamily = "inherit";
|
||||
} else {
|
||||
fontFamily = style.fontName;
|
||||
if (style.fontWeight !== null) {
|
||||
result["font-weight"] = style.fontWeight;
|
||||
}
|
||||
|
||||
({ color, background, backgroundClip, textFillColor } = textfillToCss(
|
||||
style.fill
|
||||
));
|
||||
if (style.italic !== null) {
|
||||
result["font-style"] = style.italic ? "italic" : "normal";
|
||||
}
|
||||
|
||||
// TODO: `underlined` and `strikethrough` both map to the
|
||||
// `text-decoration` CSS attribute. Which means that if only one of them
|
||||
// is defined, the other one will be disabled instead of inherited. I
|
||||
// don't think we can do anything about that, though.
|
||||
if (style.underlined !== null || style.strikethrough !== null) {
|
||||
let textDecorations: string[] = [];
|
||||
|
||||
if (style.underlined) {
|
||||
textDecorations.push("underline");
|
||||
}
|
||||
|
||||
if (style.strikethrough) {
|
||||
textDecorations.push("line-through");
|
||||
}
|
||||
|
||||
result["text-decoration"] = textDecorations.join(" ");
|
||||
}
|
||||
|
||||
if (style.allCaps !== null) {
|
||||
result["text-transform"] = style.allCaps ? "uppercase" : "none";
|
||||
}
|
||||
|
||||
if (style.fill !== null) {
|
||||
Object.assign(result, textfillToCss(style.fill));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"font-family": fontFamily,
|
||||
"font-size": fontSize,
|
||||
"font-weight": fontWeight,
|
||||
"font-style": fontStyle,
|
||||
"text-decoration":
|
||||
textDecorations.length > 0 ? textDecorations.join(" ") : "none",
|
||||
"text-transform": textTransform,
|
||||
color: color,
|
||||
background: background,
|
||||
"-webkit-background-clip": backgroundClip,
|
||||
"-webkit-text-fill-color": textFillColor,
|
||||
opacity: opacity,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export function applyTextStyleCss(
|
||||
element: HTMLElement,
|
||||
cssProps: TextStyleCss
|
||||
): void {
|
||||
// Since TextStyles don't have to include all of the styling parameters, we
|
||||
// have to clear the old style before applying the new one to ensure that no
|
||||
// undesired settings remain.
|
||||
removeTextStyle(element);
|
||||
Object.assign(element, cssProps);
|
||||
}
|
||||
|
||||
export function removeTextStyle(element: HTMLElement): void {
|
||||
for (let attribute of [
|
||||
"opacity",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"font-weight",
|
||||
"font-style",
|
||||
"text-decoration",
|
||||
"text-transform",
|
||||
"color",
|
||||
"background",
|
||||
"-webkit-background-clip",
|
||||
"-webkit-text-fill-color",
|
||||
]) {
|
||||
element.style.removeProperty(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,17 +66,16 @@ export type TextCompatibleFill =
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| RadialGradientFill
|
||||
| ImageFill
|
||||
| null;
|
||||
| ImageFill;
|
||||
export type TextStyle = {
|
||||
fontName: string | null;
|
||||
fill: TextCompatibleFill;
|
||||
fontSize: number;
|
||||
italic: boolean;
|
||||
fontWeight: "normal" | "bold";
|
||||
underlined: boolean;
|
||||
strikethrough: boolean;
|
||||
allCaps: boolean;
|
||||
fill: TextCompatibleFill | null;
|
||||
fontSize: number | null;
|
||||
italic: boolean | null;
|
||||
fontWeight: "normal" | "bold" | null;
|
||||
underlined: boolean | null;
|
||||
strikethrough: boolean | null;
|
||||
allCaps: boolean | null;
|
||||
};
|
||||
|
||||
export type Theme = {
|
||||
|
||||
@@ -55,7 +55,9 @@ class Link(FundamentalComponent):
|
||||
|
||||
`accessibility_relationship`: Describes the linked page's relationship to
|
||||
the current page. For example, a link to the next page of search results
|
||||
should use `accessibility_relationship="next"`.
|
||||
should use `accessibility_relationship="next"`. [MDN describes the
|
||||
options in more
|
||||
detail.](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel)
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -7,7 +7,7 @@ from uniserde import JsonDoc
|
||||
|
||||
import rio
|
||||
|
||||
from .. import deprecations, text_style, utils
|
||||
from .. import deprecations, text_style
|
||||
from .fundamental_component import FundamentalComponent
|
||||
|
||||
__all__ = [
|
||||
@@ -110,7 +110,7 @@ class Text(FundamentalComponent):
|
||||
overflow: t.Literal["nowrap", "wrap", "ellipsize"] = "nowrap"
|
||||
|
||||
font: rio.Font | None = None
|
||||
fill: text_style._TextFill | None | utils.NotGiven = utils.NOT_GIVEN
|
||||
fill: text_style._TextFill | None = None
|
||||
font_size: float | None = None
|
||||
italic: bool | None = None
|
||||
font_weight: t.Literal["normal", "bold"] | None = None
|
||||
@@ -143,16 +143,11 @@ class Text(FundamentalComponent):
|
||||
else:
|
||||
overflow = self.overflow
|
||||
|
||||
if isinstance(self.fill, utils.NotGiven):
|
||||
fill = "not given"
|
||||
else:
|
||||
fill = self.session._serialize_fill(self.fill)
|
||||
|
||||
# Build the result
|
||||
return {
|
||||
"style": style,
|
||||
"overflow": overflow,
|
||||
"fill": fill,
|
||||
"fill": self.session._serialize_fill(self.fill),
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
||||
+31
-14
@@ -59,6 +59,27 @@ def _prepare_docs():
|
||||
url_docs.owner = RIO_MODULE_DOCS
|
||||
RIO_MODULE_DOCS.members["URL"] = url_docs
|
||||
|
||||
# There's some trickery in the `Theme` for static typing purposes, which
|
||||
# involves some classes that we don't really want users to know about. Fix
|
||||
# up the affected docs.
|
||||
theme_docs = t.cast(
|
||||
imy.docstrings.ClassDocs, RIO_MODULE_DOCS.members["Theme"]
|
||||
)
|
||||
for attr in (
|
||||
"heading1_style",
|
||||
"heading2_style",
|
||||
"heading3_style",
|
||||
"text_style",
|
||||
):
|
||||
# Ideally we would delete the PropertyDocs and replace them with
|
||||
# AttributeDocs, but I'd rather not have to instantiate one of imy's
|
||||
# classes here. They have a lot of parameters, and are quite likely to
|
||||
# change in the future.
|
||||
property_docs = t.cast(
|
||||
imy.docstrings.PropertyDocs, theme_docs.members[attr]
|
||||
)
|
||||
property_docs.getter.return_type = rio.TextStyle
|
||||
|
||||
# Apply rio-specific post-processing
|
||||
postprocess_docs(RIO_MODULE_DOCS)
|
||||
|
||||
@@ -149,14 +170,12 @@ def get_rio_module_docs() -> imy.docstrings.ModuleDocs:
|
||||
|
||||
|
||||
@functools.cache
|
||||
def get_all_documented_objects() -> (
|
||||
dict[
|
||||
type | t.Callable | property,
|
||||
imy.docstrings.ClassDocs
|
||||
| imy.docstrings.FunctionDocs
|
||||
| imy.docstrings.PropertyDocs,
|
||||
]
|
||||
):
|
||||
def get_all_documented_objects() -> dict[
|
||||
type | t.Callable | property,
|
||||
imy.docstrings.ClassDocs
|
||||
| imy.docstrings.FunctionDocs
|
||||
| imy.docstrings.PropertyDocs,
|
||||
]:
|
||||
all_docs = get_rio_module_docs().iter_children(
|
||||
include_self=True, recursive=True
|
||||
)
|
||||
@@ -175,12 +194,10 @@ def get_all_documented_objects() -> (
|
||||
|
||||
|
||||
@functools.cache
|
||||
def get_toplevel_documented_objects() -> (
|
||||
dict[
|
||||
type | t.Callable,
|
||||
imy.docstrings.ClassDocs | imy.docstrings.FunctionDocs,
|
||||
]
|
||||
):
|
||||
def get_toplevel_documented_objects() -> dict[
|
||||
type | t.Callable,
|
||||
imy.docstrings.ClassDocs | imy.docstrings.FunctionDocs,
|
||||
]:
|
||||
"""
|
||||
Returns only objects that have their own page in our docs. (That means no
|
||||
methods.)
|
||||
|
||||
+15
-5
@@ -2812,7 +2812,7 @@ a.remove();
|
||||
)
|
||||
|
||||
for style_name in style_names:
|
||||
style = getattr(thm, f"{style_name}_style")
|
||||
style: theme.TextStyle = getattr(thm, f"{style_name}_style")
|
||||
assert isinstance(style, rio.TextStyle), style
|
||||
|
||||
css_prefix = f"--rio-global-{style_name}"
|
||||
@@ -3376,13 +3376,23 @@ a.remove();
|
||||
if icon is not None:
|
||||
icon_size = self.theme.heading2_style.font_size * 1.1
|
||||
|
||||
# Icons don't support the same fills as headings. Pick out a
|
||||
# valid option.
|
||||
for fill in (
|
||||
self.theme.heading2_style.fill,
|
||||
self.theme.heading3_style.fill,
|
||||
self.theme.heading1_style.fill,
|
||||
self.theme.text_style.fill,
|
||||
):
|
||||
if fill is not None:
|
||||
break
|
||||
else:
|
||||
fill = rio.Color.BLACK
|
||||
|
||||
title_components.append(
|
||||
rio.Icon(
|
||||
icon,
|
||||
# FIXME: This is technically wrong, since the heading
|
||||
# style could be filled with something other than a
|
||||
# valid icon color. What to do?
|
||||
fill=self.theme.heading2_style.fill, # type: ignore
|
||||
fill=fill,
|
||||
min_width=icon_size,
|
||||
min_height=icon_size,
|
||||
)
|
||||
|
||||
+2
-2
@@ -62,7 +62,7 @@ class CardSection(rio.Component):
|
||||
# Add Section Title
|
||||
rio.Text(
|
||||
"Why choose our service?",
|
||||
style=theme.BOLD_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_SECTION_TITLE_DESKTOP,
|
||||
justify="center",
|
||||
),
|
||||
# Add Section Description
|
||||
@@ -114,7 +114,7 @@ class CardSection(rio.Component):
|
||||
# Add section title
|
||||
rio.Text(
|
||||
"Why choose our service?",
|
||||
style=theme.BOLD_SECTION_TITEL_MOBILE,
|
||||
style=theme.BOLD_SECTION_TITLE_MOBILE,
|
||||
),
|
||||
# Add section description
|
||||
rio.Text(
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ class FaqSection(rio.Component):
|
||||
rio.Text(
|
||||
"Frequently asked questions",
|
||||
justify="center",
|
||||
style=theme.BOLD_BIGGER_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_BIGGER_SECTION_TITLE_DESKTOP,
|
||||
),
|
||||
# Add Section Subtitle
|
||||
rio.Text(
|
||||
@@ -90,7 +90,7 @@ class FaqSection(rio.Component):
|
||||
"Frequently asked questions",
|
||||
justify="center",
|
||||
overflow="wrap",
|
||||
style=theme.BOLD_SECTION_TITEL_MOBILE,
|
||||
style=theme.BOLD_SECTION_TITLE_MOBILE,
|
||||
),
|
||||
# Add Section Subtitle
|
||||
rio.Text(
|
||||
|
||||
+2
-2
@@ -33,13 +33,13 @@ class GetStarted(rio.Component):
|
||||
# Set styles and layout behavior based on the device type
|
||||
if device == "desktop":
|
||||
header_text_style: rio.TextStyle = (
|
||||
theme.BOLD_SMALLER_SECTION_TITEL_DESKTOP
|
||||
theme.BOLD_SMALLER_SECTION_TITLE_DESKTOP
|
||||
)
|
||||
sub_header_text_size = 1.1
|
||||
overflow = "nowrap"
|
||||
|
||||
else:
|
||||
header_text_style: rio.TextStyle = theme.BOLD_SECTION_TITEL_MOBILE
|
||||
header_text_style: rio.TextStyle = theme.BOLD_SECTION_TITLE_MOBILE
|
||||
sub_header_text_size = 1
|
||||
overflow = "wrap"
|
||||
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class HeroSection(rio.Component):
|
||||
rio.Text(
|
||||
"Build your SaaS in seconds",
|
||||
justify="center",
|
||||
style=theme.BOLD_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_SECTION_TITLE_DESKTOP,
|
||||
),
|
||||
# Add subtitle
|
||||
rio.Text(
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ class MajorColumn(rio.Component):
|
||||
# Add header
|
||||
rio.Text(
|
||||
self.header,
|
||||
style=theme.BOLD_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_SECTION_TITLE_DESKTOP,
|
||||
),
|
||||
# Add sub-header
|
||||
rio.Text(
|
||||
@@ -89,7 +89,7 @@ class MajorColumn(rio.Component):
|
||||
# Add header
|
||||
rio.Text(
|
||||
self.header,
|
||||
style=theme.BOLD_SECTION_TITEL_MOBILE,
|
||||
style=theme.BOLD_SECTION_TITLE_MOBILE,
|
||||
),
|
||||
# Add sub-header
|
||||
rio.Text(
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ class PricingSection(rio.Component):
|
||||
rio.Text(
|
||||
"A plan for every need",
|
||||
justify="center",
|
||||
style=theme.BOLD_BIGGER_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_BIGGER_SECTION_TITLE_DESKTOP,
|
||||
),
|
||||
# Sub-header providing additional context
|
||||
rio.Text(
|
||||
@@ -106,7 +106,7 @@ class PricingSection(rio.Component):
|
||||
"A plan for every need",
|
||||
justify="center", # Center-align the text horizontally
|
||||
overflow="wrap", # Allow text to wrap to the next line if necessary
|
||||
style=theme.BOLD_BIGGER_SECTION_TITEL_MOBILE,
|
||||
style=theme.BOLD_BIGGER_SECTION_TITLE_MOBILE,
|
||||
),
|
||||
# Sub-header providing additional context
|
||||
rio.Text(
|
||||
|
||||
+2
-2
@@ -109,7 +109,7 @@ class Testimonials(rio.Component):
|
||||
# Section Title
|
||||
rio.Text(
|
||||
"What our customers are saying.",
|
||||
style=theme.BOLD_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_SECTION_TITLE_DESKTOP,
|
||||
justify="center",
|
||||
),
|
||||
# Brief description
|
||||
@@ -162,7 +162,7 @@ class Testimonials(rio.Component):
|
||||
rio.Text(
|
||||
"What our customers are saying.",
|
||||
overflow="wrap",
|
||||
style=theme.BOLD_SECTION_TITEL_MOBILE,
|
||||
style=theme.BOLD_SECTION_TITLE_MOBILE,
|
||||
justify="center",
|
||||
),
|
||||
# Brief Description
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@ class BlogPage(rio.Component):
|
||||
return rio.Column(
|
||||
rio.Text(
|
||||
"Blog",
|
||||
style=theme.BOLD_BIGGER_SECTION_TITEL_DESKTOP,
|
||||
style=theme.BOLD_BIGGER_SECTION_TITLE_DESKTOP,
|
||||
),
|
||||
rio.Text(
|
||||
"Welcome to our blog. Here you can find all the latest news and updates.",
|
||||
@@ -64,7 +64,7 @@ class BlogPage(rio.Component):
|
||||
rio.Text(
|
||||
"Blog",
|
||||
overflow="wrap",
|
||||
style=theme.BOLD_BIGGER_SECTION_TITEL_MOBILE,
|
||||
style=theme.BOLD_BIGGER_SECTION_TITLE_MOBILE,
|
||||
),
|
||||
rio.Text(
|
||||
"Welcome to our blog. Here you can find all the latest news and updates.",
|
||||
|
||||
@@ -54,20 +54,20 @@ DARK_TEXT_SMALLER = rio.TextStyle(
|
||||
|
||||
|
||||
# Text style for desktop
|
||||
BOLD_BIGGER_SECTION_TITEL_DESKTOP = rio.TextStyle(
|
||||
BOLD_BIGGER_SECTION_TITLE_DESKTOP = rio.TextStyle(
|
||||
fill=TEXT_FILL_BRIGHTER,
|
||||
font_size=SUB_TITLE_HEIGHT * 1.1,
|
||||
font_weight="bold",
|
||||
)
|
||||
|
||||
|
||||
BOLD_SECTION_TITEL_DESKTOP = rio.TextStyle(
|
||||
BOLD_SECTION_TITLE_DESKTOP = rio.TextStyle(
|
||||
fill=TEXT_FILL_BRIGHTER,
|
||||
font_size=SUB_TITLE_HEIGHT,
|
||||
font_weight="bold",
|
||||
)
|
||||
|
||||
BOLD_SMALLER_SECTION_TITEL_DESKTOP = rio.TextStyle(
|
||||
BOLD_SMALLER_SECTION_TITLE_DESKTOP = rio.TextStyle(
|
||||
fill=TEXT_FILL_BRIGHTER,
|
||||
font_size=SUB_TITLE_HEIGHT * 0.8,
|
||||
font_weight="bold",
|
||||
@@ -75,13 +75,13 @@ BOLD_SMALLER_SECTION_TITEL_DESKTOP = rio.TextStyle(
|
||||
|
||||
|
||||
# Text style for mobile
|
||||
BOLD_BIGGER_SECTION_TITEL_MOBILE = rio.TextStyle(
|
||||
BOLD_BIGGER_SECTION_TITLE_MOBILE = rio.TextStyle(
|
||||
fill=TEXT_FILL_BRIGHTER,
|
||||
font_size=SUB_TITLE_HEIGHT * 1.1 * MOBILE_TEXT_SCALING,
|
||||
font_weight="bold",
|
||||
)
|
||||
|
||||
BOLD_SECTION_TITEL_MOBILE = rio.TextStyle(
|
||||
BOLD_SECTION_TITLE_MOBILE = rio.TextStyle(
|
||||
fill=TEXT_FILL_BRIGHTER,
|
||||
font_size=SUB_TITLE_HEIGHT * MOBILE_TEXT_SCALING,
|
||||
font_weight="bold",
|
||||
|
||||
+85
-26
@@ -88,11 +88,24 @@ class TextStyle(SelfSerializing):
|
||||
be styled. You can use it to specify the font, fill, size, and other
|
||||
properties of text in your rio app.
|
||||
|
||||
All parameters are optional. Omitting a parameter will leave that setting
|
||||
unchanged (i.e. as if you hadn't applied the style at all). For example:
|
||||
|
||||
```py
|
||||
theme = rio.Theme.from_colors(text_color=rio.Color.PURPLE)
|
||||
highlighted_style = rio.TextStyle(font_weight="bold", italic=True)
|
||||
|
||||
... # Somewhere later
|
||||
|
||||
# This text will be purple, as defined in the theme, but also bold and
|
||||
# italic.
|
||||
rio.Text("Hello, World!", style=highlighted_style)
|
||||
```
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
`font`: The `Font` to use for the text. When set to `None`, the default font
|
||||
for the current context (heading or regular text, etc) will be used.
|
||||
`font`: The `Font` to use for the text.
|
||||
|
||||
`fill`: The fill (color, gradient, etc.) for the text.
|
||||
|
||||
@@ -112,24 +125,26 @@ class TextStyle(SelfSerializing):
|
||||
_: dataclasses.KW_ONLY
|
||||
font: Font | None = None
|
||||
fill: _TextFill | None = None
|
||||
font_size: float = 1.0
|
||||
italic: bool = False
|
||||
font_weight: t.Literal["normal", "bold"] = "normal"
|
||||
underlined: bool = False
|
||||
strikethrough: bool = False
|
||||
all_caps: bool = False
|
||||
font_size: float | None = None
|
||||
italic: bool | None = None
|
||||
font_weight: t.Literal["normal", "bold"] | None = None
|
||||
underlined: bool | None = None
|
||||
strikethrough: bool | None = None
|
||||
all_caps: bool | None = None
|
||||
|
||||
def replace(
|
||||
self,
|
||||
*,
|
||||
font: Font | None = None,
|
||||
font: Font | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
fill: _TextFill | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
font_size: float | None = None,
|
||||
italic: bool | None = None,
|
||||
font_weight: t.Literal["normal", "bold"] | None = None,
|
||||
underlined: bool | None = None,
|
||||
strikethrough: bool | None = None,
|
||||
all_caps: bool | None = None,
|
||||
font_size: float | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
italic: bool | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
font_weight: t.Literal["normal", "bold"]
|
||||
| None
|
||||
| utils.NotGiven = utils.NOT_GIVEN,
|
||||
underlined: bool | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
strikethrough: bool | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
all_caps: bool | None | utils.NotGiven = utils.NOT_GIVEN,
|
||||
) -> TextStyle:
|
||||
"""
|
||||
Returns an updated copy of the style.
|
||||
@@ -139,9 +154,7 @@ class TextStyle(SelfSerializing):
|
||||
|
||||
## Parameters
|
||||
|
||||
`font`: The `Font` to use for the text. When set to `None`, the default
|
||||
font for the current context (heading or regular text, etc) will be
|
||||
used.
|
||||
`font`: The `Font` to use for the text.
|
||||
|
||||
`fill`: The fill (color, gradient, etc.) for the text.
|
||||
|
||||
@@ -157,19 +170,65 @@ class TextStyle(SelfSerializing):
|
||||
|
||||
`all_caps`: Whether the text is transformed to ALL CAPS or not.
|
||||
"""
|
||||
return type(self)(
|
||||
font=self.font if font is None else font,
|
||||
return TextStyle(
|
||||
font=self.font if isinstance(font, utils.NotGiven) else font,
|
||||
fill=self.fill if isinstance(fill, utils.NotGiven) else fill,
|
||||
font_size=self.font_size if font_size is None else font_size,
|
||||
italic=self.italic if italic is None else italic,
|
||||
font_size=(
|
||||
self.font_size
|
||||
if isinstance(font_size, utils.NotGiven)
|
||||
else font_size
|
||||
),
|
||||
italic=(
|
||||
self.italic if isinstance(italic, utils.NotGiven) else italic
|
||||
),
|
||||
font_weight=(
|
||||
self.font_weight if font_weight is None else font_weight
|
||||
self.font_weight
|
||||
if isinstance(font_weight, utils.NotGiven)
|
||||
else font_weight
|
||||
),
|
||||
underlined=(
|
||||
self.underlined
|
||||
if isinstance(underlined, utils.NotGiven)
|
||||
else underlined
|
||||
),
|
||||
underlined=self.underlined if underlined is None else underlined,
|
||||
strikethrough=(
|
||||
self.strikethrough if strikethrough is None else strikethrough
|
||||
self.strikethrough
|
||||
if isinstance(strikethrough, utils.NotGiven)
|
||||
else strikethrough
|
||||
),
|
||||
all_caps=(
|
||||
self.all_caps
|
||||
if isinstance(all_caps, utils.NotGiven)
|
||||
else all_caps
|
||||
),
|
||||
)
|
||||
|
||||
def _merged_with(self, other: TextStyle) -> TextStyle:
|
||||
return TextStyle(
|
||||
font=self.font if other.font is None else other.font,
|
||||
fill=self.fill if other.fill is None else other.fill,
|
||||
font_size=(
|
||||
self.font_size if other.font_size is None else other.font_size
|
||||
),
|
||||
italic=self.italic if other.italic is None else other.italic,
|
||||
font_weight=(
|
||||
self.font_weight
|
||||
if other.font_weight is None
|
||||
else other.font_weight
|
||||
),
|
||||
underlined=(
|
||||
self.underlined
|
||||
if other.underlined is None
|
||||
else other.underlined
|
||||
),
|
||||
strikethrough=(
|
||||
self.strikethrough
|
||||
if other.strikethrough is None
|
||||
else other.strikethrough
|
||||
),
|
||||
all_caps=(
|
||||
self.all_caps if other.all_caps is None else other.all_caps
|
||||
),
|
||||
all_caps=self.all_caps if all_caps is None else all_caps,
|
||||
)
|
||||
|
||||
def _serialize(self, sess: rio.Session) -> JsonDoc:
|
||||
|
||||
+79
-21
@@ -270,6 +270,26 @@ class Palette:
|
||||
)
|
||||
|
||||
|
||||
class TextStyle(text_style_module.TextStyle):
|
||||
"""
|
||||
For static typing purposes only. A `TextStyle` where none of the attributes
|
||||
are `None`.
|
||||
|
||||
We don't really want the user to know that this class exists. It has the
|
||||
same name as the regular `rio.TextStyle` because that name is displayed by
|
||||
IDEs. It's also special-cased in the code that creates Rio's documentation.
|
||||
"""
|
||||
|
||||
font: text_style_module.Font # type: ignore
|
||||
fill: text_style_module._TextFill # type: ignore
|
||||
font_size: float # type: ignore
|
||||
italic: bool # type: ignore
|
||||
font_weight: t.Literal["normal", "bold"] # type: ignore
|
||||
underlined: bool # type: ignore
|
||||
strikethrough: bool # type: ignore
|
||||
all_caps: bool # type: ignore
|
||||
|
||||
|
||||
@t.final
|
||||
@dataclasses.dataclass()
|
||||
class Theme:
|
||||
@@ -282,8 +302,8 @@ class Theme:
|
||||
|
||||
Warning: The exact attributes available in themes are still subject to
|
||||
change. The recommended way to create themes is using either the
|
||||
`from_colors` or `pair_from_colors` method, as they provide a more
|
||||
stable interface.
|
||||
`Theme.from_colors` or `Theme.pair_from_colors` method, as they provide
|
||||
a more stable interface.
|
||||
|
||||
## Attributes
|
||||
|
||||
@@ -328,14 +348,6 @@ class Theme:
|
||||
color.
|
||||
|
||||
`monospace_font`: The font to use for monospace text, such as code.
|
||||
|
||||
`heading1_style`: The text style to use for the largest headings.
|
||||
|
||||
`heading2_style`: The text style to use for the second largest headings.
|
||||
|
||||
`heading3_style`: The text style to use for the third largest headings.
|
||||
|
||||
`text_style`: The text style to use for regular text.
|
||||
"""
|
||||
|
||||
_: dataclasses.KW_ONLY
|
||||
@@ -361,11 +373,44 @@ class Theme:
|
||||
|
||||
monospace_font: text_style_module.Font
|
||||
|
||||
# Text styles
|
||||
heading1_style: rio.TextStyle
|
||||
heading2_style: rio.TextStyle
|
||||
heading3_style: rio.TextStyle
|
||||
text_style: rio.TextStyle
|
||||
# Text styles are defined as properties for type checking reasons. Users can
|
||||
# assign a regular `rio.TextStyle`, but accessing an attribute returns a
|
||||
# style where all attributes are not `None`.
|
||||
@property
|
||||
def heading1_style(self) -> TextStyle:
|
||||
"The text style to use for the largest headings."
|
||||
return self._heading1_style
|
||||
|
||||
@heading1_style.setter
|
||||
def heading1_style(self, style: text_style_module.TextStyle) -> None:
|
||||
self._heading1_style = self._heading1_style._merged_with(style)
|
||||
|
||||
@property
|
||||
def heading2_style(self) -> TextStyle:
|
||||
"The text style to use for the second largest headings."
|
||||
return self._heading2_style
|
||||
|
||||
@heading2_style.setter
|
||||
def heading2_style(self, style: text_style_module.TextStyle) -> None:
|
||||
self._heading2_style = self._heading2_style._merged_with(style)
|
||||
|
||||
@property
|
||||
def heading3_style(self) -> TextStyle:
|
||||
"The text style to use for the third largest headings."
|
||||
return self._heading3_style
|
||||
|
||||
@heading3_style.setter
|
||||
def heading3_style(self, style: text_style_module.TextStyle) -> None:
|
||||
self._heading3_style = self._heading3_style._merged_with(style)
|
||||
|
||||
@property
|
||||
def text_style(self) -> TextStyle:
|
||||
"The text style to use for regular text."
|
||||
return self._text_style
|
||||
|
||||
@text_style.setter
|
||||
def text_style(self, style: text_style_module.TextStyle) -> None:
|
||||
self._text_style = self._text_style._merged_with(style)
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Themes are still very much in flux. New attributes will be added,
|
||||
@@ -410,6 +455,15 @@ class Theme:
|
||||
"""
|
||||
self = object.__new__(Theme)
|
||||
|
||||
# Make sure the TextStyles have all attributes set
|
||||
for style in (
|
||||
heading1_style,
|
||||
heading2_style,
|
||||
heading3_style,
|
||||
text_style,
|
||||
):
|
||||
assert None not in vars(style).values()
|
||||
|
||||
self.__dict__.update(
|
||||
{
|
||||
"primary_palette": primary_palette,
|
||||
@@ -426,10 +480,10 @@ class Theme:
|
||||
"corner_radius_large": corner_radius_large,
|
||||
"shadow_color": shadow_color,
|
||||
"monospace_font": monospace_font,
|
||||
"heading1_style": heading1_style,
|
||||
"heading2_style": heading2_style,
|
||||
"heading3_style": heading3_style,
|
||||
"text_style": text_style,
|
||||
"_heading1_style": heading1_style,
|
||||
"_heading2_style": heading2_style,
|
||||
"_heading3_style": heading3_style,
|
||||
"_text_style": text_style,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -710,10 +764,15 @@ class Theme:
|
||||
heading_fill = heading_fill
|
||||
|
||||
# Text styles
|
||||
heading1_style = rio.TextStyle(
|
||||
heading1_style = text_style_module.TextStyle(
|
||||
font=font if heading_font is None else heading_font,
|
||||
fill=heading_fill,
|
||||
font_size=2.3,
|
||||
italic=False,
|
||||
font_weight="normal",
|
||||
underlined=False,
|
||||
strikethrough=False,
|
||||
all_caps=False,
|
||||
)
|
||||
heading2_style = heading1_style.replace(font_size=1.7)
|
||||
heading3_style = heading1_style.replace(font_size=1.2)
|
||||
@@ -723,7 +782,6 @@ class Theme:
|
||||
fill=neutral_and_background_text_color,
|
||||
)
|
||||
|
||||
# Build the final theme
|
||||
# Instantiate the theme. `__init__` is blocked to prevent users from
|
||||
# doing something foolish. Work around that.
|
||||
return rio.Theme._create_new(
|
||||
|
||||
Reference in New Issue
Block a user