Compare commits
No commits in common. "pagetop-work-in-progress" and "main" have entirely different histories.
pagetop-wo
...
main
302 changed files with 3187 additions and 19042 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,9 +1,6 @@
|
||||||
# Ignora directorios de compilación
|
# Ignora directorios de compilación
|
||||||
**/target
|
**/target
|
||||||
|
|
||||||
# Ignora directorios de archivos estáticos
|
|
||||||
**/static
|
|
||||||
|
|
||||||
# Archivos de log
|
# Archivos de log
|
||||||
**/log/*.log*
|
**/log/*.log*
|
||||||
|
|
||||||
|
|
|
||||||
58
Cargo.lock
generated
58
Cargo.lock
generated
|
|
@ -20,15 +20,6 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "0.7.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
|
|
@ -942,7 +933,7 @@ version = "0.4.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.1.4",
|
"aho-corasick",
|
||||||
"bstr",
|
"bstr",
|
||||||
"log",
|
"log",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
|
|
@ -974,16 +965,6 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
"bumpalo",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
|
@ -1514,17 +1495,6 @@ dependencies = [
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minify-js"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b1fa5546ee8bd66024113e506cabe4230e76635a094c06ea2051b66021dda92e"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick 0.7.20",
|
|
||||||
"lazy_static",
|
|
||||||
"parse-js",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
|
|
@ -1751,6 +1721,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pagetop",
|
"pagetop",
|
||||||
"pagetop-build",
|
"pagetop-build",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1760,6 +1731,7 @@ dependencies = [
|
||||||
"pagetop",
|
"pagetop",
|
||||||
"pagetop-build",
|
"pagetop-build",
|
||||||
"serde",
|
"serde",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1767,18 +1739,9 @@ name = "pagetop-build"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grass",
|
"grass",
|
||||||
"minify-js",
|
|
||||||
"pagetop-statics",
|
"pagetop-statics",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pagetop-htmx"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"pagetop",
|
|
||||||
"pagetop-build",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop-macros"
|
name = "pagetop-macros"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -1849,19 +1812,6 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parse-js"
|
|
||||||
version = "0.20.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2742b5e32dcb5930447ed9f9e401a7dfd883867fc079c4fac44ae8ba3593710e"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick 0.7.20",
|
|
||||||
"bumpalo",
|
|
||||||
"hashbrown 0.13.2",
|
|
||||||
"lazy_static",
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pastey"
|
name = "pastey"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -2144,7 +2094,7 @@ version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.1.4",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ members = [
|
||||||
# Extensions
|
# Extensions
|
||||||
"extensions/pagetop-aliner",
|
"extensions/pagetop-aliner",
|
||||||
"extensions/pagetop-bootsier",
|
"extensions/pagetop-bootsier",
|
||||||
"extensions/pagetop-htmx",
|
|
||||||
"extensions/pagetop-seaorm",
|
"extensions/pagetop-seaorm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -36,7 +35,6 @@ indexmap = "2.14"
|
||||||
indoc = "2.0"
|
indoc = "2.0"
|
||||||
itoa = "1.0"
|
itoa = "1.0"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
minify-js = "0.6"
|
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
pastey = "0.2"
|
pastey = "0.2"
|
||||||
path-slash = "0.2"
|
path-slash = "0.2"
|
||||||
|
|
@ -64,7 +62,6 @@ pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" }
|
||||||
# Extensions
|
# Extensions
|
||||||
pagetop-aliner = { version = "0.1", path = "extensions/pagetop-aliner" }
|
pagetop-aliner = { version = "0.1", path = "extensions/pagetop-aliner" }
|
||||||
pagetop-bootsier = { version = "0.1", path = "extensions/pagetop-bootsier" }
|
pagetop-bootsier = { version = "0.1", path = "extensions/pagetop-bootsier" }
|
||||||
pagetop-htmx = { version = "0.1", path = "extensions/pagetop-htmx" }
|
|
||||||
pagetop-seaorm = { version = "0.0", path = "extensions/pagetop-seaorm" }
|
pagetop-seaorm = { version = "0.0", path = "extensions/pagetop-seaorm" }
|
||||||
# PageTop
|
# PageTop
|
||||||
pagetop = { version = "0.5", path = "." }
|
pagetop = { version = "0.5", path = "." }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/banner.png" />
|
<img src="https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/banner.png" />
|
||||||
|
|
||||||
<h1>PageTop</h1>
|
<h1>PageTop</h1>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
:root {
|
|
||||||
/* Font families */
|
|
||||||
--val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif;
|
|
||||||
--val-font-serif: Georgia,"Times New Roman",serif;
|
|
||||||
--val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
|
||||||
--val-font-family: var(--val-font-sans);
|
|
||||||
/* Font size */
|
|
||||||
--val-fs--base: 1rem;
|
|
||||||
/* Font weight */
|
|
||||||
--val-fw--base: 400;
|
|
||||||
/* Line height */
|
|
||||||
--val-lh--base: 1.5;
|
|
||||||
/* Colors */
|
|
||||||
--val-color--bg: #fafafa;
|
|
||||||
--val-color--text: #212529;
|
|
||||||
--val-color--text--muted: color-mix(in srgb, var(--val-color--text) 50%, var(--val-color--bg));
|
|
||||||
--val-color--border: #212529;
|
|
||||||
--val-color--primary: #0d6efd;
|
|
||||||
--val-color--danger: #dc3545;
|
|
||||||
--val-color--switch-off: #adb5bd;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, *::before, *::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: var(--val-font-family);
|
|
||||||
font-size: var(--val-fs--base);
|
|
||||||
font-weight: var(--val-fw--base);
|
|
||||||
line-height: var(--val-lh--base);
|
|
||||||
color: var(--val-color--text);
|
|
||||||
background-color: var(--val-color--bg);
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Form components
|
|
||||||
*/
|
|
||||||
|
|
||||||
.form-required {
|
|
||||||
color: var(--val-color--danger);
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-text {
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
color: var(--val-color--text--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-field > select.form-select,
|
|
||||||
.form-field > input.form-control,
|
|
||||||
.form-field > input.form-range,
|
|
||||||
.form-field > textarea.form-control {
|
|
||||||
display: block;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 2rem 0 1rem;
|
|
||||||
background: var(--val-color--bg);
|
|
||||||
}
|
|
||||||
fieldset > legend {
|
|
||||||
background-color: var(--val-color--bg);
|
|
||||||
border: 2px groove threedface;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
min-height: 1.5rem;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
.form-check-inline {
|
|
||||||
display: inline-flex;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
.form-check-reverse {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
justify-content: flex-start;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.form-check-input {
|
|
||||||
flex-shrink: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.form-check-label {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.form-switch .form-check-input {
|
|
||||||
appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 2em;
|
|
||||||
height: 1em;
|
|
||||||
background-color: var(--val-color--switch-off);
|
|
||||||
border-radius: 1em;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color .15s ease-in-out;
|
|
||||||
}
|
|
||||||
.form-switch .form-check-input::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
transition: transform .15s ease-in-out;
|
|
||||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, .25);
|
|
||||||
}
|
|
||||||
.form-switch .form-check-input:checked {
|
|
||||||
background-color: var(--val-color--primary);
|
|
||||||
}
|
|
||||||
.form-switch .form-check-input:checked::after {
|
|
||||||
transform: translateX(1em);
|
|
||||||
}
|
|
||||||
|
|
||||||
input:disabled,
|
|
||||||
input:disabled + label {
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
input:disabled + label {
|
|
||||||
color: var(--val-color--text--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Region Footer
|
|
||||||
*/
|
|
||||||
|
|
||||||
.region-footer {
|
|
||||||
padding: .75rem 0 3rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
8
build.rs
8
build.rs
|
|
@ -1,12 +1,6 @@
|
||||||
use pagetop_build::{StaticFilesBundle, copy_dir};
|
use pagetop_build::StaticFilesBundle;
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
// Regenera `static/` desde cero sólo si hay cambios en `assets/`.
|
|
||||||
println!("cargo:rerun-if-changed=assets");
|
|
||||||
let _ = std::fs::remove_dir_all("static");
|
|
||||||
|
|
||||||
copy_dir("assets", "static")?;
|
|
||||||
|
|
||||||
StaticFilesBundle::from_dir("./static", None)
|
StaticFilesBundle::from_dir("./static", None)
|
||||||
.with_name("assets")
|
.with_name("assets")
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use pagetop_bootsier::theme::form;
|
|
||||||
use pagetop_bootsier::theme::*;
|
use pagetop_bootsier::theme::*;
|
||||||
|
|
||||||
include_locales!(LOC from "examples/locale");
|
include_locales!(LOC from "examples/locale");
|
||||||
|
|
@ -144,20 +143,17 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_value("form-selections"),
|
.with_value("form-selections"),
|
||||||
)
|
)
|
||||||
// Botones de acción.
|
// Botones de acción.
|
||||||
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
|
|
||||||
PropsOp::add_classes(classes::ButtonColor::solid(
|
|
||||||
Color::Primary,
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
|
|
||||||
PropsOp::add_classes(classes::ButtonColor::outline(
|
|
||||||
Color::Secondary,
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::plain(L10n::t("btn_cancel", &LOC)).with_prop(
|
Button::submit(L10n::t("btn_submit", &LOC))
|
||||||
PropsOp::add_classes(classes::ButtonColor::link()),
|
.with_color(ButtonColor::Background(Color::Primary)),
|
||||||
),
|
)
|
||||||
|
.with_child(
|
||||||
|
Button::reset(L10n::t("btn_reset", &LOC))
|
||||||
|
.with_color(ButtonColor::Outline(Color::Secondary)),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Button::plain(L10n::t("btn_cancel", &LOC))
|
||||||
|
.with_color(ButtonColor::Link),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -179,7 +175,6 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_name("name")
|
.with_name("name")
|
||||||
.with_label(L10n::t("label_name", &LOC))
|
.with_label(L10n::t("label_name", &LOC))
|
||||||
.with_placeholder(L10n::t("placeholder_name", &LOC))
|
.with_placeholder(L10n::t("placeholder_name", &LOC))
|
||||||
.with_help_text(L10n::t("help_name", &LOC))
|
|
||||||
.with_required(true),
|
.with_required(true),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
|
|
@ -190,7 +185,6 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
"placeholder_email",
|
"placeholder_email",
|
||||||
&LOC,
|
&LOC,
|
||||||
))
|
))
|
||||||
.with_help_text(L10n::t("help_email", &LOC))
|
|
||||||
.with_autocomplete(
|
.with_autocomplete(
|
||||||
Some(form::Autocomplete::email()),
|
Some(form::Autocomplete::email()),
|
||||||
)
|
)
|
||||||
|
|
@ -268,41 +262,26 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_value("form-text"),
|
.with_value("form-text"),
|
||||||
)
|
)
|
||||||
// Botones de acción.
|
// Botones de acción.
|
||||||
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
|
|
||||||
PropsOp::add_classes(classes::ButtonColor::solid(
|
|
||||||
Color::Primary,
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
|
|
||||||
PropsOp::add_classes(classes::ButtonColor::outline(
|
|
||||||
Color::Secondary,
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::plain(L10n::t("btn_cancel", &LOC)).with_prop(
|
Button::submit(L10n::t("btn_submit", &LOC))
|
||||||
PropsOp::add_classes(classes::ButtonColor::link()),
|
.with_color(ButtonColor::Background(Color::Primary)),
|
||||||
),
|
)
|
||||||
|
.with_child(
|
||||||
|
Button::reset(L10n::t("btn_reset", &LOC))
|
||||||
|
.with_color(ButtonColor::Outline(Color::Secondary)),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Button::plain(L10n::t("btn_cancel", &LOC))
|
||||||
|
.with_color(ButtonColor::Link),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// Bloque 3: listas de selección y etiquetas flotantes.
|
// Bloque 3: listas de selección y etiquetas flotantes.
|
||||||
.with_child(
|
.with_child(
|
||||||
Block::new()
|
Block::new()
|
||||||
.with_title(
|
.with_title(L10n::t("block_lists", &LOC))
|
||||||
if global::SETTINGS.app.theme.eq_ignore_ascii_case("bootsier") {
|
.with_child(
|
||||||
L10n::t("block_lists_floating", &LOC)
|
Form::new()
|
||||||
} else {
|
|
||||||
L10n::t("block_lists", &LOC)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.with_child(form_lists()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.render()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn form_lists() -> Form {
|
|
||||||
let mut form = Form::new()
|
|
||||||
.with_id("form-lists")
|
.with_id("form-lists")
|
||||||
.with_action("/")
|
.with_action("/")
|
||||||
.with_method(form::Method::Post)
|
.with_method(form::Method::Post)
|
||||||
|
|
@ -315,11 +294,17 @@ fn form_lists() -> Form {
|
||||||
.with_name("language")
|
.with_name("language")
|
||||||
.with_label(L10n::t("label_language", &LOC))
|
.with_label(L10n::t("label_language", &LOC))
|
||||||
.with_item(
|
.with_item(
|
||||||
form::select::Item::new("", L10n::t("select_choose", &LOC))
|
form::select::Item::new(
|
||||||
|
"",
|
||||||
|
L10n::t("select_choose", &LOC),
|
||||||
|
)
|
||||||
.with_selected(true),
|
.with_selected(true),
|
||||||
)
|
)
|
||||||
.with_group(
|
.with_group(
|
||||||
form::select::Group::new(L10n::t("select_group_europe", &LOC))
|
form::select::Group::new(L10n::t(
|
||||||
|
"select_group_europe",
|
||||||
|
&LOC,
|
||||||
|
))
|
||||||
.with_item(form::select::Item::new(
|
.with_item(form::select::Item::new(
|
||||||
"es",
|
"es",
|
||||||
L10n::t("select_spanish", &LOC),
|
L10n::t("select_spanish", &LOC),
|
||||||
|
|
@ -330,7 +315,10 @@ fn form_lists() -> Form {
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.with_group(
|
.with_group(
|
||||||
form::select::Group::new(L10n::t("select_group_americas", &LOC))
|
form::select::Group::new(L10n::t(
|
||||||
|
"select_group_americas",
|
||||||
|
&LOC,
|
||||||
|
))
|
||||||
.with_item(form::select::Item::new(
|
.with_item(form::select::Item::new(
|
||||||
"en",
|
"en",
|
||||||
L10n::t("select_english", &LOC),
|
L10n::t("select_english", &LOC),
|
||||||
|
|
@ -341,7 +329,10 @@ fn form_lists() -> Form {
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.with_item(
|
.with_item(
|
||||||
form::select::Item::new("xx", L10n::t("select_disabled", &LOC))
|
form::select::Item::new(
|
||||||
|
"xx",
|
||||||
|
L10n::t("select_disabled", &LOC),
|
||||||
|
)
|
||||||
.with_disabled(true),
|
.with_disabled(true),
|
||||||
)
|
)
|
||||||
.with_required(true),
|
.with_required(true),
|
||||||
|
|
@ -351,24 +342,38 @@ fn form_lists() -> Form {
|
||||||
.with_name("technologies")
|
.with_name("technologies")
|
||||||
.with_label(L10n::t("label_technologies", &LOC))
|
.with_label(L10n::t("label_technologies", &LOC))
|
||||||
.with_item(
|
.with_item(
|
||||||
form::select::Item::new("rust", L10n::n("Rust")).with_selected(true),
|
form::select::Item::new(
|
||||||
|
"rust",
|
||||||
|
L10n::n("Rust"),
|
||||||
)
|
)
|
||||||
.with_item(
|
|
||||||
form::select::Item::new("python", L10n::n("Python"))
|
|
||||||
.with_selected(true),
|
.with_selected(true),
|
||||||
)
|
)
|
||||||
.with_item(form::select::Item::new("javascript", L10n::n("JavaScript")))
|
.with_item(
|
||||||
.with_item(form::select::Item::new("go", L10n::n("Go")))
|
form::select::Item::new(
|
||||||
.with_item(form::select::Item::new("typescript", L10n::n("TypeScript")))
|
"python",
|
||||||
|
L10n::n("Python"),
|
||||||
|
)
|
||||||
|
.with_selected(true),
|
||||||
|
)
|
||||||
|
.with_item(form::select::Item::new(
|
||||||
|
"javascript",
|
||||||
|
L10n::n("JavaScript"),
|
||||||
|
))
|
||||||
|
.with_item(form::select::Item::new(
|
||||||
|
"go",
|
||||||
|
L10n::n("Go"),
|
||||||
|
))
|
||||||
|
.with_item(form::select::Item::new(
|
||||||
|
"typescript",
|
||||||
|
L10n::n("TypeScript"),
|
||||||
|
))
|
||||||
.with_multiple(true)
|
.with_multiple(true)
|
||||||
.with_rows(Some(4))
|
.with_rows(Some(4))
|
||||||
.with_help_text(L10n::t("help_technologies", &LOC)),
|
.with_help_text(L10n::t("help_technologies", &LOC)),
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
// Etiquetas flotantes.
|
||||||
// Etiquetas flotantes: solo disponibles con el tema Bootsier.
|
.with_child(
|
||||||
if global::SETTINGS.app.theme.eq_ignore_ascii_case("bootsier") {
|
|
||||||
form = form.with_child(
|
|
||||||
form::Fieldset::new()
|
form::Fieldset::new()
|
||||||
.with_legend(L10n::t("fieldset_floating", &LOC))
|
.with_legend(L10n::t("fieldset_floating", &LOC))
|
||||||
.with_child(
|
.with_child(
|
||||||
|
|
@ -383,7 +388,10 @@ fn form_lists() -> Form {
|
||||||
form::Textarea::new()
|
form::Textarea::new()
|
||||||
.with_name("fl_comment")
|
.with_name("fl_comment")
|
||||||
.with_label(L10n::t("label_comment", &LOC))
|
.with_label(L10n::t("label_comment", &LOC))
|
||||||
.with_placeholder(L10n::t("placeholder_comment", &LOC))
|
.with_placeholder(L10n::t(
|
||||||
|
"placeholder_comment",
|
||||||
|
&LOC,
|
||||||
|
))
|
||||||
.with_floating_label(true),
|
.with_floating_label(true),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
|
|
@ -391,14 +399,20 @@ fn form_lists() -> Form {
|
||||||
.with_name("fl_country")
|
.with_name("fl_country")
|
||||||
.with_label(L10n::t("label_country", &LOC))
|
.with_label(L10n::t("label_country", &LOC))
|
||||||
.with_item(
|
.with_item(
|
||||||
form::select::Item::new("", L10n::t("select_choose", &LOC))
|
form::select::Item::new(
|
||||||
|
"",
|
||||||
|
L10n::t("select_choose", &LOC),
|
||||||
|
)
|
||||||
.with_selected(true),
|
.with_selected(true),
|
||||||
)
|
)
|
||||||
.with_item(form::select::Item::new(
|
.with_item(form::select::Item::new(
|
||||||
"de",
|
"de",
|
||||||
L10n::t("select_germany", &LOC),
|
L10n::t("select_germany", &LOC),
|
||||||
))
|
))
|
||||||
.with_item(form::select::Item::new("es", L10n::t("select_spain", &LOC)))
|
.with_item(form::select::Item::new(
|
||||||
|
"es",
|
||||||
|
L10n::t("select_spain", &LOC),
|
||||||
|
))
|
||||||
.with_item(form::select::Item::new(
|
.with_item(form::select::Item::new(
|
||||||
"fr",
|
"fr",
|
||||||
L10n::t("select_france", &LOC),
|
L10n::t("select_france", &LOC),
|
||||||
|
|
@ -410,10 +424,7 @@ fn form_lists() -> Form {
|
||||||
.with_floating_label(true)
|
.with_floating_label(true)
|
||||||
.with_required(true),
|
.with_required(true),
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
}
|
|
||||||
|
|
||||||
form
|
|
||||||
// Campo oculto (form::Hidden).
|
// Campo oculto (form::Hidden).
|
||||||
.with_child(
|
.with_child(
|
||||||
form::Hidden::new()
|
form::Hidden::new()
|
||||||
|
|
@ -422,19 +433,21 @@ fn form_lists() -> Form {
|
||||||
)
|
)
|
||||||
// Botones de acción.
|
// Botones de acción.
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::submit(L10n::t("btn_submit", &LOC)).with_prop(PropsOp::add_classes(
|
Button::submit(L10n::t("btn_submit", &LOC))
|
||||||
classes::ButtonColor::solid(Color::Primary),
|
.with_color(ButtonColor::Background(Color::Primary)),
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::reset(L10n::t("btn_reset", &LOC)).with_prop(PropsOp::add_classes(
|
Button::reset(L10n::t("btn_reset", &LOC))
|
||||||
classes::ButtonColor::outline(Color::Secondary),
|
.with_color(ButtonColor::Outline(Color::Secondary)),
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::plain(L10n::t("btn_cancel", &LOC))
|
Button::plain(L10n::t("btn_cancel", &LOC))
|
||||||
.with_prop(PropsOp::add_classes(classes::ButtonColor::link())),
|
.with_color(ButtonColor::Link),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pagetop::main]
|
#[pagetop::main]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ title = Form controls
|
||||||
slogan = Bootsier form components showcase
|
slogan = Bootsier form components showcase
|
||||||
block_selections = Checkboxes, switches and radio buttons
|
block_selections = Checkboxes, switches and radio buttons
|
||||||
block_text = Text fields, multiline and range
|
block_text = Text fields, multiline and range
|
||||||
block_lists = Selection lists
|
block_lists = Select lists and floating labels
|
||||||
block_lists_floating = Select lists and floating labels
|
|
||||||
|
|
||||||
fieldset_text = Text fields
|
fieldset_text = Text fields
|
||||||
label_name = Full name
|
label_name = Full name
|
||||||
|
|
@ -17,8 +16,6 @@ label_url = Website
|
||||||
placeholder_url = https://example.com
|
placeholder_url = https://example.com
|
||||||
label_search = Search
|
label_search = Search
|
||||||
placeholder_search = Search term...
|
placeholder_search = Search term...
|
||||||
help_name = Enter your full name as it appears on your ID.
|
|
||||||
help_email = We will only use your email to send important notifications.
|
|
||||||
|
|
||||||
fieldset_textarea = Multiline text
|
fieldset_textarea = Multiline text
|
||||||
label_comment = Comment
|
label_comment = Comment
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ title = Controles de formulario
|
||||||
slogan = Componentes Bootsier para formularios
|
slogan = Componentes Bootsier para formularios
|
||||||
block_selections = Casillas, interruptores y botones de opción
|
block_selections = Casillas, interruptores y botones de opción
|
||||||
block_text = Campos de texto, multilínea y rango
|
block_text = Campos de texto, multilínea y rango
|
||||||
block_lists = Listas de selección
|
block_lists = Listas de selección y etiquetas flotantes
|
||||||
block_lists_floating = Listas de selección y etiquetas flotantes
|
|
||||||
|
|
||||||
fieldset_text = Campos de texto
|
fieldset_text = Campos de texto
|
||||||
label_name = Nombre completo
|
label_name = Nombre completo
|
||||||
|
|
@ -17,8 +16,6 @@ label_url = Sitio web
|
||||||
placeholder_url = https://ejemplo.com
|
placeholder_url = https://ejemplo.com
|
||||||
label_search = Búsqueda
|
label_search = Búsqueda
|
||||||
placeholder_search = Término de búsqueda...
|
placeholder_search = Término de búsqueda...
|
||||||
help_name = Introduce tu nombre completo tal como aparece en tu documento de identidad.
|
|
||||||
help_email = Solo usaremos tu correo para enviarte notificaciones importantes.
|
|
||||||
|
|
||||||
fieldset_textarea = Texto multilínea
|
fieldset_textarea = Texto multilínea
|
||||||
label_comment = Comentario
|
label_comment = Comentario
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,10 @@ impl Extension for SuperMenu {
|
||||||
))
|
))
|
||||||
.with_item(navbar::Item::nav(
|
.with_item(navbar::Item::nav(
|
||||||
Nav::new()
|
Nav::new()
|
||||||
.with_prop(PropsOp::add_classes(
|
.with_classes(
|
||||||
|
ClassesOp::Add,
|
||||||
classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(),
|
classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(),
|
||||||
))
|
)
|
||||||
.with_item(nav::Item::link(L10n::t("menus_item_sign_up", &LOC), |cx| {
|
.with_item(nav::Item::link(L10n::t("menus_item_sign_up", &LOC), |cx| {
|
||||||
cx.route("/auth/sign-up")
|
cx.route("/auth/sign-up")
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,8 @@ authors.workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pagetop.workspace = true
|
pagetop.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pagetop-build.workspace = true
|
pagetop-build.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Sobre PageTop
|
## 🧭 Sobre PageTop
|
||||||
|
|
||||||
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
||||||
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
configurables, basadas en HTML, CSS y JavaScript.
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
|
|
||||||
## Guía rápida
|
|
||||||
|
## ⚡️ Guía rápida
|
||||||
|
|
||||||
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
||||||
|
|
||||||
|
|
@ -43,6 +44,11 @@ impl Extension for MyApp {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pagetop::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
Application::prepare(&MyApp).run()?.await
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Y **selecciona el tema en la configuración** de la aplicación:
|
Y **selecciona el tema en la configuración** de la aplicación:
|
||||||
|
|
@ -61,10 +67,10 @@ use pagetop_aliner::Aliner;
|
||||||
async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
Page::new(request)
|
Page::new(request)
|
||||||
.with_theme(&Aliner)
|
.with_theme(&Aliner)
|
||||||
.with_child(
|
.add_child(
|
||||||
Block::new()
|
Block::new()
|
||||||
.with_title(L10n::l("sample_title"))
|
.with_title(L10n::l("sample_title"))
|
||||||
.with_child(Html::with(|cx| html! {
|
.add_child(Html::with(|cx| html! {
|
||||||
p { (L10n::l("sample_content").using(cx)) }
|
p { (L10n::l("sample_content").using(cx)) }
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
@ -72,13 +78,15 @@ async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advertencia
|
|
||||||
|
## 🚧 Advertencia
|
||||||
|
|
||||||
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
|
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
|
||||||
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
|
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
|
||||||
hasta que se libere la versión **1.0.0**.
|
hasta que se libere la versión **1.0.0**.
|
||||||
|
|
||||||
## Licencia
|
|
||||||
|
## 📜 Licencia
|
||||||
|
|
||||||
El código está disponible bajo una doble licencia:
|
El código está disponible bajo una doble licencia:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@
|
||||||
|
|
||||||
<h1>PageTop Aliner</h1>
|
<h1>PageTop Aliner</h1>
|
||||||
|
|
||||||
<p>Tema de <strong>PageTop</strong> que muestra esquemáticamente la composición de las páginas HTML.</p>
|
<p>Tema para <strong>PageTop</strong> que muestra esquemáticamente la composición de las páginas HTML.</p>
|
||||||
|
|
||||||
[](https://docs.rs/pagetop-aliner)
|
[](https://docs.rs/pagetop-aliner)
|
||||||
[](https://crates.io/crates/pagetop-aliner)
|
[](https://crates.io/crates/pagetop-aliner)
|
||||||
[](https://crates.io/crates/pagetop-aliner)
|
[](https://crates.io/crates/pagetop-aliner)
|
||||||
[](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner#licencia)
|
[](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner#licencia)
|
||||||
|
|
||||||
|
<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Sobre PageTop
|
## Sobre PageTop
|
||||||
|
|
@ -18,7 +19,8 @@
|
||||||
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
configurables, basadas en HTML, CSS y JavaScript.
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
|
|
||||||
## Guía rápida
|
|
||||||
|
# ⚡️ Guía rápida
|
||||||
|
|
||||||
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
||||||
|
|
||||||
|
|
@ -44,6 +46,11 @@ impl Extension for MyApp {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pagetop::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
Application::prepare(&MyApp).run()?.await
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Y **selecciona el tema en la configuración** de la aplicación:
|
Y **selecciona el tema en la configuración** de la aplicación:
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,8 @@ authors.workspace = true
|
||||||
pagetop.workspace = true
|
pagetop.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pagetop-build.workspace = true
|
pagetop-build.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Sobre PageTop
|
## 🧭 Sobre PageTop
|
||||||
|
|
||||||
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
||||||
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
configurables, basadas en HTML, CSS y JavaScript.
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
|
|
||||||
## Guía rápida
|
|
||||||
|
## ⚡️ Guía rápida
|
||||||
|
|
||||||
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
||||||
|
|
||||||
|
|
@ -43,6 +44,11 @@ impl Extension for MyApp {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pagetop::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
Application::prepare(&MyApp).run()?.await
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Y **selecciona el tema en la configuración** de la aplicación:
|
Y **selecciona el tema en la configuración** de la aplicación:
|
||||||
|
|
@ -61,10 +67,10 @@ use pagetop_bootsier::Bootsier;
|
||||||
async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
Page::new(request)
|
Page::new(request)
|
||||||
.with_theme(&Bootsier)
|
.with_theme(&Bootsier)
|
||||||
.with_child(
|
.add_child(
|
||||||
Block::new()
|
Block::new()
|
||||||
.with_title(L10n::l("sample_title"))
|
.with_title(L10n::l("sample_title"))
|
||||||
.with_child(Html::with(|cx| html! {
|
.add_child(Html::with(|cx| html! {
|
||||||
p { (L10n::l("sample_content").using(cx)) }
|
p { (L10n::l("sample_content").using(cx)) }
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
@ -72,19 +78,22 @@ async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Créditos
|
|
||||||
|
## 📚 Créditos
|
||||||
|
|
||||||
Este *crate* integra la biblioteca de estilos [Bootstrap 5.3.8](https://getbootstrap.com/) para
|
Este *crate* integra la biblioteca de estilos [Bootstrap 5.3.8](https://getbootstrap.com/) para
|
||||||
definir el comportamiento, la apariencia y los componentes de la interfaz. Bootstrap se distribuye
|
definir el comportamiento, la apariencia y los componentes de la interfaz. Bootstrap se distribuye
|
||||||
bajo licencia [MIT](https://github.com/twbs/bootstrap/blob/main/LICENSE).
|
bajo licencia [MIT](https://github.com/twbs/bootstrap/blob/main/LICENSE).
|
||||||
|
|
||||||
## Advertencia
|
|
||||||
|
## 🚧 Advertencia
|
||||||
|
|
||||||
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
|
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
|
||||||
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
|
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
|
||||||
hasta que se libere la versión **1.0.0**.
|
hasta que se libere la versión **1.0.0**.
|
||||||
|
|
||||||
## Licencia
|
|
||||||
|
## 📜 Licencia
|
||||||
|
|
||||||
El código está disponible bajo una doble licencia:
|
El código está disponible bajo una doble licencia:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
// Bootsier CSS rules: self-hosted fonts, form components, and regions.
|
|
||||||
|
|
||||||
// Self-hosted Source Sans 3 (SIL OFL 1.1), served from /bootsier/fonts.
|
|
||||||
// Required by AdminLTE 4, which declares it as the primary font family in $font-family-sans-serif.
|
|
||||||
@font-face {
|
|
||||||
font-family: "Source Sans 3";
|
|
||||||
src: url("/bootsier/fonts/bootsier.font.woff2") format("woff2");
|
|
||||||
font-weight: 200 900;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Source Sans 3";
|
|
||||||
src: url("/bootsier/fonts/bootsier.font.italic.woff2") format("woff2");
|
|
||||||
font-weight: 200 900;
|
|
||||||
font-style: italic;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font-relative top offset to keep the skip-link hidden at any font size.
|
|
||||||
.skip-link {
|
|
||||||
top: -3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required field indicator in forms.
|
|
||||||
.form-required {
|
|
||||||
color: var(--bs-danger);
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Form field with consistent bottom margin.
|
|
||||||
.form-field {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fieldset with border and floating legend.
|
|
||||||
fieldset {
|
|
||||||
position: relative;
|
|
||||||
background-color: var(--bs-body-bg);
|
|
||||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
|
||||||
border-radius: var(--bs-border-radius);
|
|
||||||
padding: 2rem 1rem 1rem;
|
|
||||||
margin: 2rem 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset > legend {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 1rem;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: var(--bs-secondary-color);
|
|
||||||
background-color: var(--bs-body-bg);
|
|
||||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
|
||||||
border-radius: var(--bs-border-radius);
|
|
||||||
padding: 0.125rem 0.75rem;
|
|
||||||
font-size: ($font-size-sm + $font-size-base) / 2;
|
|
||||||
font-weight: var(--bs-font-weight-normal);
|
|
||||||
line-height: 1.25;
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 75%;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fieldset-description {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gap between group label and first inline check.
|
|
||||||
.form-label + .form-check-inline {
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Footer region.
|
|
||||||
.region-footer {
|
|
||||||
padding: 0.75rem 0 3rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
// Bootstrap Icons v1.13.1, fonts served from /bootsier/fonts.
|
|
||||||
|
|
||||||
$bootstrap-icons-font: "bootsier.icons";
|
|
||||||
$bootstrap-icons-font-dir: "/bootsier/fonts";
|
|
||||||
@import "bootstrap-icons-1.13.1/bootstrap-icons";
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
// Bootsier overrides for Bootstrap variables. Imported before bootstrap/scss/variables
|
|
||||||
// so that Bootstrap's !default declarations do not override these values.
|
|
||||||
|
|
||||||
$font-size-base: 1.125rem;
|
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,337 +0,0 @@
|
||||||
/* ==========================================================================
|
|
||||||
AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/* Skip Links - WCAG 2.4.1: Bypass Blocks */
|
|
||||||
.skip-link {
|
|
||||||
position: absolute;
|
|
||||||
top: -40px;
|
|
||||||
left: 6px;
|
|
||||||
z-index: 999999;
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--bs-white);
|
|
||||||
text-decoration: none;
|
|
||||||
background: var(--bs-primary);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
top: 0;
|
|
||||||
outline: 3px solid var(--bs-warning);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--bs-white);
|
|
||||||
text-decoration: none;
|
|
||||||
background: var(--bs-primary-emphasis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enhanced Focus Indicators - WCAG 2.4.7: Focus Visible */
|
|
||||||
.focus-enhanced {
|
|
||||||
&:focus {
|
|
||||||
outline: 3px solid var(--bs-focus-ring-color, #0d6efd);
|
|
||||||
outline-offset: 2px;
|
|
||||||
box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* High Contrast Mode Support */
|
|
||||||
@media (prefers-contrast: high) {
|
|
||||||
.card {
|
|
||||||
border: 2px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
border-color: currentcolor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reduced Motion Support - WCAG 2.3.3: Animation from Interactions */
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
transition-duration: .01ms !important;
|
|
||||||
animation-duration: .01ms !important;
|
|
||||||
animation-iteration-count: 1 !important;
|
|
||||||
scroll-behavior: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade {
|
|
||||||
opacity: 1 !important;
|
|
||||||
/* stylelint-disable-next-line property-disallowed-list */
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse {
|
|
||||||
/* stylelint-disable-next-line property-disallowed-list */
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.fade .modal-dialog {
|
|
||||||
transform: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Screen Reader Only Content */
|
|
||||||
.sr-only {
|
|
||||||
position: absolute !important;
|
|
||||||
width: 1px !important;
|
|
||||||
height: 1px !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: -1px !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
clip: rect(0, 0, 0, 0) !important;
|
|
||||||
white-space: nowrap !important;
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only-focusable:focus {
|
|
||||||
position: static !important;
|
|
||||||
width: auto !important;
|
|
||||||
height: auto !important;
|
|
||||||
padding: inherit !important;
|
|
||||||
margin: inherit !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
clip: auto !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Focus Trap Utilities */
|
|
||||||
.focus-trap {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Accessible Color Combinations - WCAG 1.4.3: Contrast (Minimum) */
|
|
||||||
.text-accessible-primary {
|
|
||||||
color: #003d82; /* 4.5:1 contrast on white */
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-accessible-success {
|
|
||||||
color: #0f5132; /* 4.5:1 contrast on white */
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-accessible-danger {
|
|
||||||
color: #842029; /* 4.5:1 contrast on white */
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-accessible-warning {
|
|
||||||
color: #664d03; /* 4.5:1 contrast on white */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ARIA Live Regions */
|
|
||||||
.live-region {
|
|
||||||
position: absolute;
|
|
||||||
left: -10000px;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.live-region-visible {
|
|
||||||
position: static;
|
|
||||||
left: auto;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enhanced Error States - WCAG 3.3.1: Error Identification */
|
|
||||||
.form-control.is-invalid {
|
|
||||||
border-color: var(--bs-danger);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: var(--bs-danger);
|
|
||||||
box-shadow: 0 0 0 .25rem rgba(220, 53, 69, .25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.invalid-feedback {
|
|
||||||
&[role="alert"] {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Target Size - WCAG 2.5.8: Target Size (Minimum) */
|
|
||||||
.touch-target {
|
|
||||||
min-width: 44px;
|
|
||||||
min-height: 44px;
|
|
||||||
|
|
||||||
&.touch-target-small {
|
|
||||||
min-width: 24px;
|
|
||||||
min-height: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table Accessibility */
|
|
||||||
.table-accessible {
|
|
||||||
th {
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: var(--bs-secondary-bg);
|
|
||||||
|
|
||||||
&[scope="col"] {
|
|
||||||
border-bottom: 2px solid var(--bs-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[scope="row"] {
|
|
||||||
border-right: 2px solid var(--bs-border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
caption {
|
|
||||||
padding: .75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--bs-secondary);
|
|
||||||
text-align: left;
|
|
||||||
caption-side: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation Landmarks */
|
|
||||||
nav[role="navigation"] {
|
|
||||||
&:not([aria-label]):not([aria-labelledby]) {
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
left: -10000px;
|
|
||||||
content: "Navigation";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Fieldset Styling */
|
|
||||||
fieldset {
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 1px solid var(--bs-border-color);
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0 .5rem;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading States */
|
|
||||||
.loading[aria-busy="true"] {
|
|
||||||
position: relative;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin-top: -10px;
|
|
||||||
margin-left: -10px;
|
|
||||||
content: "";
|
|
||||||
border: 2px solid var(--bs-primary);
|
|
||||||
border-top-color: transparent;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
&::after {
|
|
||||||
border-top-color: var(--bs-primary);
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark Mode Accessibility */
|
|
||||||
[data-bs-theme="dark"] {
|
|
||||||
.text-accessible-primary {
|
|
||||||
color: #6ea8fe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-accessible-success {
|
|
||||||
color: #75b798;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-accessible-danger {
|
|
||||||
color: #f1aeb5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-accessible-warning {
|
|
||||||
color: #ffda6a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print Accessibility */
|
|
||||||
@media print {
|
|
||||||
.skip-link,
|
|
||||||
.btn,
|
|
||||||
.nav-link {
|
|
||||||
color: #000 !important;
|
|
||||||
background: transparent !important;
|
|
||||||
border: 1px solid #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a[href^="http"]::after {
|
|
||||||
font-size: .8em;
|
|
||||||
content: " (" attr(href) ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print Layout Fix - Ensure sidebar and main content are both visible */
|
|
||||||
.app-wrapper {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-rows: auto 1fr auto !important;
|
|
||||||
grid-template-columns: auto 1fr !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-overlay {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-sidebar {
|
|
||||||
position: static !important;
|
|
||||||
display: block !important;
|
|
||||||
min-width: 200px !important;
|
|
||||||
max-width: 200px !important;
|
|
||||||
max-height: none !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
height: auto !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-header {
|
|
||||||
position: static !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-main {
|
|
||||||
width: auto !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-content {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-footer {
|
|
||||||
position: static !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.app-content {
|
|
||||||
padding: $lte-content-padding-y $lte-content-padding-x;
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// Core: Main Footer
|
|
||||||
//
|
|
||||||
|
|
||||||
.app-footer {
|
|
||||||
grid-area: #{$lte-prefix}app-footer;
|
|
||||||
width: inherit;
|
|
||||||
max-width: 100vw;
|
|
||||||
min-height: 3rem;
|
|
||||||
padding: $lte-app-footer-padding;
|
|
||||||
color: $lte-app-footer-color;
|
|
||||||
background-color: $lte-app-footer-bg;
|
|
||||||
border-top: $lte-app-footer-border-top;
|
|
||||||
@include transition($lte-transition-speed $lte-transition-fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-footer {
|
|
||||||
.app-footer {
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: $lte-zindex-fixed-footer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When layout-fixed is used, app-main has overflow: auto which prevents
|
|
||||||
// position: sticky from working on the footer. Ensure the grid keeps the
|
|
||||||
// footer pinned at the bottom while app-main scrolls independently.
|
|
||||||
&.layout-fixed {
|
|
||||||
.app-main {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
//
|
|
||||||
// Core: Main Header
|
|
||||||
//
|
|
||||||
|
|
||||||
.app-header {
|
|
||||||
z-index: $lte-zindex-app-header;
|
|
||||||
grid-area: #{$lte-prefix}app-header;
|
|
||||||
max-width: 100vw;
|
|
||||||
border-bottom: $lte-app-header-bottom-border;
|
|
||||||
@include transition($lte-transition-speed $lte-transition-fn);
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
position: relative;
|
|
||||||
height: $nav-link-height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navbar badge
|
|
||||||
.navbar-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 9px;
|
|
||||||
right: 5px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
font-size: .6rem;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
.app-header {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: $lte-zindex-fixed-header;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixes #6020: when only `fixed-header` is used (without `layout-fixed`),
|
|
||||||
// the sidebar — including its branding — used to scroll with the page
|
|
||||||
// because nothing made it sticky. Pin the sidebar so the brand and menu
|
|
||||||
// stay visible while the page content scrolls beneath the fixed header.
|
|
||||||
// Only applied on `sidebar-expand-*` breakpoints, since the mobile sidebar
|
|
||||||
// is an off-canvas overlay that handles its own positioning.
|
|
||||||
@each $breakpoint in map-keys($grid-breakpoints) {
|
|
||||||
$next: breakpoint-next($breakpoint, $grid-breakpoints);
|
|
||||||
$infix: breakpoint-infix($next, $grid-breakpoints);
|
|
||||||
|
|
||||||
&.sidebar-expand#{$infix} {
|
|
||||||
@include media-breakpoint-up($next) {
|
|
||||||
.app-sidebar {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
max-height: 100vh;
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
height: subtract(100vh, add($lte-app-header-height, 1px));
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
.app-main {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
grid-area: #{$lte-prefix}app-main;
|
|
||||||
max-width: 100vw;
|
|
||||||
padding-bottom: $lte-app-main-padding-bottom;
|
|
||||||
@include transition($lte-transition-speed $lte-transition-fn);
|
|
||||||
|
|
||||||
.app-content-header {
|
|
||||||
padding: 1rem $lte-content-padding-x;
|
|
||||||
|
|
||||||
.breadcrumb {
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-content-top-area,
|
|
||||||
.app-content-bottom-area {
|
|
||||||
color: $lte-app-content-bottom-area-color;
|
|
||||||
background-color: $lte-app-content-bottom-area-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-content-top-area {
|
|
||||||
padding: $lte-app-content-top-area-padding-y $lte-app-content-top-area-padding-x;
|
|
||||||
border-bottom: $lte-app-content-top-area-top-border;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-content-bottom-area {
|
|
||||||
padding: $lte-app-content-bottom-area-padding-y $lte-app-content-bottom-area-padding-x;
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: $lte-app-content-bottom-area-margin-bottom;
|
|
||||||
border-top: $lte-app-content-bottom-area-top-border;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,619 +0,0 @@
|
||||||
.app-sidebar {
|
|
||||||
--#{$lte-prefix}sidebar-hover-bg: #{$lte-sidebar-hover-bg};
|
|
||||||
--#{$lte-prefix}sidebar-color: #{$lte-sidebar-color};
|
|
||||||
--#{$lte-prefix}sidebar-hover-color: #{$lte-sidebar-hover-color};
|
|
||||||
--#{$lte-prefix}sidebar-active-color: #{$lte-sidebar-active-color};
|
|
||||||
--#{$lte-prefix}sidebar-menu-active-bg: #{$lte-sidebar-menu-active-bg};
|
|
||||||
--#{$lte-prefix}sidebar-menu-active-color: #{$lte-sidebar-menu-active-color};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-bg: #{$lte-sidebar-submenu-bg};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-color: #{$lte-sidebar-submenu-color};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-hover-color: #{$lte-sidebar-submenu-hover-color};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-hover-bg: #{$lte-sidebar-submenu-hover-bg};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-active-color: #{$lte-sidebar-submenu-active-color};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-active-bg: #{$lte-sidebar-submenu-active-bg};
|
|
||||||
--#{$lte-prefix}sidebar-header-color: #{$lte-sidebar-header-color};
|
|
||||||
|
|
||||||
z-index: $lte-zindex-sidebar;
|
|
||||||
grid-area: #{$lte-prefix}app-sidebar;
|
|
||||||
min-width: var(--#{$lte-prefix}sidebar-width);
|
|
||||||
max-width: var(--#{$lte-prefix}sidebar-width);
|
|
||||||
@include transition($lte-sidebar-transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-brand {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: $lte-app-header-height;
|
|
||||||
padding: $lte-brand-link-padding-y $lte-brand-link-padding-x;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: $navbar-brand-font-size;
|
|
||||||
white-space: nowrap;
|
|
||||||
border-bottom: $lte-brand-link-border-buttom solid var(--#{$prefix}border-color);
|
|
||||||
@include transition(width $lte-transition-speed $lte-transition-fn);
|
|
||||||
|
|
||||||
.brand-link {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
.brand-image {
|
|
||||||
float: left;
|
|
||||||
width: auto;
|
|
||||||
max-height: 33px;
|
|
||||||
line-height: .8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-image-xs {
|
|
||||||
float: left;
|
|
||||||
width: auto;
|
|
||||||
max-height: 33px;
|
|
||||||
margin-top: -.1rem;
|
|
||||||
line-height: .8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-image-xl {
|
|
||||||
width: auto;
|
|
||||||
max-height: 40px;
|
|
||||||
line-height: .8;
|
|
||||||
|
|
||||||
&.single {
|
|
||||||
margin-top: -.3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-text {
|
|
||||||
margin-left: .5rem;
|
|
||||||
color: rgba(var(--#{$prefix}emphasis-color-rgb), .8);
|
|
||||||
@include transition(flex $lte-transition-speed $lte-transition-fn, width $lte-transition-speed $lte-transition-fn);
|
|
||||||
&:hover {
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
padding-top: $lte-sidebar-padding-y;
|
|
||||||
padding-right: $lte-sidebar-padding-x;
|
|
||||||
padding-bottom: $lte-sidebar-padding-y;
|
|
||||||
padding-left: $lte-sidebar-padding-x;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
@include scrollbar-color-gray();
|
|
||||||
@include scrollbar-width-thin();
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
p {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.nav-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 1.5rem;
|
|
||||||
max-width: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sidebar Menu. First level links
|
|
||||||
.sidebar-menu > .nav-item {
|
|
||||||
// links
|
|
||||||
> .nav-link {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-color);
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-hover-color);
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-hover-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-menu-active-color);
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-menu-active-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open state
|
|
||||||
&.menu-open > .nav-link {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-hover-color);
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-hover-bg);
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-menu-active-color);
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-menu-active-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First Level Submenu
|
|
||||||
> .nav-treeview {
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-submenu-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section Heading
|
|
||||||
.nav-header {
|
|
||||||
display: inline;
|
|
||||||
overflow: hidden;
|
|
||||||
color: var(--#{$lte-prefix}sidebar-header-color);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All links within the sidebar menu
|
|
||||||
a {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All submenus
|
|
||||||
.nav-treeview {
|
|
||||||
> .nav-item {
|
|
||||||
> .nav-link {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-submenu-color);
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-submenu-hover-color);
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-submenu-hover-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--#{$lte-prefix}sidebar-submenu-active-color);
|
|
||||||
background-color: var(--#{$lte-prefix}sidebar-submenu-active-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sidebar navigation menu
|
|
||||||
.sidebar-menu {
|
|
||||||
// All levels
|
|
||||||
.nav-item {
|
|
||||||
> .nav-link {
|
|
||||||
margin-bottom: .2rem;
|
|
||||||
|
|
||||||
.nav-arrow {
|
|
||||||
@include transition(transform $lte-transition-fn $lte-transition-speed);
|
|
||||||
transform: translateY(-50%) #{"/*rtl:append:rotate(180deg)*/"};
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All levels
|
|
||||||
.nav-link > .nav-badge,
|
|
||||||
.nav-link > p > .nav-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 1rem;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link > .nav-arrow,
|
|
||||||
.nav-link > p > .nav-arrow {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
@include transition(width $lte-transition-fn $lte-transition-speed);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: inline;
|
|
||||||
padding-left: .5rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-header {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding: $nav-link-padding-y ($nav-link-padding-y * 1.5);
|
|
||||||
font-size: .9rem;
|
|
||||||
@include transition(width $lte-transition-fn $lte-transition-speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree view menu
|
|
||||||
.nav-treeview {
|
|
||||||
display: none;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-open {
|
|
||||||
> .nav-treeview {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .nav-link {
|
|
||||||
.nav-arrow {
|
|
||||||
transform: translateY(-50%) rotate(90deg) #{"/*rtl:ignore*/"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override: ensure badges/arrows are aligned to the end (right in LTR, left in RTL)
|
|
||||||
.nav-link > .nav-badge,
|
|
||||||
.nav-link > p > .nav-badge,
|
|
||||||
.nav-link > .nav-arrow,
|
|
||||||
.nav-link > p > .nav-arrow {
|
|
||||||
right: 1rem !important; // place at end in LTR
|
|
||||||
left: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-indent {
|
|
||||||
.nav-treeview {
|
|
||||||
padding-left: $lte-sidebar-padding-x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-compact.nav-indent {
|
|
||||||
.nav-treeview {
|
|
||||||
padding-left: 0;
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
padding-left: $lte-sidebar-padding-x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-mini.sidebar-collapse.nav-indent {
|
|
||||||
.app-sidebar:hover {
|
|
||||||
.nav-treeview {
|
|
||||||
padding-left: 0;
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
padding-left: $lte-sidebar-padding-x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-collapse {
|
|
||||||
&.nav-compact.nav-indent {
|
|
||||||
.nav-treeview {
|
|
||||||
.nav-item {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-compact .nav-link {
|
|
||||||
@include border-radius(0);
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A fix for text overflow while transitioning from sidebar mini to full sidebar
|
|
||||||
.sidebar-menu,
|
|
||||||
.sidebar-menu > .nav-header,
|
|
||||||
.sidebar-menu .nav-link {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logo style
|
|
||||||
.logo-xs,
|
|
||||||
.logo-xl {
|
|
||||||
position: absolute;
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
&.brand-image-xs {
|
|
||||||
top: 12px;
|
|
||||||
left: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.brand-image-xl {
|
|
||||||
top: 6px;
|
|
||||||
left: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-xs {
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
&.brand-image-xl {
|
|
||||||
top: 8px;
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-link {
|
|
||||||
&.logo-switch {
|
|
||||||
&::before {
|
|
||||||
content: "\00a0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-mini.sidebar-collapse {
|
|
||||||
.app-sidebar {
|
|
||||||
min-width: $lte-sidebar-mini-width;
|
|
||||||
max-width: $lte-sidebar-mini-width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the sidebar headers
|
|
||||||
.sidebar-menu .nav-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-menu {
|
|
||||||
.nav-link {
|
|
||||||
width: $lte-sidebar-mini-width - $lte-sidebar-padding-x * 2;
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: inline-block;
|
|
||||||
width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.nav-badge,
|
|
||||||
.nav-arrow {
|
|
||||||
display: none;
|
|
||||||
animation-name: fadeOut;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-text {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-menu .nav-link p,
|
|
||||||
.brand-text,
|
|
||||||
.logo-xl,
|
|
||||||
.nav-arrow {
|
|
||||||
visibility: hidden;
|
|
||||||
animation-name: fadeOut;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.logo-xs {
|
|
||||||
display: inline-block;
|
|
||||||
visibility: visible;
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.sidebar-without-hover) {
|
|
||||||
.app-sidebar:hover {
|
|
||||||
min-width: var(--#{$lte-prefix}sidebar-width);
|
|
||||||
max-width: var(--#{$lte-prefix}sidebar-width);
|
|
||||||
|
|
||||||
.sidebar-menu .nav-header {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-menu .nav-link {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-menu .nav-link p,
|
|
||||||
.brand-text,
|
|
||||||
.logo-xl {
|
|
||||||
width: auto;
|
|
||||||
margin-left: 0;
|
|
||||||
visibility: visible;
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-text {
|
|
||||||
display: inline;
|
|
||||||
max-width: inherit;
|
|
||||||
margin-left: .5rem;
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-badge,
|
|
||||||
.nav-arrow {
|
|
||||||
display: inline-block;
|
|
||||||
visibility: visible;
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
animation-delay: $lte-transition-speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link p {
|
|
||||||
padding-left: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-xs {
|
|
||||||
visibility: hidden;
|
|
||||||
animation-name: fadeOut;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-collapse:not(.sidebar-mini) {
|
|
||||||
.app-sidebar {
|
|
||||||
margin-left: calc(var(--#{$lte-prefix}sidebar-width) * -1); // stylelint-disable-line function-disallowed-list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-expand {
|
|
||||||
@each $breakpoint in map-keys($grid-breakpoints) {
|
|
||||||
$next: breakpoint-next($breakpoint, $grid-breakpoints);
|
|
||||||
$infix: breakpoint-infix($next, $grid-breakpoints);
|
|
||||||
|
|
||||||
/* stylelint-disable-next-line scss/selector-no-union-class-name */
|
|
||||||
&#{$infix} {
|
|
||||||
@include media-breakpoint-up($next) {
|
|
||||||
&.layout-fixed {
|
|
||||||
.app-main-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.app-sidebar-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.app-main {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.app-sidebar {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
max-height: 100vh;
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
height: subtract(100vh, add($lte-app-header-height, 1px));
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sidebar-open {
|
|
||||||
.nav-link > .nav-badge,
|
|
||||||
.nav-link > p > .nav-badge {
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
animation-delay: $lte-transition-speed;
|
|
||||||
}
|
|
||||||
.nav-link > .nav-arrow,
|
|
||||||
.nav-link > p > .nav-arrow {
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
animation-delay: $lte-transition-speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down($next) {
|
|
||||||
$max: breakpoint-max($next);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
display: none;
|
|
||||||
content: "#{$max}";
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-sidebar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
max-height: 100vh;
|
|
||||||
margin-left: calc(var(--#{$lte-prefix}sidebar-width) * -1); // stylelint-disable-line function-disallowed-list
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
height: subtract(100vh, add($lte-app-header-height, 1px));
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sidebar-open {
|
|
||||||
.app-sidebar {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-overlay {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: $lte-zindex-sidebar-overlay;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
visibility: visible;
|
|
||||||
background-color: rgba(0, 0, 0, .2);
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-menu .nav-link p,
|
|
||||||
.app-sidebar .brand-text,
|
|
||||||
.app-sidebar .logo-xs,
|
|
||||||
.app-sidebar .logo-xl {
|
|
||||||
@include transition(margin-left $lte-transition-speed linear, opacity $lte-transition-speed ease, visibility $lte-transition-speed ease);
|
|
||||||
}
|
|
||||||
|
|
||||||
// To prevent onload transition and animation
|
|
||||||
.app-loaded {
|
|
||||||
&.sidebar-mini.sidebar-collapse {
|
|
||||||
.sidebar-menu .nav-link p,
|
|
||||||
.brand-text {
|
|
||||||
animation-duration: $lte-transition-speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body:not(.app-loaded) {
|
|
||||||
.app-header,
|
|
||||||
.app-sidebar,
|
|
||||||
.app-main,
|
|
||||||
.app-footer {
|
|
||||||
@include transition(none !important);
|
|
||||||
animation-duration: 0s !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hold-transition {
|
|
||||||
.app-header,
|
|
||||||
.app-sidebar,
|
|
||||||
.app-main,
|
|
||||||
.app-footer,
|
|
||||||
.nav-arrow,
|
|
||||||
.nav-badge {
|
|
||||||
@include transition(none !important);
|
|
||||||
animation-duration: 0s !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if $enable-dark-mode {
|
|
||||||
@include color-mode(dark) {
|
|
||||||
&.app-sidebar,
|
|
||||||
.app-sidebar {
|
|
||||||
--#{$lte-prefix}sidebar-hover-bg: #{$lte-sidebar-hover-bg-dark};
|
|
||||||
--#{$lte-prefix}sidebar-color: #{$lte-sidebar-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-hover-color: #{$lte-sidebar-hover-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-active-color: #{$lte-sidebar-active-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-menu-active-bg: #{$lte-sidebar-menu-active-bg-dark};
|
|
||||||
--#{$lte-prefix}sidebar-menu-active-color: #{$lte-sidebar-menu-active-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-bg: #{$lte-sidebar-submenu-bg-dark};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-color: #{$lte-sidebar-submenu-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-hover-color: #{$lte-sidebar-submenu-hover-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-hover-bg: #{$lte-sidebar-submenu-hover-bg-dark};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-active-color: #{$lte-sidebar-submenu-active-color-dark};
|
|
||||||
--#{$lte-prefix}sidebar-submenu-active-bg: #{$lte-sidebar-submenu-active-bg-dark};
|
|
||||||
--#{$lte-prefix}sidebar-header-color: #{$lte-sidebar-header-color-dark};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
//
|
|
||||||
// Core: Layout
|
|
||||||
//
|
|
||||||
|
|
||||||
.app-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas:
|
|
||||||
"#{$lte-prefix}app-sidebar #{$lte-prefix}app-header"
|
|
||||||
"#{$lte-prefix}app-sidebar #{$lte-prefix}app-main"
|
|
||||||
"#{$lte-prefix}app-sidebar #{$lte-prefix}app-footer";
|
|
||||||
grid-template-rows: min-content 1fr min-content;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
grid-gap: 0;
|
|
||||||
align-content: stretch;
|
|
||||||
align-items: stretch;
|
|
||||||
max-width: 100vw;
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// Callouts
|
|
||||||
//
|
|
||||||
|
|
||||||
.callout {
|
|
||||||
--#{$prefix}link-color-rgb: var(--#{$lte-prefix}callout-link);
|
|
||||||
--#{$prefix}code-color: var(--#{$lte-prefix}callout-code-color);
|
|
||||||
|
|
||||||
padding: 1.25rem;
|
|
||||||
color: var(--#{$lte-prefix}callout-color, inherit);
|
|
||||||
background-color: var(--#{$lte-prefix}callout-bg, var(--bs-gray-100));
|
|
||||||
border-left: .25rem solid var(--#{$lte-prefix}callout-border, var(--bs-gray-300));
|
|
||||||
|
|
||||||
.callout-link {
|
|
||||||
font-weight: $lte-callout-link-font-weight;
|
|
||||||
color: var(--#{$prefix}callout-link-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-bottom: .25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ .callout {
|
|
||||||
margin-top: -.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variations
|
|
||||||
@each $name, $color in $theme-colors {
|
|
||||||
.callout-#{$name} {
|
|
||||||
--#{$lte-prefix}callout-color: var(--#{$prefix}#{$name}-text-emphasis);
|
|
||||||
--#{$lte-prefix}callout-bg: var(--#{$prefix}#{$name}-bg-subtle);
|
|
||||||
--#{$lte-prefix}callout-border: var(--#{$prefix}#{$name}-border-subtle);
|
|
||||||
--#{$prefix}callout-link-color: var(--#{$prefix}#{$name}-text-emphasis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,311 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Cards
|
|
||||||
//
|
|
||||||
|
|
||||||
// Color variants
|
|
||||||
.card {
|
|
||||||
@include box-shadow($lte-card-shadow);
|
|
||||||
|
|
||||||
&[class*="card-"]:not(.card-outline),
|
|
||||||
&[class*="text-bg-"]:not(.card-outline) {
|
|
||||||
> .card-header {
|
|
||||||
color: var(--#{$lte-prefix}card-variant-color);
|
|
||||||
background-color: var(--#{$lte-prefix}card-variant-bg);
|
|
||||||
|
|
||||||
.btn-tool {
|
|
||||||
--#{$prefix}btn-color: rgba(var(--#{$lte-prefix}card-variant-color-rgb), .8);
|
|
||||||
--#{$prefix}btn-hover-color: var(--#{$lte-prefix}card-variant-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.card-outline {
|
|
||||||
border-top: 3px solid var(--#{$lte-prefix}card-variant-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.maximized-card {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: $zindex-modal-backdrop;
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
height: 100% !important;
|
|
||||||
max-height: 100% !important;
|
|
||||||
|
|
||||||
&.was-collapsed .card-body {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-lte-toggle="card-collapse"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-lte-icon="maximize"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header,
|
|
||||||
.card-footer {
|
|
||||||
@include border-radius(0 !important);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.maximized-card) {
|
|
||||||
[data-lte-icon="minimize"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// collapsed mode
|
|
||||||
&.collapsed-card {
|
|
||||||
// Use > .card-header to scope to direct card only, not nested cards
|
|
||||||
> .card-header [data-lte-icon="collapse"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .card-body,
|
|
||||||
> .card-footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.collapsed-card) {
|
|
||||||
// Use > .card-header to scope to direct card only, not nested cards
|
|
||||||
> .card-header [data-lte-icon="expand"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.nav.flex-column {
|
|
||||||
> li {
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: 1px solid $card-border-color;
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixed height to 300px
|
|
||||||
&.height-control {
|
|
||||||
.card-body {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-end {
|
|
||||||
border-right: 1px solid $card-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-start {
|
|
||||||
border-left: 1px solid $card-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.card-tabs {
|
|
||||||
&:not(.card-outline) {
|
|
||||||
> .card-header {
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
&:first-child .nav-link {
|
|
||||||
border-left-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.card-outline {
|
|
||||||
.nav-item {
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
&:first-child .nav-link {
|
|
||||||
margin-left: 0;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-tools {
|
|
||||||
margin: .3rem .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.expanding-card).collapsed-card {
|
|
||||||
.card-header {
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
.nav-tabs {
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expanding-card {
|
|
||||||
.card-header {
|
|
||||||
.nav-tabs {
|
|
||||||
.nav-item {
|
|
||||||
margin-bottom: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.card-outline-tabs {
|
|
||||||
border-top: 0;
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
.nav-item {
|
|
||||||
&:first-child .nav-link {
|
|
||||||
margin-left: 0;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
border-top: 3px solid transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-top: 3px solid $nav-tabs-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
&:hover {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-tools {
|
|
||||||
margin: .5rem .5rem .3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.expanding-card).collapsed-card .card-header {
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
.nav-tabs {
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expanding-card {
|
|
||||||
.card-header {
|
|
||||||
.nav-tabs {
|
|
||||||
.nav-item {
|
|
||||||
margin-bottom: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximized Card Body Scroll fix
|
|
||||||
html.maximized-card {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add clearfix to header, body and footer
|
|
||||||
.card-header,
|
|
||||||
.card-body,
|
|
||||||
.card-footer {
|
|
||||||
@include clearfix();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box header
|
|
||||||
.card-header {
|
|
||||||
position: relative;
|
|
||||||
padding: (($card-spacer-y * .5) * 2) $card-spacer-x;
|
|
||||||
background-color: transparent;
|
|
||||||
border-bottom: 1px solid $card-border-color;
|
|
||||||
|
|
||||||
@if $enable-rounded {
|
|
||||||
@include border-top-radius($border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsed-card & {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .card-tools {
|
|
||||||
float: right;
|
|
||||||
margin-right: -$card-spacer-x * .5;
|
|
||||||
|
|
||||||
.input-group,
|
|
||||||
.nav,
|
|
||||||
.pagination {
|
|
||||||
margin-top: -$card-spacer-y * .4;
|
|
||||||
margin-bottom: -$card-spacer-y * .4;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-bs-toggle="tooltip"] {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
float: left;
|
|
||||||
margin: 0;
|
|
||||||
font-size: $lte-card-title-font-size;
|
|
||||||
font-weight: $lte-card-title-font-weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box Tools Buttons
|
|
||||||
.btn-tool {
|
|
||||||
--#{$prefix}btn-padding-x: .5rem;
|
|
||||||
--#{$prefix}btn-padding-y: .25rem;
|
|
||||||
|
|
||||||
&:not(.btn-tool-custom) {
|
|
||||||
--#{$prefix}btn-color: var(--#{$prefix}tertiary-color);
|
|
||||||
--#{$prefix}btn-bg: transparent;
|
|
||||||
--#{$prefix}btn-box-shadow: none;
|
|
||||||
--#{$prefix}btn-hover-color: var(--#{$prefix}secondary-color);
|
|
||||||
--#{$prefix}btn-active-border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin: -$card-spacer-y 0;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@each $name, $color in $theme-colors {
|
|
||||||
.card-#{$name},
|
|
||||||
.bg-#{$name},
|
|
||||||
.text-bg-#{$name} {
|
|
||||||
--#{$lte-prefix}card-variant-bg: #{$color};
|
|
||||||
--#{$lte-prefix}card-variant-bg-rgb: #{to-rgb($color)};
|
|
||||||
--#{$lte-prefix}card-variant-color: #{color-contrast($color)};
|
|
||||||
--#{$lte-prefix}card-variant-color-rgb: #{to-rgb(color-contrast($color))};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box Body
|
|
||||||
.card-body {
|
|
||||||
// Tables within the box body
|
|
||||||
> .table {
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
> thead > tr > th,
|
|
||||||
> thead > tr > td {
|
|
||||||
border-top-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
.compact-mode {
|
|
||||||
.app-header {
|
|
||||||
max-height: $lte-app-header-height-compact;
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
max-height: $nav-link-height-compact;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
--bs-nav-link-padding-y: .25rem;
|
|
||||||
--bs-nav-link-padding-x: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sidebar-mini.sidebar-collapse {
|
|
||||||
.app-sidebar:not(:hover) {
|
|
||||||
min-width: $lte-sidebar-mini-width-compact;
|
|
||||||
max-width: $lte-sidebar-mini-width-compact;
|
|
||||||
|
|
||||||
.sidebar-menu {
|
|
||||||
.nav-link {
|
|
||||||
width: $lte-sidebar-mini-width-compact - $lte-sidebar-padding-x * 2 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-xs,
|
|
||||||
.logo-xl {
|
|
||||||
max-height: $lte-app-header-height-compact;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-image {
|
|
||||||
width: $nav-link-height-compact;
|
|
||||||
height: $nav-link-height-compact;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-brand {
|
|
||||||
height: $lte-app-header-height-compact;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-footer {
|
|
||||||
padding: $lte-app-footer-padding-compact;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
.nav-icon {
|
|
||||||
min-width: 1.1rem;
|
|
||||||
max-width: 1.1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Direct Chat
|
|
||||||
//
|
|
||||||
|
|
||||||
.direct-chat {
|
|
||||||
.card-body {
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.chat-pane-open {
|
|
||||||
.direct-chat-contacts {
|
|
||||||
transform: translate(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
&.timestamp-light {
|
|
||||||
.direct-chat-timestamp {
|
|
||||||
color: rgba(var(--#{$prefix}body-color-rgb), .65);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.timestamp-dark {
|
|
||||||
.direct-chat-timestamp {
|
|
||||||
color: rgba(var(--#{$prefix}body-color-rgb), .9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-messages {
|
|
||||||
height: 250px;
|
|
||||||
padding: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
transform: translate(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-msg,
|
|
||||||
.direct-chat-text {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-msg {
|
|
||||||
@include clearfix();
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-messages,
|
|
||||||
.direct-chat-contacts {
|
|
||||||
@include transition(transform .5s ease-in-out);
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-text {
|
|
||||||
@if $enable-rounded {
|
|
||||||
@include border-radius($border-radius-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
padding: 5px 10px;
|
|
||||||
margin: 5px 0 0 50px;
|
|
||||||
color: $lte-direct-chat-default-font-color;
|
|
||||||
background-color: $lte-direct-chat-default-msg-bg;
|
|
||||||
border: 1px solid $lte-direct-chat-default-msg-border-color;
|
|
||||||
|
|
||||||
//Create the arrow
|
|
||||||
&::after,
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 100%;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
content: " ";
|
|
||||||
border: solid transparent;
|
|
||||||
border-right-color: $lte-direct-chat-default-msg-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
margin-top: -5px;
|
|
||||||
border-width: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
margin-top: -6px;
|
|
||||||
border-width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.end & {
|
|
||||||
margin-right: 50px;
|
|
||||||
margin-left: 0;
|
|
||||||
|
|
||||||
&::after,
|
|
||||||
&::before {
|
|
||||||
right: auto;
|
|
||||||
left: 100%;
|
|
||||||
border-right-color: transparent;
|
|
||||||
border-left-color: $lte-direct-chat-default-msg-border-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-img {
|
|
||||||
@include border-radius(50%);
|
|
||||||
float: left;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
.end & {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-infos {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-timestamp {
|
|
||||||
color: rgba(var(--#{$prefix}body-color-rgb), .75);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Direct chat contacts pane
|
|
||||||
.direct-chat-contacts-open {
|
|
||||||
.direct-chat-contacts {
|
|
||||||
transform: translate(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-contacts {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 250px;
|
|
||||||
overflow: auto;
|
|
||||||
color: var(--#{$prefix}body-bg);
|
|
||||||
background-color: var(--#{$prefix}body-color);
|
|
||||||
transform: translate(101%, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.direct-chat-contacts-light {
|
|
||||||
background-color: var(--#{$prefix}light-bg-subtle);
|
|
||||||
|
|
||||||
.contacts-list-name {
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-date {
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-msg {
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Contacts list -- for displaying contacts in direct chat contacts pane
|
|
||||||
.contacts-list {
|
|
||||||
@include list-unstyled();
|
|
||||||
|
|
||||||
> li {
|
|
||||||
@include clearfix();
|
|
||||||
padding: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px solid rgba($black, .2);
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-img {
|
|
||||||
@include border-radius(50%);
|
|
||||||
float: left;
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-info {
|
|
||||||
margin-left: 45px;
|
|
||||||
color: var(--#{$prefix}body-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-name,
|
|
||||||
.contacts-list-status {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-status {
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-date {
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--#{$prefix}secondary-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contacts-list-msg {
|
|
||||||
color: var(--#{$prefix}secondary-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.end > .direct-chat-text {
|
|
||||||
color: var(--#{$lte-prefix}direct-chat-color);
|
|
||||||
background-color: var(--#{$lte-prefix}direct-chat-bg);
|
|
||||||
border-color: var(--#{$lte-prefix}direct-chat-bg);
|
|
||||||
|
|
||||||
&::after,
|
|
||||||
&::before {
|
|
||||||
border-left-color: var(--#{$lte-prefix}direct-chat-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color variants
|
|
||||||
@each $name, $color in $theme-colors {
|
|
||||||
.direct-chat-#{$name} {
|
|
||||||
--#{$lte-prefix}direct-chat-color: #{color-contrast($color)};
|
|
||||||
--#{$lte-prefix}direct-chat-bg: #{$color};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,820 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Docs
|
|
||||||
//
|
|
||||||
// Visual treatment for documentation pages. Body class `.docs-page` opts in.
|
|
||||||
// Demo pages are not affected.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Inline code blocks rendered by MDX (Astro highlight default class)
|
|
||||||
.astro-code {
|
|
||||||
padding: .75rem;
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.docs-page {
|
|
||||||
// Constrain the docs card to a comfortable reading width.
|
|
||||||
// Tables and code blocks inside can still scroll horizontally if needed.
|
|
||||||
.app-content > .container-fluid {
|
|
||||||
max-width: 60rem; // ~960px
|
|
||||||
|
|
||||||
@include media-breakpoint-up(xxl) {
|
|
||||||
max-width: 64rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Card body — breathing room + prose typography.
|
|
||||||
// Only applied inside docs content cards so demo-page card content
|
|
||||||
// stays unchanged.
|
|
||||||
//
|
|
||||||
.card > .card-body {
|
|
||||||
padding: 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
|
||||||
padding: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Heading hierarchy. The MDX content in src/html/components/docs/ uses
|
|
||||||
// ##### (h5) as the dominant section heading and ###### (h6) as a
|
|
||||||
// sub-heading (most visible in FAQ.mdx, where h5 is a topic and h6 is
|
|
||||||
// a question). Style h5 as a major section and h6 as a sub-section so
|
|
||||||
// the visual hierarchy matches the semantic one.
|
|
||||||
//
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
.h1,
|
|
||||||
.h2,
|
|
||||||
.h3,
|
|
||||||
.h4,
|
|
||||||
.h5,
|
|
||||||
.h6 {
|
|
||||||
margin-top: 2em;
|
|
||||||
margin-bottom: .5em;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.3;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
scroll-margin-top: 4rem; // anchor-link friendly
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h2,
|
|
||||||
.h2 {
|
|
||||||
padding-bottom: .35em;
|
|
||||||
font-size: 1.75rem;
|
|
||||||
border-bottom: 1px solid var(--#{$prefix}border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3,
|
|
||||||
.h3 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4,
|
|
||||||
.h4 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// h5 is the dominant section heading across docs MDX files. Give it
|
|
||||||
// a visible separator above so sections are easy to scan.
|
|
||||||
h5,
|
|
||||||
.h5 {
|
|
||||||
padding-top: 1.5em;
|
|
||||||
margin-top: 2.5em;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
border-top: 1px solid var(--#{$prefix}border-color);
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-top: 0;
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// h6 is the sub-section / FAQ question style. Smaller than h5 but
|
|
||||||
// still distinctly heading-like, with a touch of accent colour to
|
|
||||||
// separate from body text.
|
|
||||||
h6,
|
|
||||||
.h6 {
|
|
||||||
margin-top: 1.75em;
|
|
||||||
font-size: 1.08rem;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Paragraphs and lists.
|
|
||||||
//
|
|
||||||
p,
|
|
||||||
ul,
|
|
||||||
ol,
|
|
||||||
dl,
|
|
||||||
blockquote,
|
|
||||||
table {
|
|
||||||
margin-bottom: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: .35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tighter nested lists
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin-top: .35rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lead-style first paragraph (just after a heading)
|
|
||||||
h2 + p,
|
|
||||||
h3 + p,
|
|
||||||
h5 + p {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Inline code — pill style that complements MDX's syntax-highlighted blocks.
|
|
||||||
//
|
|
||||||
p code,
|
|
||||||
li code,
|
|
||||||
td code,
|
|
||||||
th code,
|
|
||||||
h2 code,
|
|
||||||
h3 code,
|
|
||||||
h4 code,
|
|
||||||
h5 code,
|
|
||||||
h6 code {
|
|
||||||
padding: .15em .4em;
|
|
||||||
font-size: .875em;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Code blocks.
|
|
||||||
//
|
|
||||||
pre.astro-code,
|
|
||||||
pre {
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: .875rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Tables — reference-doc styling with hairline borders.
|
|
||||||
//
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: .95rem;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius-sm);
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: .65rem .9rem;
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
border-bottom: 1px solid var(--#{$prefix}border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:last-child td {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Blockquotes used as info callouts.
|
|
||||||
//
|
|
||||||
blockquote {
|
|
||||||
padding: .85rem 1.1rem;
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
background: var(--#{$prefix}info-bg-subtle);
|
|
||||||
border-left: 4px solid var(--#{$prefix}info-border-subtle);
|
|
||||||
@include border-radius($border-radius-sm);
|
|
||||||
|
|
||||||
p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: rgba(0, 0, 0, .05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Prose links — stand out more than Bootstrap defaults.
|
|
||||||
//
|
|
||||||
a:not(.btn):not(.nav-link) {
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
text-underline-offset: 3px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration-thickness: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Horizontal rule — used as additional section separator.
|
|
||||||
//
|
|
||||||
hr {
|
|
||||||
margin: 2.5rem 0;
|
|
||||||
border: 0;
|
|
||||||
border-top: 1px solid var(--#{$prefix}border-color);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lead paragraph — the first paragraph in any doc is a summary.
|
|
||||||
// Visually heavier so it functions as a deck.
|
|
||||||
//
|
|
||||||
> p:first-child,
|
|
||||||
> p:first-of-type {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// FAQ / disclosure accordion — uses native <details>/<summary>.
|
|
||||||
// Add `.faq-item` to <details> to opt in.
|
|
||||||
//
|
|
||||||
details.faq-item {
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
@include transition(border-color .15s ease, box-shadow .15s ease);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--#{$prefix}primary-border-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] {
|
|
||||||
background: var(--#{$prefix}body-bg);
|
|
||||||
border-color: var(--#{$prefix}primary-border-subtle);
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
position: relative;
|
|
||||||
padding: .9rem 3rem .9rem 1.1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
list-style: none;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
@include transition(color .15s ease);
|
|
||||||
|
|
||||||
// Hide the default disclosure triangle in Webkit/Blink
|
|
||||||
&::-webkit-details-marker {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the marker in modern Firefox too
|
|
||||||
&::marker {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right-side chevron, rotates on open
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 1.1rem;
|
|
||||||
display: inline-block;
|
|
||||||
width: .55rem;
|
|
||||||
height: .55rem;
|
|
||||||
margin-top: -.4rem;
|
|
||||||
content: "";
|
|
||||||
border-right: 2px solid currentcolor;
|
|
||||||
border-bottom: 2px solid currentcolor;
|
|
||||||
opacity: .55;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
@include transition(transform .2s ease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] > summary {
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
border-bottom: 1px solid var(--#{$prefix}border-color);
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
margin-top: -.15rem;
|
|
||||||
opacity: 1;
|
|
||||||
transform: rotate(-135deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content (everything after <summary>) gets the inner padding
|
|
||||||
> *:not(summary) {
|
|
||||||
padding-right: 1.1rem;
|
|
||||||
padding-left: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:not(summary):first-of-type {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:not(summary):last-child {
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tighten paragraphs/lists inside an FAQ answer
|
|
||||||
p,
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin-bottom: .65rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:last-child p:last-child,
|
|
||||||
> p:last-child,
|
|
||||||
> ul:last-child,
|
|
||||||
> ol:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code blocks inside an FAQ answer get a less-distracting bg
|
|
||||||
pre.astro-code,
|
|
||||||
pre {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dark-mode tweak: blockquote info bg needs more contrast on dark
|
|
||||||
&[data-bs-theme="dark"],
|
|
||||||
[data-bs-theme="dark"] & {
|
|
||||||
.card > .card-body blockquote code {
|
|
||||||
background: rgba(255, 255, 255, .08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// FAQ page — custom layout that breaks out of the standard docs card
|
|
||||||
// to give the FAQ a more distinctive visual identity. Opt in via
|
|
||||||
// body.faq-page (set in pages/docs/faq.astro).
|
|
||||||
//
|
|
||||||
|
|
||||||
.faq-page {
|
|
||||||
// The FAQ doesn't use the standard card wrapper used by other docs,
|
|
||||||
// so the container can stretch wider for the chip strip and section
|
|
||||||
// grid.
|
|
||||||
.app-content > .container-fluid {
|
|
||||||
max-width: 64rem;
|
|
||||||
|
|
||||||
@include media-breakpoint-up(xxl) {
|
|
||||||
max-width: 72rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Hero -------------------------------------------------------------
|
|
||||||
.faq-hero {
|
|
||||||
position: relative;
|
|
||||||
padding: 3rem 1.5rem 2.5rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
overflow: hidden;
|
|
||||||
background: radial-gradient(ellipse at top left, rgba(var(--#{$prefix}primary-rgb), .12), transparent 60%), radial-gradient(ellipse at bottom right, rgba(var(--#{$prefix}info-rgb), .1), transparent 65%), var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-hero-eyebrow {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: .4rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: .35rem .85rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: .75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: .08em;
|
|
||||||
background: var(--#{$prefix}primary-bg-subtle);
|
|
||||||
border: 1px solid var(--#{$prefix}primary-border-subtle);
|
|
||||||
@include border-radius(50rem);
|
|
||||||
|
|
||||||
.bi {
|
|
||||||
font-size: .9rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-hero-title {
|
|
||||||
margin: 0 0 .5rem;
|
|
||||||
font-size: 2.25rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.15;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
|
||||||
font-size: 2.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-hero-lead {
|
|
||||||
max-width: 36rem;
|
|
||||||
margin: 0 auto 1.75rem;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
line-height: 1.55;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-search {
|
|
||||||
position: relative;
|
|
||||||
max-width: 32rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
height: 3rem;
|
|
||||||
padding-right: 3rem;
|
|
||||||
padding-left: 3rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
background: var(--#{$prefix}body-bg);
|
|
||||||
@include border-radius(50rem);
|
|
||||||
@include transition(box-shadow .15s ease, border-color .15s ease);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 .25rem rgba(var(--#{$prefix}primary-rgb), .15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-search-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 1.2rem;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-search-clear {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: .65rem;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
border: 0;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
@include border-radius(50%);
|
|
||||||
@include transition(background .15s ease, color .15s ease);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
background: var(--#{$prefix}secondary-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-empty-state {
|
|
||||||
max-width: 32rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 1.5rem auto 0;
|
|
||||||
font-size: .95rem;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
|
|
||||||
.bi {
|
|
||||||
margin-right: .35rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Section nav chips ------------------------------------------------
|
|
||||||
.faq-chips {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: .5rem;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: .45rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: .45rem .85rem;
|
|
||||||
font-size: .875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
text-decoration: none;
|
|
||||||
background: var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius(50rem);
|
|
||||||
@include transition(transform .15s ease, border-color .15s ease, background-color .15s ease, color .15s ease);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bi {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-chip-count {
|
|
||||||
padding: 0 .45rem;
|
|
||||||
font-size: .7rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
@include border-radius(50rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per-section chip accent on hover/active
|
|
||||||
@each $tone in (primary, info, warning, success, danger, secondary) {
|
|
||||||
.faq-chip-#{$tone}:hover {
|
|
||||||
color: var(--#{$prefix}#{$tone}-text-emphasis);
|
|
||||||
background: var(--#{$prefix}#{$tone}-bg-subtle);
|
|
||||||
border-color: var(--#{$prefix}#{$tone}-border-subtle);
|
|
||||||
|
|
||||||
.faq-chip-count {
|
|
||||||
color: var(--#{$prefix}#{$tone}-text-emphasis);
|
|
||||||
background: rgba(0, 0, 0, .06);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Sections ---------------------------------------------------------
|
|
||||||
.faq-section {
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
scroll-margin-top: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-section-header {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
border-left: 4px solid var(--#{$prefix}primary);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-section-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 2.75rem;
|
|
||||||
height: 2.75rem;
|
|
||||||
font-size: 1.35rem;
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
background: var(--#{$prefix}primary-bg-subtle);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-section-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-section-count {
|
|
||||||
margin: .1rem 0 0;
|
|
||||||
font-size: .8rem;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per-section tone accents
|
|
||||||
@each $tone in (primary, info, warning, success, danger, secondary) {
|
|
||||||
.faq-section-#{$tone} {
|
|
||||||
border-left-color: var(--#{$prefix}#{$tone});
|
|
||||||
|
|
||||||
.faq-section-icon {
|
|
||||||
color: var(--#{$prefix}#{$tone}-text-emphasis);
|
|
||||||
background: var(--#{$prefix}#{$tone}-bg-subtle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- FAQ items (override the generic .docs-page details styling) ------
|
|
||||||
.faq-section-items .faq-item {
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
@include transition(border-color .15s ease, box-shadow .15s ease, transform .15s ease);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--#{$prefix}primary-border-subtle);
|
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] {
|
|
||||||
border-color: var(--#{$prefix}primary-border-subtle);
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, .04);
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: flex;
|
|
||||||
gap: .85rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
list-style: none;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
&::-webkit-details-marker {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::marker {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: none; // disable the generic chevron, we use our own
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-q-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 1.75rem;
|
|
||||||
height: 1.75rem;
|
|
||||||
font-size: .85rem;
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
background: var(--#{$prefix}primary-bg-subtle);
|
|
||||||
@include border-radius(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-q-text {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-q-chevron {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
@include transition(transform .2s ease, color .2s ease);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] .faq-q-chevron {
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] summary {
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
border-bottom: 1px solid var(--#{$prefix}border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-answer {
|
|
||||||
padding: 1.1rem 1.25rem 1.1rem 3.85rem; // align with the question text (icon + gap)
|
|
||||||
font-size: .95rem;
|
|
||||||
line-height: 1.65;
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
|
|
||||||
p:last-child,
|
|
||||||
ul:last-child,
|
|
||||||
ol:last-child,
|
|
||||||
pre:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
text-decoration: underline;
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration-thickness: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: .12em .35em;
|
|
||||||
font-size: .875em;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
background: var(--#{$prefix}tertiary-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.astro-code,
|
|
||||||
pre {
|
|
||||||
padding: .85rem 1rem;
|
|
||||||
margin-top: .75rem;
|
|
||||||
margin-bottom: .75rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: .85rem;
|
|
||||||
line-height: 1.55;
|
|
||||||
@include border-radius($border-radius-sm);
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- CTA footer -------------------------------------------------------
|
|
||||||
.faq-cta {
|
|
||||||
padding: 2.5rem 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
background: radial-gradient(ellipse at top, rgba(var(--#{$prefix}primary-rgb), .08), transparent 60%), var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
@include border-radius($border-radius-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-cta-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 3.5rem;
|
|
||||||
height: 3.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
color: var(--#{$prefix}primary);
|
|
||||||
background: var(--#{$prefix}primary-bg-subtle);
|
|
||||||
@include border-radius(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-cta h2 {
|
|
||||||
margin: 0 0 .5rem;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-cta p {
|
|
||||||
max-width: 32rem;
|
|
||||||
margin: 0 auto 1.5rem;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-cta-actions {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: .5rem;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Dropdown
|
|
||||||
//
|
|
||||||
|
|
||||||
// General Dropdown Rules
|
|
||||||
|
|
||||||
// Ensure children cannot overflow and break the dropdown border radius
|
|
||||||
.dropdown-menu {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fs-7 {
|
|
||||||
.dropdown-menu {
|
|
||||||
font-size: $font-size-sm !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle::after {
|
|
||||||
vertical-align: .2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-icon {
|
|
||||||
&::after {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dropdown Sizes
|
|
||||||
.dropdown-menu-lg {
|
|
||||||
min-width: 280px;
|
|
||||||
max-width: 300px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.dropdown-divider {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item {
|
|
||||||
padding: $dropdown-padding-y $dropdown-item-padding-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dropdown Submenu
|
|
||||||
.dropdown-submenu {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
> a::after {
|
|
||||||
@include caret-end();
|
|
||||||
float: right;
|
|
||||||
margin-top: .5rem;
|
|
||||||
margin-left: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .dropdown-menu {
|
|
||||||
top: 0;
|
|
||||||
left: 100%;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dropdown Hover
|
|
||||||
.dropdown-hover {
|
|
||||||
&:hover,
|
|
||||||
&.nav-item.dropdown:hover,
|
|
||||||
.dropdown-submenu:hover,
|
|
||||||
&.dropdown-submenu:hover {
|
|
||||||
> .dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Dropdown Sizes
|
|
||||||
.dropdown-menu-xl {
|
|
||||||
min-width: 360px;
|
|
||||||
max-width: 420px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.dropdown-divider {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item {
|
|
||||||
padding: $dropdown-padding-y $dropdown-item-padding-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dropdown header and footer
|
|
||||||
.dropdown-footer,
|
|
||||||
.dropdown-header {
|
|
||||||
display: block;
|
|
||||||
padding: .5rem $dropdown-item-padding-x;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add fade animation to dropdown menus by appending
|
|
||||||
// the class .animated-dropdown-menu to the .dropdown-menu ul (or ol)
|
|
||||||
.open:not(.dropup) > .animated-dropdown-menu {
|
|
||||||
animation: flipInX .7s both;
|
|
||||||
backface-visibility: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix dropdown menu in navbars
|
|
||||||
.navbar-custom-menu > .navbar-nav {
|
|
||||||
> li {
|
|
||||||
position: relative;
|
|
||||||
> .dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
.navbar-custom-menu > .navbar-nav {
|
|
||||||
float: right;
|
|
||||||
> li {
|
|
||||||
position: static;
|
|
||||||
> .dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
right: 5%;
|
|
||||||
left: auto;
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Menu
|
|
||||||
.navbar-nav > .user-menu {
|
|
||||||
> .nav-link::after {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .dropdown-menu {
|
|
||||||
width: 280px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
// Header menu
|
|
||||||
> li.user-header {
|
|
||||||
min-height: 175px;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
// User image
|
|
||||||
> img {
|
|
||||||
z-index: 5;
|
|
||||||
width: 90px;
|
|
||||||
height: 90px;
|
|
||||||
border: 3px solid;
|
|
||||||
border-color: transparent;
|
|
||||||
border-color: var(--#{$prefix}border-color-translucent);
|
|
||||||
}
|
|
||||||
|
|
||||||
> p {
|
|
||||||
z-index: 5;
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 17px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
|
|
||||||
> small {
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu Body
|
|
||||||
> .user-body {
|
|
||||||
@include clearfix();
|
|
||||||
padding: 15px;
|
|
||||||
border-top: 1px solid var(--#{$prefix}border-color);
|
|
||||||
border-bottom: 1px solid var(--#{$prefix}border-color-translucent);
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu Footer
|
|
||||||
> .user-footer {
|
|
||||||
@include clearfix();
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--#{$prefix}light-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-image {
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
float: none;
|
|
||||||
margin-top: -8px;
|
|
||||||
margin-right: .4rem;
|
|
||||||
line-height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
float: left;
|
|
||||||
width: $lte-sidebar-user-image-width;
|
|
||||||
height: $lte-sidebar-user-image-width;
|
|
||||||
margin-top: -2px;
|
|
||||||
// margin-right: 10px;
|
|
||||||
@include border-radius(50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Info Box
|
|
||||||
//
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
@include box-shadow($lte-card-shadow);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 80px;
|
|
||||||
padding: .5rem;
|
|
||||||
margin-bottom: map-get($spacers, 3);
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
height: 2px;
|
|
||||||
margin: 5px 0;
|
|
||||||
background-color: rgba(var(--#{$lte-prefix}card-variant-color-rgb), .125);
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
background-color: var(--#{$lte-prefix}card-variant-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 70px;
|
|
||||||
font-size: 1.875rem;
|
|
||||||
text-align: center;
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
|
|
||||||
> img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box-content {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 10px;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box-number {
|
|
||||||
display: block;
|
|
||||||
margin-top: .25rem;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-description,
|
|
||||||
.info-box-text {
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box-more {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-description {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
|
||||||
.col-xl-2 &,
|
|
||||||
.col-lg-2 &,
|
|
||||||
.col-md-2 & {
|
|
||||||
.progress-description {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-xl-3 &,
|
|
||||||
.col-lg-3 &,
|
|
||||||
.col-md-3 & {
|
|
||||||
.progress-description {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
.col-xl-2 &,
|
|
||||||
.col-lg-2 &,
|
|
||||||
.col-md-2 & {
|
|
||||||
.progress-description {
|
|
||||||
@include font-size(.75rem);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-xl-3 &,
|
|
||||||
.col-lg-3 &,
|
|
||||||
.col-md-3 & {
|
|
||||||
.progress-description {
|
|
||||||
@include font-size(.75rem);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(xl) {
|
|
||||||
.col-xl-2 &,
|
|
||||||
.col-lg-2 &,
|
|
||||||
.col-md-2 & {
|
|
||||||
.progress-description {
|
|
||||||
@include font-size(1rem);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-xl-3 &,
|
|
||||||
.col-lg-3 &,
|
|
||||||
.col-md-3 & {
|
|
||||||
.progress-description {
|
|
||||||
@include font-size(1rem);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// Misc: Miscellaneous
|
|
||||||
//
|
|
||||||
|
|
||||||
// Image sizes
|
|
||||||
.img-size-64,
|
|
||||||
.img-size-50,
|
|
||||||
.img-size-32 {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-size-64 {
|
|
||||||
width: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-size-50 {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-size-32 {
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
//
|
|
||||||
// General: Mixins
|
|
||||||
//
|
|
||||||
|
|
||||||
@import "mixins/animations";
|
|
||||||
@import "mixins/scrollbar";
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Progress Bar
|
|
||||||
//
|
|
||||||
|
|
||||||
//General CSS
|
|
||||||
.progress {
|
|
||||||
@include box-shadow(null);
|
|
||||||
@include border-radius($lte-progress-bar-border-radius);
|
|
||||||
|
|
||||||
// Vertical bars
|
|
||||||
&.vertical {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 30px;
|
|
||||||
height: 200px;
|
|
||||||
margin-right: 10px;
|
|
||||||
|
|
||||||
> .progress-bar {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sizes
|
|
||||||
&.sm,
|
|
||||||
&.progress-sm {
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.xs,
|
|
||||||
&.progress-xs {
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.xxs,
|
|
||||||
&.progress-xxs {
|
|
||||||
width: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-group {
|
|
||||||
margin-bottom: map-get($spacers, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// size variation
|
|
||||||
.progress-sm {
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-xs {
|
|
||||||
height: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-xxs {
|
|
||||||
height: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove margins from progress bars when put in a table
|
|
||||||
.table {
|
|
||||||
tr > td {
|
|
||||||
.progress {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
:root,
|
|
||||||
[data-bs-theme="light"] {
|
|
||||||
// Sidebar
|
|
||||||
--#{$lte-prefix}sidebar-width: #{$lte-sidebar-width};
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Small Box
|
|
||||||
//
|
|
||||||
|
|
||||||
.small-box {
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
@include box-shadow($lte-card-shadow);
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
--bs-link-color-rgb: none;
|
|
||||||
--bs-link-hover-color-rgb: none;
|
|
||||||
--bs-heading-color: none;
|
|
||||||
|
|
||||||
// content wrapper
|
|
||||||
> .inner {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .small-box-footer {
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
display: block;
|
|
||||||
padding: 3px 0;
|
|
||||||
text-align: center;
|
|
||||||
background-color: rgba($black, .07);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba($black, .1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
@include font-size(2.2rem);
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 0 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
.col-xl-2 &,
|
|
||||||
.col-lg-2 &,
|
|
||||||
.col-md-2 & {
|
|
||||||
h3 {
|
|
||||||
@include font-size(1.6rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-xl-3 &,
|
|
||||||
.col-lg-3 &,
|
|
||||||
.col-md-3 & {
|
|
||||||
h3 {
|
|
||||||
@include font-size(1.6rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(xl) {
|
|
||||||
.col-xl-2 &,
|
|
||||||
.col-lg-2 &,
|
|
||||||
.col-md-2 & {
|
|
||||||
h3 {
|
|
||||||
@include font-size(2.2rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-xl-3 &,
|
|
||||||
.col-lg-3 &,
|
|
||||||
.col-md-3 & {
|
|
||||||
h3 {
|
|
||||||
@include font-size(2.2rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
> small {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: .9rem;
|
|
||||||
color: $gray-100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3,
|
|
||||||
p {
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the icon
|
|
||||||
.small-box-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 15px;
|
|
||||||
z-index: 0;
|
|
||||||
height: 70px;
|
|
||||||
font-size: 70px;
|
|
||||||
color: rgba($black, .15);
|
|
||||||
@include transition(transform $lte-transition-speed linear);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small box hover state
|
|
||||||
&:hover {
|
|
||||||
// Animate icons on small box hover
|
|
||||||
.small-box-icon {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
// No need for icons on very small devices
|
|
||||||
.small-box {
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.small-box-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Table
|
|
||||||
//
|
|
||||||
|
|
||||||
.table {
|
|
||||||
&:not(.table-dark) {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixed table head
|
|
||||||
// Uses Bootstrap CSS variables so the sticky header follows light/dark mode
|
|
||||||
// automatically. Fixes #6026.
|
|
||||||
&.table-head-fixed {
|
|
||||||
thead tr:nth-child(1) th {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
background-color: var(--bs-body-bg, #{$white});
|
|
||||||
border-bottom: 0;
|
|
||||||
box-shadow: inset 0 1px 0 var(--bs-border-color, #{$table-border-color}), inset 0 -1px 0 var(--bs-border-color, #{$table-border-color});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no border
|
|
||||||
&.no-border {
|
|
||||||
&,
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// .text-center in tables
|
|
||||||
&.text-center {
|
|
||||||
&,
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.table-valign-middle {
|
|
||||||
thead > tr > th,
|
|
||||||
thead > tr > td,
|
|
||||||
tbody > tr > th,
|
|
||||||
tbody > tr > td {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body.p-0 & {
|
|
||||||
thead > tr > th,
|
|
||||||
thead > tr > td,
|
|
||||||
tfoot > tr > th,
|
|
||||||
tfoot > tr > td,
|
|
||||||
tbody > tr > th,
|
|
||||||
tbody > tr > td {
|
|
||||||
&:first-of-type {
|
|
||||||
padding-left: map-get($spacers, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
padding-right: map-get($spacers, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
//
|
|
||||||
// Component: Timeline
|
|
||||||
//
|
|
||||||
|
|
||||||
.timeline {
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 0 45px;
|
|
||||||
// The line
|
|
||||||
&::before {
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 31px;
|
|
||||||
width: 4px;
|
|
||||||
margin: 0;
|
|
||||||
content: "";
|
|
||||||
background-color: var(--#{$prefix}border-color);
|
|
||||||
}
|
|
||||||
// Element
|
|
||||||
> div {
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
display: table;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
margin-right: 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
// The content
|
|
||||||
> .timeline-item {
|
|
||||||
@include box-shadow($lte-card-shadow);
|
|
||||||
@include border-radius($border-radius);
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-right: 15px;
|
|
||||||
margin-left: 60px;
|
|
||||||
color: var(--#{$prefix}body-color);
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
// The time and header
|
|
||||||
> .time {
|
|
||||||
float: right;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
}
|
|
||||||
// Header
|
|
||||||
> .timeline-header {
|
|
||||||
padding: 10px;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.1;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
border-bottom: 1px solid var(--#{$prefix}border-color);
|
|
||||||
// Link in header
|
|
||||||
> a {
|
|
||||||
font-weight: 600;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Item body and footer
|
|
||||||
> .timeline-body,
|
|
||||||
> .timeline-footer {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .timeline-body {
|
|
||||||
> img {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
> dl,
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 18px;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 30px;
|
|
||||||
text-align: center;
|
|
||||||
background-color: var(--#{$prefix}secondary-bg);
|
|
||||||
border-radius: 50%; // stylelint-disable-line property-disallowed-list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Time label
|
|
||||||
> .time-label {
|
|
||||||
> span {
|
|
||||||
@include border-radius(4px);
|
|
||||||
display: inline-block;
|
|
||||||
padding: 5px;
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-inverse {
|
|
||||||
> div {
|
|
||||||
> .timeline-item {
|
|
||||||
@include box-shadow(none);
|
|
||||||
background-color: var(--#{$prefix}tertiary-bg);
|
|
||||||
border: 1px solid var(--#{$prefix}border-color);
|
|
||||||
|
|
||||||
> .timeline-header {
|
|
||||||
border-bottom-color: var(--#{$prefix}border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// Toast
|
|
||||||
//
|
|
||||||
|
|
||||||
@each $name, $color in $theme-colors {
|
|
||||||
.toast-#{$name} {
|
|
||||||
--#{$prefix}toast-header-color: #{color-contrast($color)};
|
|
||||||
--#{$prefix}toast-header-bg: #{$color};
|
|
||||||
--#{$prefix}toast-header-border-color: #{$color};
|
|
||||||
--#{$prefix}toast-border-color: #{$color};
|
|
||||||
--#{$prefix}toast-bg: var(--#{$prefix}#{$name}-bg-subtle);
|
|
||||||
|
|
||||||
@if color-contrast($color) == $color-contrast-light {
|
|
||||||
.btn-close {
|
|
||||||
@include btn-close-white();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if $enable-dark-mode {
|
|
||||||
@include color-mode(dark) {
|
|
||||||
@each $name, $color in $theme-colors {
|
|
||||||
.toast-#{$name} {
|
|
||||||
@if color-contrast($color) == $color-contrast-dark {
|
|
||||||
.btn-close {
|
|
||||||
--#{$prefix}btn-close-white-filter: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// SIDEBAR SKINS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
|
|
||||||
$lte-sidebar-hover-bg-dark: rgba(255, 255, 255, .1) !default;
|
|
||||||
$lte-sidebar-color-dark: #c2c7d0 !default;
|
|
||||||
$lte-sidebar-hover-color-dark: $white !default;
|
|
||||||
$lte-sidebar-active-color-dark: $white !default;
|
|
||||||
$lte-sidebar-menu-active-bg-dark: rgba(255, 255, 255, .1) !default;
|
|
||||||
$lte-sidebar-menu-active-color-dark: $white !default;
|
|
||||||
$lte-sidebar-submenu-bg-dark: transparent !default;
|
|
||||||
$lte-sidebar-submenu-color-dark: #c2c7d0 !default;
|
|
||||||
$lte-sidebar-submenu-hover-color-dark: $white !default;
|
|
||||||
$lte-sidebar-submenu-hover-bg-dark: rgba(255, 255, 255, .1) !default;
|
|
||||||
$lte-sidebar-submenu-active-color-dark: $white !default;
|
|
||||||
$lte-sidebar-submenu-active-bg-dark: rgba(255, 255, 255, .1) !default;
|
|
||||||
$lte-sidebar-header-color-dark: tint-color(#c2c7d0, 5%) !default;
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
//
|
|
||||||
// Custom AdminLTE Variables
|
|
||||||
//
|
|
||||||
|
|
||||||
// Prefix for :root CSS variables and others.
|
|
||||||
$lte-prefix: lte- !default;
|
|
||||||
|
|
||||||
// TRANSITIONS SETTINGS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// Transition global options
|
|
||||||
$lte-transition-speed: .3s !default;
|
|
||||||
$lte-transition-fn: ease-in-out !default;
|
|
||||||
|
|
||||||
// SIDEBAR
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-sidebar-width: 250px !default;
|
|
||||||
$lte-sidebar-breakpoint: lg !default;
|
|
||||||
$lte-sidebar-padding-x: .5rem !default;
|
|
||||||
$lte-sidebar-padding-x-compact: .5rem !default;
|
|
||||||
$lte-sidebar-padding-y: .5rem !default;
|
|
||||||
$lte-sidebar-padding-y-compact: .25rem !default;
|
|
||||||
$lte-sidebar-transition: min-width $lte-transition-speed $lte-transition-fn,
|
|
||||||
max-width $lte-transition-speed $lte-transition-fn,
|
|
||||||
margin-left $lte-transition-speed $lte-transition-fn,
|
|
||||||
margin-right $lte-transition-speed $lte-transition-fn !default;
|
|
||||||
|
|
||||||
// SIDEBAR SKINS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
|
|
||||||
$lte-sidebar-hover-bg: rgba($black, .1) !default;
|
|
||||||
$lte-sidebar-color: $gray-800 !default;
|
|
||||||
$lte-sidebar-hover-color: $gray-900 !default;
|
|
||||||
$lte-sidebar-active-color: $black !default;
|
|
||||||
$lte-sidebar-menu-active-bg: rgba($black, .1) !default;
|
|
||||||
$lte-sidebar-menu-active-color: $black !default;
|
|
||||||
$lte-sidebar-submenu-bg: transparent !default;
|
|
||||||
$lte-sidebar-submenu-color: #777 !default;
|
|
||||||
$lte-sidebar-submenu-hover-color: $black !default;
|
|
||||||
$lte-sidebar-submenu-hover-bg: rgba($black, .1) !default;
|
|
||||||
$lte-sidebar-submenu-active-color: $gray-900 !default;
|
|
||||||
$lte-sidebar-submenu-active-bg: rgba($black, .1) !default;
|
|
||||||
$lte-sidebar-header-color: shade-color($gray-800, 5%) !default;
|
|
||||||
|
|
||||||
// SIDEBAR MINI
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$nav-link-padding-x-compact: .25rem !default;
|
|
||||||
$lte-sidebar-mini-width: ($nav-link-padding-x + $lte-sidebar-padding-x + .8rem) * 2 !default;
|
|
||||||
$lte-sidebar-mini-width-compact: ($nav-link-padding-x-compact + $lte-sidebar-padding-x-compact + .8rem) * 2 !default;
|
|
||||||
$lte-sidebar-nav-icon-width: $lte-sidebar-mini-width - (($lte-sidebar-padding-x + $nav-link-padding-x) * 2) !default;
|
|
||||||
$lte-sidebar-user-image-width: $lte-sidebar-nav-icon-width + ($nav-link-padding-x * .4) !default;
|
|
||||||
|
|
||||||
// MAIN HEADER
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$nav-link-height-compact: 1.75rem !default;
|
|
||||||
$lte-app-header-bottom-border-width: $border-width !default;
|
|
||||||
$lte-app-header-bottom-border-color: var(--#{$prefix}border-color) !default;
|
|
||||||
$lte-app-header-bottom-border: $lte-app-header-bottom-border-width solid $lte-app-header-bottom-border-color !default;
|
|
||||||
$lte-app-header-link-padding-y: $navbar-padding-y !default;
|
|
||||||
$lte-app-header-height: ($nav-link-height + ($lte-app-header-link-padding-y * 2)) !default;
|
|
||||||
$lte-app-header-height-compact: ($nav-link-height-compact + ($lte-app-header-link-padding-y * 2)) !default;
|
|
||||||
$lte-zindex-fixed-header: $zindex-fixed !default;
|
|
||||||
|
|
||||||
// APP MAIN
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-app-main-padding-bottom: $grid-gutter-width * .5 !default;
|
|
||||||
|
|
||||||
// CONTENT PADDING
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-content-padding-y: 0 !default;
|
|
||||||
$lte-content-padding-x: .5rem !default;
|
|
||||||
|
|
||||||
// MAIN FOOTER
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-app-footer-padding: 1rem !default;
|
|
||||||
$lte-app-footer-padding-compact: .5rem !default;
|
|
||||||
$lte-app-footer-border-top-width: 1px !default;
|
|
||||||
$lte-app-footer-border-top-color: var(--#{$prefix}border-color) !default;
|
|
||||||
$lte-app-footer-border-top: $lte-app-footer-border-top-width solid $lte-app-footer-border-top-color !default;
|
|
||||||
$lte-app-footer-bg: var(--#{$prefix}body-bg) !default;
|
|
||||||
$lte-app-footer-color: var(--#{$prefix}secondary-color) !default;
|
|
||||||
$lte-zindex-fixed-footer: $zindex-fixed !default;
|
|
||||||
// CONTENT BOTTOM AREA
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-app-content-bottom-area-margin-bottom: -$lte-app-main-padding-bottom !default;
|
|
||||||
$lte-app-content-bottom-area-color: $lte-app-footer-color !default;
|
|
||||||
$lte-app-content-bottom-area-bg: $lte-app-footer-bg !default;
|
|
||||||
$lte-app-content-bottom-area-top-border: $lte-app-footer-border-top !default;
|
|
||||||
$lte-app-content-bottom-area-padding-y: $lte-app-footer-padding !default;
|
|
||||||
$lte-app-content-bottom-area-padding-x: 0 !default;
|
|
||||||
|
|
||||||
// CONTENT TOP AREA
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-app-content-top-area-top-border: $lte-app-footer-border-top !default;
|
|
||||||
$lte-app-content-top-area-padding-y: $lte-app-footer-padding !default;
|
|
||||||
$lte-app-content-top-area-padding-x: 0 !default;
|
|
||||||
|
|
||||||
// BRAND LINK
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$navbar-brand-padding-y-compact: $navbar-brand-padding-y * .75 !default;
|
|
||||||
$navbar-padding-y-compact: $navbar-padding-y * .5 !default;
|
|
||||||
$lte-brand-link-padding-y: $navbar-brand-padding-y + $navbar-padding-y !default;
|
|
||||||
$lte-brand-link-padding-y-compact: $navbar-brand-padding-y-compact + $navbar-padding-y-compact !default;
|
|
||||||
$lte-brand-link-padding-x: $lte-sidebar-padding-x !default;
|
|
||||||
$lte-brand-link-padding-x-compact: $lte-sidebar-padding-x-compact !default;
|
|
||||||
$lte-brand-link-border-buttom: 1px !default;
|
|
||||||
|
|
||||||
// CARDS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-card-shadow: 0 0 1px rgba(var(--#{$prefix}body-color-rgb), .125), 0 1px 3px rgba(var(--#{$prefix}body-color-rgb), .2) !default;
|
|
||||||
$lte-card-title-font-size: 1.1rem !default;
|
|
||||||
$lte-card-title-font-weight: $font-weight-normal !default;
|
|
||||||
|
|
||||||
// PROGRESS BARS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-progress-bar-border-radius: 1px !default;
|
|
||||||
|
|
||||||
// CALLOUTS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-callout-link-font-weight: $alert-link-font-weight !default;
|
|
||||||
|
|
||||||
// DIRECT CHAT
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-direct-chat-default-msg-bg: var(--#{$prefix}secondary-bg) !default;
|
|
||||||
$lte-direct-chat-default-font-color: var(--#{$prefix}emphasis-color) !default;
|
|
||||||
$lte-direct-chat-default-msg-border-color: var(--#{$prefix}border-color) !default;
|
|
||||||
|
|
||||||
// Z-INDEX
|
|
||||||
// --------------------------------------------------------
|
|
||||||
$lte-zindex-app-header: $zindex-fixed + 4 !default;
|
|
||||||
$lte-zindex-sidebar: $zindex-fixed + 8 !default;
|
|
||||||
$lte-zindex-sidebar-overlay: $lte-zindex-sidebar - 1 !default;
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
/*!
|
|
||||||
* AdminLTE v4.0.0
|
|
||||||
* Author: Colorlib
|
|
||||||
* Website: AdminLTE.io <https://adminlte.io>
|
|
||||||
* License: Open source - MIT <https://opensource.org/licenses/MIT>
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Bootstrap Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootstrap/scss/functions";
|
|
||||||
|
|
||||||
// AdminLTE Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootstrap-variables"; // little modified are here
|
|
||||||
|
|
||||||
// Bootstrap Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootstrap/scss/variables";
|
|
||||||
@import "bootstrap/scss/variables-dark";
|
|
||||||
@import "bootstrap/scss/maps";
|
|
||||||
@import "bootstrap/scss/mixins";
|
|
||||||
@import "bootstrap/scss/utilities";
|
|
||||||
|
|
||||||
// Bootstrap Layout & components
|
|
||||||
@import "bootstrap/scss/root";
|
|
||||||
@import "bootstrap/scss/reboot";
|
|
||||||
@import "bootstrap/scss/type";
|
|
||||||
@import "bootstrap/scss/images";
|
|
||||||
@import "bootstrap/scss/containers";
|
|
||||||
@import "bootstrap/scss/grid";
|
|
||||||
@import "bootstrap/scss/tables";
|
|
||||||
@import "bootstrap/scss/forms";
|
|
||||||
@import "bootstrap/scss/buttons";
|
|
||||||
@import "bootstrap/scss/transitions";
|
|
||||||
@import "bootstrap/scss/dropdown";
|
|
||||||
@import "bootstrap/scss/button-group";
|
|
||||||
@import "bootstrap/scss/nav";
|
|
||||||
@import "bootstrap/scss/navbar";
|
|
||||||
@import "bootstrap/scss/card";
|
|
||||||
@import "bootstrap/scss/accordion";
|
|
||||||
@import "bootstrap/scss/breadcrumb";
|
|
||||||
@import "bootstrap/scss/pagination";
|
|
||||||
@import "bootstrap/scss/badge";
|
|
||||||
@import "bootstrap/scss/alert";
|
|
||||||
@import "bootstrap/scss/progress";
|
|
||||||
@import "bootstrap/scss/list-group";
|
|
||||||
@import "bootstrap/scss/close";
|
|
||||||
@import "bootstrap/scss/toasts";
|
|
||||||
@import "bootstrap/scss/modal";
|
|
||||||
@import "bootstrap/scss/tooltip";
|
|
||||||
@import "bootstrap/scss/popover";
|
|
||||||
@import "bootstrap/scss/carousel";
|
|
||||||
@import "bootstrap/scss/spinners";
|
|
||||||
@import "bootstrap/scss/offcanvas";
|
|
||||||
@import "bootstrap/scss/placeholders";
|
|
||||||
|
|
||||||
// Bootstrap Helpers
|
|
||||||
@import "bootstrap/scss/helpers";
|
|
||||||
|
|
||||||
// Bootstrap Utilities
|
|
||||||
@import "bootstrap/scss/utilities/api";
|
|
||||||
|
|
||||||
// AdminLTE Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "variables";
|
|
||||||
@import "variables-dark";
|
|
||||||
@import "mixins";
|
|
||||||
|
|
||||||
// AdiminLTE Parts
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "parts/core";
|
|
||||||
@import "parts/components";
|
|
||||||
@import "parts/extra-components";
|
|
||||||
@import "parts/pages";
|
|
||||||
@import "parts/miscellaneous";
|
|
||||||
|
|
||||||
// AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance
|
|
||||||
@import "accessibility";
|
|
||||||
|
|
||||||
// AdminLTE Documentation Styles (only applies to pages with body.docs-page)
|
|
||||||
@import "docs";
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
//
|
|
||||||
// Mixins: Animation
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
@keyframes flipInX {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transition-timing-function: ease-in;
|
|
||||||
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
transition-timing-function: ease-in;
|
|
||||||
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
80% {
|
|
||||||
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: perspective(400px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeOut {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shake {
|
|
||||||
0% {
|
|
||||||
transform: translate(2px, 1px) rotate(0deg);
|
|
||||||
}
|
|
||||||
10% {
|
|
||||||
transform: translate(-1px, -2px) rotate(-2deg);
|
|
||||||
}
|
|
||||||
20% {
|
|
||||||
transform: translate(-3px, 0) rotate(3deg);
|
|
||||||
}
|
|
||||||
30% {
|
|
||||||
transform: translate(0, 2px) rotate(0deg);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: translate(1px, -1px) rotate(1deg);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translate(-1px, 2px) rotate(-1deg);
|
|
||||||
}
|
|
||||||
60% {
|
|
||||||
transform: translate(-3px, 1px) rotate(0deg);
|
|
||||||
}
|
|
||||||
70% {
|
|
||||||
transform: translate(2px, 1px) rotate(-2deg);
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
transform: translate(-1px, -1px) rotate(4deg);
|
|
||||||
}
|
|
||||||
90% {
|
|
||||||
transform: translate(2px, 2px) rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translate(1px, -2px) rotate(-1deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes wobble {
|
|
||||||
0% {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
15% {
|
|
||||||
transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
45% {
|
|
||||||
transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
75% {
|
|
||||||
transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
//
|
|
||||||
// Mixins: Scrollbar
|
|
||||||
//
|
|
||||||
|
|
||||||
@mixin scrollbar-color-gray() {
|
|
||||||
scrollbar-color: var(--#{$prefix}secondary-bg) transparent;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background-color: var(--#{$prefix}secondary-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-corner {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin scrollbar-width-thin() {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: .5rem;
|
|
||||||
height: .5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin scrollbar-width-none() {
|
|
||||||
scrollbar-width: none;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
//
|
|
||||||
// Pages: Lock Screen
|
|
||||||
//
|
|
||||||
|
|
||||||
// ADD THIS CLASS TO THE <BODY> TAG
|
|
||||||
.lockscreen {
|
|
||||||
// User name [optional]
|
|
||||||
.lockscreen-name {
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lockscreen-logo {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
font-size: 35px;
|
|
||||||
font-weight: 300;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--#{$prefix}emphasis-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lockscreen-wrapper {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-top: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will contain the image and the sign in form
|
|
||||||
.lockscreen-item {
|
|
||||||
position: relative;
|
|
||||||
width: 290px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 10px auto 30px;
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
@include border-radius(4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User image
|
|
||||||
.lockscreen-image {
|
|
||||||
position: absolute;
|
|
||||||
top: -25px;
|
|
||||||
left: -10px;
|
|
||||||
z-index: 10;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
@include border-radius(50%);
|
|
||||||
|
|
||||||
> img {
|
|
||||||
@include border-radius(50%);
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains the password input and the login button
|
|
||||||
.lockscreen-credentials {
|
|
||||||
margin-left: 70px;
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lockscreen-footer {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
//
|
|
||||||
// Pages: Login & Register
|
|
||||||
//
|
|
||||||
|
|
||||||
.login-logo,
|
|
||||||
.register-logo {
|
|
||||||
margin-bottom: .9rem;
|
|
||||||
font-size: 2.1rem;
|
|
||||||
font-weight: 300;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-page,
|
|
||||||
.register-page {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box,
|
|
||||||
.register-box {
|
|
||||||
width: 400px;
|
|
||||||
|
|
||||||
@media (max-width: map-get($grid-breakpoints, sm)) {
|
|
||||||
width: 90%;
|
|
||||||
margin-top: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-card-body,
|
|
||||||
.register-card-body {
|
|
||||||
padding: 20px;
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
background-color: var(--#{$prefix}body-bg);
|
|
||||||
border-top: 0;
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
.form-control {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
~ .input-group-prepend .input-group-text,
|
|
||||||
~ .input-group-append .input-group-text {
|
|
||||||
border-color: $input-focus-border-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-valid {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
~ .input-group-prepend .input-group-text,
|
|
||||||
~ .input-group-append .input-group-text {
|
|
||||||
border-color: $success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-invalid {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
~ .input-group-append .input-group-text {
|
|
||||||
border-color: $danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-text {
|
|
||||||
color: var(--#{$prefix}secondary-color);
|
|
||||||
background-color: transparent;
|
|
||||||
@include border-top-end-radius($border-radius);
|
|
||||||
@include border-bottom-end-radius($border-radius);
|
|
||||||
@include transition($input-transition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box-msg,
|
|
||||||
.register-box-msg {
|
|
||||||
padding: 0 20px 20px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-auth-links {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
//
|
|
||||||
// Part: Components
|
|
||||||
//
|
|
||||||
|
|
||||||
@import "../progress-bars";
|
|
||||||
@import "../cards";
|
|
||||||
@import "../table";
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// Part: Core
|
|
||||||
//
|
|
||||||
|
|
||||||
@import "../root";
|
|
||||||
@import "../app-wrapper";
|
|
||||||
@import "../app-content";
|
|
||||||
@import "../app-header";
|
|
||||||
@import "../app-sidebar";
|
|
||||||
@import "../app-main";
|
|
||||||
@import "../app-footer";
|
|
||||||
@import "../dropdown";
|
|
||||||
@import "../callouts";
|
|
||||||
@import "../compact-mode";
|
|
||||||
@import "../docs";
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
//
|
|
||||||
// Part: Extra Components
|
|
||||||
//
|
|
||||||
|
|
||||||
@import "../small-box";
|
|
||||||
@import "../info-box";
|
|
||||||
@import "../timeline";
|
|
||||||
@import "../direct-chat";
|
|
||||||
@import "../toasts";
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
//
|
|
||||||
// Part: Miscellaneous
|
|
||||||
//
|
|
||||||
|
|
||||||
@import "../miscellaneous";
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
//
|
|
||||||
// Part: Pages
|
|
||||||
//
|
|
||||||
|
|
||||||
@import "../pages/login_and_register";
|
|
||||||
@import "../pages/lockscreen";
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
// AdminLTE v4.0.0 + Bootstrap 5.3.8 - PageTop Bootsier Styles.
|
|
||||||
|
|
||||||
// Bootstrap Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootstrap-5.3.8/scss/functions";
|
|
||||||
|
|
||||||
// AdminLTE Configuration (little modified are here)
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "adminlte-4.0.0/scss/bootstrap-variables";
|
|
||||||
|
|
||||||
// Bootsier Bootstrap variable overrides
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootsier-variables";
|
|
||||||
|
|
||||||
// Bootstrap Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootstrap-5.3.8/scss/variables";
|
|
||||||
@import "bootstrap-5.3.8/scss/variables-dark";
|
|
||||||
@import "bootstrap-5.3.8/scss/maps";
|
|
||||||
@import "bootstrap-5.3.8/scss/mixins";
|
|
||||||
@import "bootstrap-5.3.8/scss/utilities";
|
|
||||||
|
|
||||||
// Bootstrap Layout & components
|
|
||||||
@import "bootstrap-5.3.8/scss/root";
|
|
||||||
@import "bootstrap-5.3.8/scss/reboot";
|
|
||||||
@import "bootstrap-5.3.8/scss/type";
|
|
||||||
@import "bootstrap-5.3.8/scss/images";
|
|
||||||
@import "bootstrap-5.3.8/scss/containers";
|
|
||||||
@import "bootstrap-5.3.8/scss/grid";
|
|
||||||
@import "bootstrap-5.3.8/scss/tables";
|
|
||||||
@import "bootstrap-5.3.8/scss/forms";
|
|
||||||
@import "bootstrap-5.3.8/scss/buttons";
|
|
||||||
@import "bootstrap-5.3.8/scss/transitions";
|
|
||||||
@import "bootstrap-5.3.8/scss/dropdown";
|
|
||||||
@import "bootstrap-5.3.8/scss/button-group";
|
|
||||||
@import "bootstrap-5.3.8/scss/nav";
|
|
||||||
@import "bootstrap-5.3.8/scss/navbar";
|
|
||||||
@import "bootstrap-5.3.8/scss/card";
|
|
||||||
@import "bootstrap-5.3.8/scss/accordion";
|
|
||||||
@import "bootstrap-5.3.8/scss/breadcrumb";
|
|
||||||
@import "bootstrap-5.3.8/scss/pagination";
|
|
||||||
@import "bootstrap-5.3.8/scss/badge";
|
|
||||||
@import "bootstrap-5.3.8/scss/alert";
|
|
||||||
@import "bootstrap-5.3.8/scss/progress";
|
|
||||||
@import "bootstrap-5.3.8/scss/list-group";
|
|
||||||
@import "bootstrap-5.3.8/scss/close";
|
|
||||||
@import "bootstrap-5.3.8/scss/toasts";
|
|
||||||
@import "bootstrap-5.3.8/scss/modal";
|
|
||||||
@import "bootstrap-5.3.8/scss/tooltip";
|
|
||||||
@import "bootstrap-5.3.8/scss/popover";
|
|
||||||
@import "bootstrap-5.3.8/scss/carousel";
|
|
||||||
@import "bootstrap-5.3.8/scss/spinners";
|
|
||||||
@import "bootstrap-5.3.8/scss/offcanvas";
|
|
||||||
@import "bootstrap-5.3.8/scss/placeholders";
|
|
||||||
|
|
||||||
// Bootstrap Helpers
|
|
||||||
@import "bootstrap-5.3.8/scss/helpers";
|
|
||||||
|
|
||||||
// Bootsier Utilities (must precede utilities/api to extend $utilities)
|
|
||||||
@import "bootsier-utilities";
|
|
||||||
|
|
||||||
// Bootstrap Utilities
|
|
||||||
@import "bootstrap-5.3.8/scss/utilities/api";
|
|
||||||
|
|
||||||
// AdminLTE Configuration
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "adminlte-4.0.0/scss/variables";
|
|
||||||
@import "adminlte-4.0.0/scss/variables-dark";
|
|
||||||
@import "adminlte-4.0.0/scss/mixins";
|
|
||||||
|
|
||||||
// AdminLTE Parts
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "adminlte-4.0.0/scss/parts/core";
|
|
||||||
@import "adminlte-4.0.0/scss/parts/components";
|
|
||||||
@import "adminlte-4.0.0/scss/parts/extra-components";
|
|
||||||
@import "adminlte-4.0.0/scss/parts/pages";
|
|
||||||
@import "adminlte-4.0.0/scss/parts/miscellaneous";
|
|
||||||
|
|
||||||
// AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance
|
|
||||||
@import "adminlte-4.0.0/scss/accessibility";
|
|
||||||
|
|
||||||
// AdminLTE Documentation Styles (only applies to pages with body.docs-page)
|
|
||||||
@import "adminlte-4.0.0/scss/docs";
|
|
||||||
|
|
||||||
// Bootstrap Icons 1.13.1
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootsier-icons";
|
|
||||||
|
|
||||||
// Bootsier customizations
|
|
||||||
// ---------------------------------------------------
|
|
||||||
@import "bootsier-custom";
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Fullscreen: keeps maximize/minimize icons in sync with the actual fullscreen state.
|
|
||||||
document.addEventListener('fullscreenchange', function () {
|
|
||||||
var isFs = !!document.fullscreenElement;
|
|
||||||
document.querySelectorAll('[data-lte-icon="maximize"]').forEach(function (el) {
|
|
||||||
el.classList.toggle('d-none', isFs);
|
|
||||||
});
|
|
||||||
document.querySelectorAll('[data-lte-icon="minimize"]').forEach(function (el) {
|
|
||||||
el.classList.toggle('d-none', !isFs);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Color mode selector (light / dark / auto).
|
|
||||||
var STORAGE_KEY = 'lte-theme';
|
|
||||||
var getStored = function () { return localStorage.getItem(STORAGE_KEY); };
|
|
||||||
var setStored = function (theme) { localStorage.setItem(STORAGE_KEY, theme); };
|
|
||||||
var prefersDark = function () {
|
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
};
|
|
||||||
|
|
||||||
var setTheme = function (theme) {
|
|
||||||
var resolved = (theme === 'auto') ? (prefersDark() ? 'dark' : 'light') : theme;
|
|
||||||
document.documentElement.setAttribute('data-bs-theme', resolved);
|
|
||||||
};
|
|
||||||
|
|
||||||
var showActiveTheme = function (theme) {
|
|
||||||
document.querySelectorAll('[data-bs-theme-value]').forEach(function (el) {
|
|
||||||
el.classList.remove('active');
|
|
||||||
el.setAttribute('aria-pressed', 'false');
|
|
||||||
var check = el.querySelector('.bi-check-lg');
|
|
||||||
if (check) { check.classList.add('d-none'); }
|
|
||||||
});
|
|
||||||
var active = document.querySelector('[data-bs-theme-value="' + theme + '"]');
|
|
||||||
if (active) {
|
|
||||||
active.classList.add('active');
|
|
||||||
active.setAttribute('aria-pressed', 'true');
|
|
||||||
var check = active.querySelector('.bi-check-lg');
|
|
||||||
if (check) { check.classList.remove('d-none'); }
|
|
||||||
}
|
|
||||||
document.querySelectorAll('[data-lte-theme-icon]').forEach(function (icon) {
|
|
||||||
icon.classList.toggle('d-none', icon.dataset.lteThemeIcon !== theme);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
|
|
||||||
var stored = getStored();
|
|
||||||
if (!stored || stored === 'auto') { setTheme('auto'); }
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
var theme = getStored() || 'auto';
|
|
||||||
setTheme(theme);
|
|
||||||
showActiveTheme(theme);
|
|
||||||
document.querySelectorAll('[data-bs-theme-value]').forEach(function (toggle) {
|
|
||||||
toggle.addEventListener('click', function () {
|
|
||||||
var t = toggle.getAttribute('data-bs-theme-value');
|
|
||||||
setStored(t);
|
|
||||||
setTheme(t);
|
|
||||||
showActiveTheme(t);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}());
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
|
|
@ -1,85 +1,20 @@
|
||||||
//! Script de compilacion de activos estaticos.
|
use pagetop_build::StaticFilesBundle;
|
||||||
//!
|
|
||||||
//! Genera el directorio `static/` a partir de `assets/` y embebe su contenido en el binario:
|
|
||||||
//!
|
|
||||||
//! - `static/css/` - CSS compilado a partir de los archivos SCSS de `assets/`.
|
|
||||||
//! - `static/js/` - JS copiado desde `assets/`, renombrando AdminLTE a `bootsier.min.js`.
|
|
||||||
//! - `static/fonts/` - Fuentes copiadas desde `assets/`.
|
|
||||||
//!
|
|
||||||
//! Los archivos `.map` se copian a `static/js/` para uso en desarrollo pero no se incluyen en el
|
|
||||||
//! binario embebido.
|
|
||||||
|
|
||||||
use pagetop_build::{StaticFilesBundle, compile_scss, copy_file, copy_file_replacing, minify_js};
|
|
||||||
|
|
||||||
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
// Regenera `static/` desde cero sólo si hay cambios en `assets/`.
|
StaticFilesBundle::from_scss("./static/scss/bootsier.scss", "bootstrap.min.css")
|
||||||
println!("cargo:rerun-if-changed=assets");
|
.with_name("bootsier_bs")
|
||||||
let _ = std::fs::remove_dir_all("static");
|
|
||||||
|
|
||||||
// CSS: Bootstrap 5.3.8 + AdminLTE 4.0.0 + Bootstrap Icons 1.13.1.
|
|
||||||
compile_scss("assets/bootsier.scss", "static/css/bootsier.min.css")?;
|
|
||||||
|
|
||||||
// JS: Bootstrap bundle.
|
|
||||||
copy_file(
|
|
||||||
"assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js",
|
|
||||||
"static/js/bootsier.bundle.min.js",
|
|
||||||
)?;
|
|
||||||
copy_file(
|
|
||||||
"assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js.map",
|
|
||||||
"static/js/bootsier.bundle.min.js.map",
|
|
||||||
)?;
|
|
||||||
// JS: AdminLTE renombrado a bootsier.extended.min.js.
|
|
||||||
copy_file_replacing(
|
|
||||||
"assets/adminlte-4.0.0/js/adminlte.min.js",
|
|
||||||
"static/js/bootsier.extended.min.js",
|
|
||||||
&[("adminlte.min.js.map", "bootsier.extended.min.js.map")],
|
|
||||||
)?;
|
|
||||||
copy_file(
|
|
||||||
"assets/adminlte-4.0.0/js/adminlte.min.js.map",
|
|
||||||
"static/js/bootsier.extended.min.js.map",
|
|
||||||
)?;
|
|
||||||
// JS: shell de Bootsier.
|
|
||||||
minify_js(
|
|
||||||
"assets/bootsier.shell.js",
|
|
||||||
"static/js/bootsier.shell.min.js",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Fuentes: Bootstrap Icons.
|
|
||||||
copy_file(
|
|
||||||
"assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff2",
|
|
||||||
"static/fonts/bootsier.icons.woff2",
|
|
||||||
)?;
|
|
||||||
copy_file(
|
|
||||||
"assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff",
|
|
||||||
"static/fonts/bootsier.icons.woff",
|
|
||||||
)?;
|
|
||||||
// Fuentes: Source Sans 3 (SIL OFL 1.1).
|
|
||||||
copy_file(
|
|
||||||
"assets/adminlte-4.0.0/fonts/SourceSans3VF-Upright.otf.woff2",
|
|
||||||
"static/fonts/bootsier.font.woff2",
|
|
||||||
)?;
|
|
||||||
copy_file(
|
|
||||||
"assets/adminlte-4.0.0/fonts/SourceSans3VF-Italic.otf.woff2",
|
|
||||||
"static/fonts/bootsier.font.italic.woff2",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Preparación de los paquetes para embeber en el binario.
|
|
||||||
StaticFilesBundle::from_dir("./static/css", None)
|
|
||||||
.with_name("bootsier_css")
|
|
||||||
.build()?;
|
.build()?;
|
||||||
|
StaticFilesBundle::from_dir("./static/js", Some(bootstrap_js_files))
|
||||||
StaticFilesBundle::from_dir("./static/js", Some(only_js_files))
|
|
||||||
.with_name("bootsier_js")
|
.with_name("bootsier_js")
|
||||||
.build()?;
|
|
||||||
|
|
||||||
StaticFilesBundle::from_dir("./static/fonts", None)
|
|
||||||
.with_name("bootsier_fonts")
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Los archivos .map no se embeben en el binario; solo se sirven desde disco en desarrollo.
|
fn bootstrap_js_files(path: &Path) -> bool {
|
||||||
fn only_js_files(path: &Path) -> bool {
|
let bootstrap_js = "bootstrap.bundle.min.js";
|
||||||
path.extension().map_or(false, |ext| ext == "js")
|
// No filtra durante el desarrollo, solo en la compilación "release".
|
||||||
|
env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release"
|
||||||
|
|| path.file_name().is_some_and(|f| f == bootstrap_js)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
//!
|
//!
|
||||||
//! Uso:
|
//! Uso:
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust
|
||||||
//! # use pagetop::prelude::*;
|
//! # use pagetop::prelude::*;
|
||||||
//! use pagetop_bootsier::config;
|
//! use pagetop_bootsier::config;
|
||||||
//!
|
//!
|
||||||
|
|
@ -26,15 +26,12 @@ use serde::Deserialize;
|
||||||
include_config!(SETTINGS: Settings => [
|
include_config!(SETTINGS: Settings => [
|
||||||
// [bootsier]
|
// [bootsier]
|
||||||
"bootsier.max_width" => "1440px",
|
"bootsier.max_width" => "1440px",
|
||||||
// [dev]
|
|
||||||
"dev.bootsier_static_dir" => "",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// Ajustes para la sección [`Bootsier`] de [`SETTINGS`].
|
/// Ajustes para la sección [`Bootsier`] de [`SETTINGS`].
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub bootsier: Bootsier,
|
pub bootsier: Bootsier,
|
||||||
pub dev: Dev,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sección **`[bootsier]`** de la configuración. Forma parte de [`Settings`].
|
/// Sección **`[bootsier]`** de la configuración. Forma parte de [`Settings`].
|
||||||
|
|
@ -43,16 +40,3 @@ pub struct Bootsier {
|
||||||
/// Ancho máximo predeterminado para la página, por ejemplo "100%" o "90rem".
|
/// Ancho máximo predeterminado para la página, por ejemplo "100%" o "90rem".
|
||||||
pub max_width: UnitValue,
|
pub max_width: UnitValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sección **`[dev]`** de la configuración. Forma parte de [`Settings`].
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Dev {
|
|
||||||
/// Directorio raíz de `static/` para servir los archivos estáticos propios de Bootsier.
|
|
||||||
///
|
|
||||||
/// Si se indica una ruta válida, absoluta o relativa al directorio del proyecto o del binario
|
|
||||||
/// en ejecución, los archivos estáticos se servirán desde disco. Útil para poder modificar los
|
|
||||||
/// archivos estáticos mientras la aplicación está en ejecución, sin necesidad de recompilar.
|
|
||||||
///
|
|
||||||
/// Si la cadena está vacía, se ignora este ajuste.
|
|
||||||
pub bootsier_static_dir: String,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
pub fn setup(button: &mut Button) {
|
|
||||||
button
|
|
||||||
.alter_prop(PropsOp::remove_classes("button"))
|
|
||||||
.alter_prop(PropsOp::prepend_classes("btn"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
pub fn render(c: &form::input::Field, cx: &mut Context) -> Result<Markup, ComponentError> {
|
|
||||||
let container_id = c.id();
|
|
||||||
let input_id = container_id.as_deref().map(|id| util::join!(id, "-input"));
|
|
||||||
let floating = c.props().has_class("form-floating");
|
|
||||||
let input_class = if *c.plaintext() {
|
|
||||||
"form-control-plaintext"
|
|
||||||
} else {
|
|
||||||
"form-control"
|
|
||||||
};
|
|
||||||
// La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido, se
|
|
||||||
// fuerza `placeholder=""`.
|
|
||||||
let placeholder = if floating {
|
|
||||||
Some(c.placeholder().lookup(cx).unwrap_or_default())
|
|
||||||
} else {
|
|
||||||
c.placeholder().lookup(cx)
|
|
||||||
};
|
|
||||||
let label = match c.label().lookup(cx) {
|
|
||||||
Some(text) => html! {
|
|
||||||
label for=[input_id.as_deref()] class="form-label" {
|
|
||||||
(text)
|
|
||||||
@if *c.required() {
|
|
||||||
span
|
|
||||||
class="form-required"
|
|
||||||
title=(L10n::l("field_required").using(cx))
|
|
||||||
{
|
|
||||||
"*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => html! {},
|
|
||||||
};
|
|
||||||
Ok(html! {
|
|
||||||
div (c.props()) {
|
|
||||||
@if !floating { (label) }
|
|
||||||
input
|
|
||||||
type=(c.kind())
|
|
||||||
id=[input_id.as_deref()]
|
|
||||||
class=(input_class)
|
|
||||||
name=[c.name().get()]
|
|
||||||
value=[c.value().get()]
|
|
||||||
minlength=[c.minlength().get()]
|
|
||||||
maxlength=[c.maxlength().get()]
|
|
||||||
placeholder=[placeholder]
|
|
||||||
inputmode=[c.inputmode().get()]
|
|
||||||
autocomplete=[c.autocomplete().get()]
|
|
||||||
autofocus[*c.autofocus()]
|
|
||||||
readonly[*c.readonly() || *c.plaintext()]
|
|
||||||
required[*c.required()]
|
|
||||||
disabled[*c.disabled()];
|
|
||||||
@if floating { (label) }
|
|
||||||
@if let Some(description) = c.help_text().lookup(cx) {
|
|
||||||
div class="form-text" { (description) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
pub fn setup(c: &mut form::select::Field) {
|
|
||||||
if c.props().has_class("form-floating") {
|
|
||||||
c.alter_multiple(false);
|
|
||||||
c.alter_rows(None::<u16>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(c: &form::select::Field, cx: &mut Context) -> Result<Markup, ComponentError> {
|
|
||||||
let container_id = c.id();
|
|
||||||
let select_id = container_id.as_deref().map(|id| util::join!(id, "-select"));
|
|
||||||
let floating = c.props().has_class("form-floating");
|
|
||||||
let label = match c.label().lookup(cx) {
|
|
||||||
Some(text) => html! {
|
|
||||||
label for=[select_id.as_deref()] class="form-label" {
|
|
||||||
(text)
|
|
||||||
@if *c.required() {
|
|
||||||
span
|
|
||||||
class="form-required"
|
|
||||||
title=(L10n::l("field_required").using(cx))
|
|
||||||
{
|
|
||||||
"*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => html! {},
|
|
||||||
};
|
|
||||||
Ok(html! {
|
|
||||||
div (c.props()) {
|
|
||||||
@if !floating { (label) }
|
|
||||||
select
|
|
||||||
id=[select_id.as_deref()]
|
|
||||||
class="form-select"
|
|
||||||
name=[c.name().get()]
|
|
||||||
multiple[*c.multiple()]
|
|
||||||
size=[c.rows().get()]
|
|
||||||
autocomplete=[c.autocomplete().get()]
|
|
||||||
autofocus[*c.autofocus()]
|
|
||||||
required[*c.required()]
|
|
||||||
disabled[*c.disabled()]
|
|
||||||
{
|
|
||||||
@for entry in c.entries() {
|
|
||||||
@match entry {
|
|
||||||
form::select::Entry::Item(opt) => {
|
|
||||||
option
|
|
||||||
value=(opt.value().as_str().unwrap_or(""))
|
|
||||||
selected[*opt.selected()]
|
|
||||||
disabled[*opt.disabled()]
|
|
||||||
{
|
|
||||||
(opt.label().using(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
form::select::Entry::Group(group) => {
|
|
||||||
optgroup
|
|
||||||
label=(group.label().using(cx))
|
|
||||||
disabled[*group.disabled()]
|
|
||||||
{
|
|
||||||
@for opt in group.items() {
|
|
||||||
option
|
|
||||||
value=(opt.value().as_str().unwrap_or(""))
|
|
||||||
selected[*opt.selected()]
|
|
||||||
disabled[*opt.disabled()]
|
|
||||||
{
|
|
||||||
(opt.label().using(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if floating { (label) }
|
|
||||||
@if let Some(description) = c.help_text().lookup(cx) {
|
|
||||||
div class="form-text" { (description) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
pub fn setup(c: &mut form::Textarea) {
|
|
||||||
if c.props().has_class("form-floating") {
|
|
||||||
c.alter_rows(None::<u16>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(c: &form::Textarea, cx: &mut Context) -> Result<Markup, ComponentError> {
|
|
||||||
let container_id = c.id();
|
|
||||||
let textarea_id = container_id
|
|
||||||
.as_deref()
|
|
||||||
.map(|id| util::join!(id, "-textarea"));
|
|
||||||
let floating = c.props().has_class("form-floating");
|
|
||||||
// La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido se
|
|
||||||
// fuerza `placeholder=""`.
|
|
||||||
let placeholder = if floating {
|
|
||||||
Some(c.placeholder().lookup(cx).unwrap_or_default())
|
|
||||||
} else {
|
|
||||||
c.placeholder().lookup(cx)
|
|
||||||
};
|
|
||||||
let label = match c.label().lookup(cx) {
|
|
||||||
Some(text) => html! {
|
|
||||||
label for=[textarea_id.as_deref()] class="form-label" {
|
|
||||||
(text)
|
|
||||||
@if *c.required() {
|
|
||||||
span
|
|
||||||
class="form-required"
|
|
||||||
title=(L10n::l("field_required").using(cx))
|
|
||||||
{
|
|
||||||
"*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => html! {},
|
|
||||||
};
|
|
||||||
Ok(html! {
|
|
||||||
div (c.props()) {
|
|
||||||
@if !floating { (label) }
|
|
||||||
textarea
|
|
||||||
id=[textarea_id.as_deref()]
|
|
||||||
class="form-control"
|
|
||||||
name=[c.name().get()]
|
|
||||||
rows=[c.rows().get()]
|
|
||||||
minlength=[c.minlength().get()]
|
|
||||||
maxlength=[c.maxlength().get()]
|
|
||||||
placeholder=[placeholder]
|
|
||||||
autocomplete=[c.autocomplete().get()]
|
|
||||||
autofocus[*c.autofocus()]
|
|
||||||
readonly[*c.readonly()]
|
|
||||||
required[*c.required()]
|
|
||||||
disabled[*c.disabled()]
|
|
||||||
{
|
|
||||||
@if let Some(value) = c.value().get() { (value) }
|
|
||||||
}
|
|
||||||
@if floating { (label) }
|
|
||||||
@if let Some(description) = c.help_text().lookup(cx) {
|
|
||||||
div class="form-text" { (description) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
[](https://crates.io/crates/pagetop-bootsier)
|
[](https://crates.io/crates/pagetop-bootsier)
|
||||||
[](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier#licencia)
|
[](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier#licencia)
|
||||||
|
|
||||||
|
<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Sobre PageTop
|
## Sobre PageTop
|
||||||
|
|
@ -18,7 +19,8 @@
|
||||||
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
configurables, basadas en HTML, CSS y JavaScript.
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
|
|
||||||
## Guía rápida
|
|
||||||
|
# ⚡️ Guía rápida
|
||||||
|
|
||||||
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`:
|
||||||
|
|
||||||
|
|
@ -44,6 +46,11 @@ impl Extension for MyApp {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pagetop::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
Application::prepare(&MyApp).run()?.await
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Y **selecciona el tema en la configuración** de la aplicación:
|
Y **selecciona el tema en la configuración** de la aplicación:
|
||||||
|
|
@ -89,11 +96,32 @@ pub mod config;
|
||||||
|
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
|
||||||
mod handlers {
|
/// Plantillas que Bootsier añade.
|
||||||
pub mod button;
|
#[derive(AutoDefault)]
|
||||||
pub mod input;
|
pub enum BootsierTemplate {
|
||||||
pub mod select;
|
/// Plantilla predeterminada de Bootsier.
|
||||||
pub mod textarea;
|
#[default]
|
||||||
|
Standard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Template for BootsierTemplate {
|
||||||
|
fn render(&'static self, cx: &mut Context) -> Markup {
|
||||||
|
match self {
|
||||||
|
Self::Standard => theme::Container::new()
|
||||||
|
.with_classes(ClassesOp::Add, "container-wrapper")
|
||||||
|
.with_width(theme::container::Width::FluidMax(
|
||||||
|
config::SETTINGS.bootsier.max_width,
|
||||||
|
))
|
||||||
|
.with_child(Html::with(|cx| {
|
||||||
|
html! {
|
||||||
|
(DefaultRegion::Header.render(cx))
|
||||||
|
(DefaultRegion::Content.render(cx))
|
||||||
|
(DefaultRegion::Footer.render(cx))
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
.render(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementa el tema.
|
/// Implementa el tema.
|
||||||
|
|
@ -113,17 +141,8 @@ impl Extension for Bootsier {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_router(&self, router: Router) -> Router {
|
fn configure_router(&self, router: Router) -> Router {
|
||||||
let base = &config::SETTINGS.dev.bootsier_static_dir;
|
serve_static_files!(router, [bootsier_bs] => "/bootsier/bs");
|
||||||
let subdir = |s: &str| {
|
serve_static_files!(router, [bootsier_js] => "/bootsier/js");
|
||||||
if base.is_empty() {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
format!("{base}/{s}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
serve_static_files!(router, [subdir("css"), bootsier_css] => "/bootsier/css");
|
|
||||||
serve_static_files!(router, [subdir("js"), bootsier_js] => "/bootsier/js");
|
|
||||||
serve_static_files!(router, [subdir("fonts"), bootsier_fonts] => "/bootsier/fonts");
|
|
||||||
router
|
router
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,38 +153,16 @@ impl Theme for Bootsier {
|
||||||
&BootsierTemplate::Standard
|
&BootsierTemplate::Standard
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_component(
|
|
||||||
&self,
|
|
||||||
component: &mut dyn Component,
|
|
||||||
cx: &mut Context,
|
|
||||||
) -> Option<Result<Markup, ComponentError>> {
|
|
||||||
setup_component!(component, {
|
|
||||||
Button => |c| handlers::button::setup(c),
|
|
||||||
form::select::Field => |c| handlers::select::setup(c),
|
|
||||||
form::Textarea => |c| handlers::textarea::setup(c),
|
|
||||||
});
|
|
||||||
render_component!(component, {
|
|
||||||
form::input::Field => |c| handlers::input::render(c, cx),
|
|
||||||
form::select::Field => |c| handlers::select::render(c, cx),
|
|
||||||
form::Textarea => |c| handlers::textarea::render(c, cx),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn before_render_page_body(&self, page: &mut Page) {
|
fn before_render_page_body(&self, page: &mut Page) {
|
||||||
page.alter_assets(AssetsOp::AddStyleSheet(
|
page.alter_assets(AssetsOp::AddStyleSheet(
|
||||||
StyleSheet::from("/bootsier/css/bootsier.min.css")
|
StyleSheet::from("/bootsier/bs/bootstrap.min.css")
|
||||||
.with_version(ADMINLTE_VERSION)
|
|
||||||
.with_weight(-90),
|
|
||||||
))
|
|
||||||
.alter_assets(AssetsOp::AddJavaScript(
|
|
||||||
JavaScript::defer("/bootsier/js/bootsier.bundle.min.js")
|
|
||||||
.with_version(BOOTSTRAP_VERSION)
|
.with_version(BOOTSTRAP_VERSION)
|
||||||
.with_weight(-90),
|
.with_weight(-90),
|
||||||
))
|
))
|
||||||
.alter_assets(AssetsOp::AddJavaScript(
|
.alter_assets(AssetsOp::AddJavaScript(
|
||||||
JavaScript::defer("/bootsier/js/bootsier.extended.min.js")
|
JavaScript::defer("/bootsier/js/bootstrap.bundle.min.js")
|
||||||
.with_version(ADMINLTE_VERSION)
|
.with_version(BOOTSTRAP_VERSION)
|
||||||
.with_weight(-89),
|
.with_weight(-90),
|
||||||
))
|
))
|
||||||
.alter_child_in(
|
.alter_child_in(
|
||||||
&DefaultRegion::Footer,
|
&DefaultRegion::Footer,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub mod classes;
|
||||||
|
|
||||||
// Button.
|
// Button.
|
||||||
mod button;
|
mod button;
|
||||||
pub use button::{Button, ButtonAction};
|
pub use button::Button;
|
||||||
|
|
||||||
// Container.
|
// Container.
|
||||||
pub mod container;
|
pub mod container;
|
||||||
|
|
@ -27,12 +27,6 @@ pub use dropdown::Dropdown;
|
||||||
pub mod form;
|
pub mod form;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use form::Form;
|
pub use form::Form;
|
||||||
#[doc(hidden)]
|
|
||||||
pub use form::input::InputBootsier;
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use form::select::SelectBootsier;
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use form::textarea::TextareaBootsier;
|
|
||||||
|
|
||||||
// Image.
|
// Image.
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,6 @@ pub use border::BorderColor;
|
||||||
|
|
||||||
mod rounded;
|
mod rounded;
|
||||||
pub use rounded::RoundedRadius;
|
pub use rounded::RoundedRadius;
|
||||||
|
|
||||||
|
mod button;
|
||||||
|
pub use button::{ButtonAction, ButtonColor, ButtonSize};
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl BorderColor {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary");
|
/// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary");
|
||||||
/// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle");
|
/// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle");
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ impl BreakPoint {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let bp = BreakPoint::MD;
|
/// let bp = BreakPoint::MD;
|
||||||
/// assert_eq!(bp.class_with("col", ""), "col-md");
|
/// assert_eq!(bp.class_with("col", ""), "col-md");
|
||||||
|
|
|
||||||
145
extensions/pagetop-bootsier/src/theme/attrs/button.rs
Normal file
145
extensions/pagetop-bootsier/src/theme/attrs/button.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::theme::attrs::Color;
|
||||||
|
|
||||||
|
// **< ButtonAction >*********************************************************************************
|
||||||
|
|
||||||
|
/// Comportamiento de un [`Button`](crate::theme::Button) al activarse.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ButtonAction {
|
||||||
|
/// Envía un formulario al servidor. Es el **tipo por defecto**.
|
||||||
|
#[default]
|
||||||
|
Submit,
|
||||||
|
/// Restablece todos los campos de un formulario a sus valores iniciales.
|
||||||
|
Reset,
|
||||||
|
/// Botón de propósito general, sin efecto predeterminado. Su comportamiento podría definirse
|
||||||
|
/// mediante JavaScript.
|
||||||
|
Plain,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ButtonAction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
ButtonAction::Submit => "submit",
|
||||||
|
ButtonAction::Reset => "reset",
|
||||||
|
ButtonAction::Plain => "button",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< ButtonColor >********************************************************************************
|
||||||
|
|
||||||
|
/// Esquema de color para [`Button`](crate::theme::Button).
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ButtonColor {
|
||||||
|
/// No define ninguna clase.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Genera la clase `btn-{color}` (botón sólido).
|
||||||
|
Background(Color),
|
||||||
|
/// Genera la clase `btn-outline-{color}` (fondo transparente con contorno coloreado).
|
||||||
|
Outline(Color),
|
||||||
|
/// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto.
|
||||||
|
Link,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonColor {
|
||||||
|
const BTN_PREFIX: &str = "btn-";
|
||||||
|
const BTN_OUTLINE_PREFIX: &str = "btn-outline-";
|
||||||
|
const BTN_LINK: &str = "btn-link";
|
||||||
|
|
||||||
|
/// Añade la clase `btn-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
if let Self::Default = self {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::Background(c) => {
|
||||||
|
classes.push_str(Self::BTN_PREFIX);
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
Self::Outline(c) => {
|
||||||
|
classes.push_str(Self::BTN_OUTLINE_PREFIX);
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
Self::Link => classes.push_str(Self::BTN_LINK),
|
||||||
|
Self::Default => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `btn-*` correspondiente al color del botón.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// assert_eq!(
|
||||||
|
/// ButtonColor::Background(Color::Primary).to_class(),
|
||||||
|
/// "btn-primary"
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// ButtonColor::Outline(Color::Danger).to_class(),
|
||||||
|
/// "btn-outline-danger"
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(ButtonColor::Link.to_class(), "btn-link");
|
||||||
|
/// assert_eq!(ButtonColor::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut class = String::new();
|
||||||
|
self.push_class(&mut class);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< ButtonSize >*********************************************************************************
|
||||||
|
|
||||||
|
/// Tamaño visual de un botón.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ButtonSize {
|
||||||
|
/// Tamaño por defecto del tema (no añade clase).
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Botón compacto.
|
||||||
|
Small,
|
||||||
|
/// Botón grande.
|
||||||
|
Large,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonSize {
|
||||||
|
const BTN_SM: &str = "btn-sm";
|
||||||
|
const BTN_LG: &str = "btn-lg";
|
||||||
|
|
||||||
|
/// Añade la clase de tamaño `btn-sm` o `btn-lg` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let class = match self {
|
||||||
|
Self::Default => return,
|
||||||
|
Self::Small => Self::BTN_SM,
|
||||||
|
Self::Large => Self::BTN_LG,
|
||||||
|
};
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `btn-sm` o `btn-lg` correspondiente al tamaño del botón.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// assert_eq!(ButtonSize::Small.to_class(), "btn-sm");
|
||||||
|
/// assert_eq!(ButtonSize::Large.to_class(), "btn-lg");
|
||||||
|
/// assert_eq!(ButtonSize::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut class = String::new();
|
||||||
|
self.push_class(&mut class);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ impl Color {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(Color::Primary.to_class(), "primary");
|
/// assert_eq!(Color::Primary.to_class(), "primary");
|
||||||
/// assert_eq!(Color::Danger.to_class(), "danger");
|
/// assert_eq!(Color::Danger.to_class(), "danger");
|
||||||
|
|
@ -123,7 +123,7 @@ impl Opacity {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100");
|
/// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100");
|
||||||
/// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50");
|
/// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50");
|
||||||
|
|
@ -155,7 +155,7 @@ impl Opacity {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(Opacity::Opaque.to_class(), "opacity-100");
|
/// assert_eq!(Opacity::Opaque.to_class(), "opacity-100");
|
||||||
/// assert_eq!(Opacity::Half.to_class(), "opacity-50");
|
/// assert_eq!(Opacity::Half.to_class(), "opacity-50");
|
||||||
|
|
@ -236,7 +236,7 @@ impl ColorBg {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(ColorBg::Body.to_class(), "bg-body");
|
/// assert_eq!(ColorBg::Body.to_class(), "bg-body");
|
||||||
/// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary");
|
/// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary");
|
||||||
|
|
@ -320,7 +320,7 @@ impl ColorText {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(ColorText::Body.to_class(), "text-body");
|
/// assert_eq!(ColorText::Body.to_class(), "text-body");
|
||||||
/// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary");
|
/// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary");
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl ScaleSize {
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(ScaleSize::Auto.class_with("border"), "border");
|
/// assert_eq!(ScaleSize::Auto.class_with("border"), "border");
|
||||||
/// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0");
|
/// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0");
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ impl RoundedRadius {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2");
|
/// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2");
|
||||||
/// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0");
|
/// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0");
|
||||||
|
|
@ -102,7 +102,7 @@ impl RoundedRadius {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(RoundedRadius::Default.to_class(), "rounded");
|
/// assert_eq!(RoundedRadius::Default.to_class(), "rounded");
|
||||||
/// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0");
|
/// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0");
|
||||||
|
|
|
||||||
|
|
@ -1 +1,214 @@
|
||||||
pub use pagetop::base::component::{Button, ButtonAction};
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::theme::{ButtonAction, ButtonColor, ButtonSize};
|
||||||
|
|
||||||
|
/// Componente para crear un **botón**.
|
||||||
|
///
|
||||||
|
/// Renderiza un botón con soporte para las variantes disponibles en [`ButtonAction`] (`submit`,
|
||||||
|
/// `reset` y botón genérico) y con la variedad de estilos del tema a través de [`ButtonColor`] y
|
||||||
|
/// [`ButtonSize`].
|
||||||
|
///
|
||||||
|
/// El comportamiento del botón se establece al crearlo:
|
||||||
|
///
|
||||||
|
/// - [`Button::submit()`]: botón de envío (por defecto).
|
||||||
|
/// - [`Button::reset()`]: botón de restablecimiento de valores.
|
||||||
|
/// - [`Button::plain()`]: botón genérico sin comportamiento predeterminado.
|
||||||
|
///
|
||||||
|
/// El botón puede usarse dentro o fuera de un formulario.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pagetop::prelude::*;
|
||||||
|
/// use pagetop_bootsier::theme::*;
|
||||||
|
///
|
||||||
|
/// let save = Button::submit(L10n::n("Save"))
|
||||||
|
/// .with_color(ButtonColor::Background(Color::Primary));
|
||||||
|
///
|
||||||
|
/// let cancel = Button::plain(L10n::n("Cancel"))
|
||||||
|
/// .with_color(ButtonColor::Outline(Color::Secondary));
|
||||||
|
///
|
||||||
|
/// let clear = Button::reset(L10n::n("Clear"))
|
||||||
|
/// .with_size(ButtonSize::Small);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Cuando el botón activa el envío, el navegador incluye el par `name=value` en los datos del
|
||||||
|
/// formulario **sólo si** tiene el atributo `name` definido. Es la forma habitual de identificar
|
||||||
|
/// cuál de los botones de envío fue pulsado. En el servidor se deserializa como `Option<String>`:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[derive(serde::Deserialize)]
|
||||||
|
/// struct FormData {
|
||||||
|
/// #[serde(default)]
|
||||||
|
/// action: Option<String>, // p. ej., "save" o "delete"; `None` si el botón no tenía `name`.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
|
pub struct Button {
|
||||||
|
#[getters(skip)]
|
||||||
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del botón.
|
||||||
|
classes: Classes,
|
||||||
|
/// Devuelve el comportamiento del botón al activarse.
|
||||||
|
kind: ButtonAction,
|
||||||
|
/// Devuelve el esquema de color del botón.
|
||||||
|
color: ButtonColor,
|
||||||
|
/// Devuelve el tamaño visual del botón.
|
||||||
|
size: ButtonSize,
|
||||||
|
/// Devuelve el nombre del botón.
|
||||||
|
name: AttrName,
|
||||||
|
/// Devuelve el valor del botón.
|
||||||
|
value: AttrValue,
|
||||||
|
/// Devuelve la etiqueta del botón.
|
||||||
|
label: Attr<L10n>,
|
||||||
|
/// Devuelve si el botón recibe el foco automáticamente al cargar la página.
|
||||||
|
autofocus: bool,
|
||||||
|
/// Devuelve si el botón está deshabilitado.
|
||||||
|
disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Button {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<String> {
|
||||||
|
self.id.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&mut self, _cx: &Context) {
|
||||||
|
let mut classes = "btn".to_string();
|
||||||
|
(*self.color()).push_class(&mut classes);
|
||||||
|
(*self.size()).push_class(&mut classes);
|
||||||
|
self.alter_classes(ClassesOp::Prepend, classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
Ok(html! {
|
||||||
|
button
|
||||||
|
id=[self.id()]
|
||||||
|
type=(self.kind())
|
||||||
|
class=[self.classes().get()]
|
||||||
|
name=[self.name().get()]
|
||||||
|
value=[self.value().get()]
|
||||||
|
autofocus[*self.autofocus()]
|
||||||
|
disabled[*self.disabled()]
|
||||||
|
{
|
||||||
|
@if let Some(label) = self.label().lookup(cx) {
|
||||||
|
(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
/// Crea un botón de **envío** (`type="submit"`).
|
||||||
|
///
|
||||||
|
/// Es la acción predeterminada al pulsar un botón en la mayoría de los formularios: envía los
|
||||||
|
/// datos al servidor.
|
||||||
|
pub fn submit(label: L10n) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ButtonAction::Submit,
|
||||||
|
label: Attr::some(label),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un botón de **restablecimiento** (`type="reset"`).
|
||||||
|
///
|
||||||
|
/// Al pulsarlo, devuelve todos los campos del formulario a sus valores iniciales.
|
||||||
|
pub fn reset(label: L10n) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ButtonAction::Reset,
|
||||||
|
label: Attr::some(label),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un **botón genérico** (`type="button"`).
|
||||||
|
///
|
||||||
|
/// No tiene un comportamiento predeterminado sobre el formulario. Su comportamiento puede
|
||||||
|
/// definirse mediante JavaScript.
|
||||||
|
pub fn plain(label: L10n) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ButtonAction::Plain,
|
||||||
|
label: Attr::some(label),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Button BUILDER >*************************************************************************
|
||||||
|
|
||||||
|
/// Establece el identificador único (`id`) del botón.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
|
self.id.alter_id(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modifica la lista de clases CSS aplicadas al botón.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
|
self.classes.alter_classes(op, classes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el esquema de color del botón.
|
||||||
|
///
|
||||||
|
/// Usa [`ButtonColor::Background`] para botones sólidos o [`ButtonColor::Outline`] para
|
||||||
|
/// variantes con contorno.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_color(mut self, color: ButtonColor) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el tamaño visual del botón.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_size(mut self, size: ButtonSize) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el nombre del botón (atributo `name`).
|
||||||
|
///
|
||||||
|
/// Cuando el formulario tiene varios botones de envío, el navegador incluye en el envío el par
|
||||||
|
/// `name=value` sólo del botón que activó el formulario. Permite identificar cuál fue pulsado.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
|
||||||
|
self.name.alter_name(name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el valor del botón (atributo `value`).
|
||||||
|
///
|
||||||
|
/// Es el dato que el navegador transmite al servidor junto con el `name` cuando este botón
|
||||||
|
/// activa el envío. Útil para distinguir entre varios botones de envío en un mismo formulario.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
|
||||||
|
self.value.alter_str(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece o elimina la etiqueta visible del botón (basta pasar `None` para quitarla).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_label(mut self, label: impl Into<Option<L10n>>) -> Self {
|
||||||
|
self.label.alter_opt(label.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el botón recibe el foco automáticamente al cargar la página.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_autofocus(mut self, autofocus: bool) -> Self {
|
||||||
|
self.autofocus = autofocus;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el botón está deshabilitado.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_disabled(mut self, disabled: bool) -> Self {
|
||||||
|
self.disabled = disabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@
|
||||||
mod color;
|
mod color;
|
||||||
pub use color::{Background, Text};
|
pub use color::{Background, Text};
|
||||||
|
|
||||||
mod button;
|
|
||||||
pub use button::{ButtonColor, ButtonSize};
|
|
||||||
|
|
||||||
mod border;
|
mod border;
|
||||||
pub use border::Border;
|
pub use border::Border;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ use crate::theme::attrs::{BorderColor, Opacity, ScaleSize, Side};
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// // Borde global.
|
/// // Borde global.
|
||||||
|
|
@ -145,7 +145,7 @@ impl Border {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// // Convertir explícitamente con `From::from`:
|
/// // Convertir explícitamente con `From::from`:
|
||||||
/// let b = classes::Border::from(ScaleSize::Two);
|
/// let b = classes::Border::from(ScaleSize::Two);
|
||||||
|
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
use crate::theme::attrs::Color;
|
|
||||||
|
|
||||||
// **< ButtonColor >********************************************************************************
|
|
||||||
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
enum ButtonColorStyle {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Solid,
|
|
||||||
Outline,
|
|
||||||
Link,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clases para establecer el **color y estilo** de los botones.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// use pagetop::prelude::*;
|
|
||||||
/// use pagetop_bootsier::theme::*;
|
|
||||||
///
|
|
||||||
/// // Botón sólido.
|
|
||||||
/// let save = Button::submit(L10n::n("Save"))
|
|
||||||
/// .with_prop(PropsOp::add_classes(classes::ButtonColor::solid(Color::Primary)));
|
|
||||||
///
|
|
||||||
/// // Botón con contorno.
|
|
||||||
/// let cancel = Button::plain(L10n::n("Cancel"))
|
|
||||||
/// .with_prop(PropsOp::add_classes(classes::ButtonColor::outline(Color::Secondary)));
|
|
||||||
///
|
|
||||||
/// // Botón tipo enlace.
|
|
||||||
/// let back = Button::plain(L10n::n("Back"))
|
|
||||||
/// .with_prop(PropsOp::add_classes(classes::ButtonColor::link()));
|
|
||||||
/// ```
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct ButtonColor {
|
|
||||||
style: ButtonColorStyle,
|
|
||||||
color: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonColor {
|
|
||||||
/// Sin clase de color (estilo por defecto del tema).
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Botón sólido: genera la clase `btn-{color}`.
|
|
||||||
pub fn solid(color: Color) -> Self {
|
|
||||||
Self {
|
|
||||||
style: ButtonColorStyle::Solid,
|
|
||||||
color,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Botón con contorno: genera la clase `btn-outline-{color}`.
|
|
||||||
pub fn outline(color: Color) -> Self {
|
|
||||||
Self {
|
|
||||||
style: ButtonColorStyle::Outline,
|
|
||||||
color,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Botón tipo enlace: genera la clase `btn-link`.
|
|
||||||
pub fn link() -> Self {
|
|
||||||
Self {
|
|
||||||
style: ButtonColorStyle::Link,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonColor BUILDER >********************************************************************
|
|
||||||
|
|
||||||
/// Cambia el color aplicado al botón (`btn-*` o `btn-outline-*`).
|
|
||||||
pub fn with_color(mut self, color: Color) -> Self {
|
|
||||||
self.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonColor HELPERS >********************************************************************
|
|
||||||
|
|
||||||
/// Devuelve la clase `btn-*` correspondiente al color del botón.
|
|
||||||
///
|
|
||||||
/// Si no se ha definido ningún estilo, devuelve `""`.
|
|
||||||
pub fn to_class(self) -> String {
|
|
||||||
match self.style {
|
|
||||||
ButtonColorStyle::None => String::new(),
|
|
||||||
ButtonColorStyle::Solid => format!("btn-{}", self.color.as_str()),
|
|
||||||
ButtonColorStyle::Outline => format!("btn-outline-{}", self.color.as_str()),
|
|
||||||
ButtonColorStyle::Link => "btn-link".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<CowStr> for ButtonColor {
|
|
||||||
fn into(self) -> CowStr {
|
|
||||||
self.to_class().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonSize >*********************************************************************************
|
|
||||||
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
enum ButtonSizeVariant {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Small,
|
|
||||||
Large,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clases para establecer el **tamaño** de los botones.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// use pagetop::prelude::*;
|
|
||||||
/// use pagetop_bootsier::theme::*;
|
|
||||||
///
|
|
||||||
/// let small = Button::submit(L10n::n("Save"))
|
|
||||||
/// .with_prop(PropsOp::add_classes(classes::ButtonSize::small()));
|
|
||||||
///
|
|
||||||
/// let large = Button::submit(L10n::n("Save"))
|
|
||||||
/// .with_prop(PropsOp::add_classes(classes::ButtonSize::large()));
|
|
||||||
/// ```
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct ButtonSize {
|
|
||||||
size: ButtonSizeVariant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonSize {
|
|
||||||
/// Sin clase de tamaño (tamaño por defecto del tema).
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Botón compacto: genera la clase `btn-sm`.
|
|
||||||
pub fn small() -> Self {
|
|
||||||
Self {
|
|
||||||
size: ButtonSizeVariant::Small,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Botón grande: genera la clase `btn-lg`.
|
|
||||||
pub fn large() -> Self {
|
|
||||||
Self {
|
|
||||||
size: ButtonSizeVariant::Large,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonSize HELPERS >*********************************************************************
|
|
||||||
|
|
||||||
/// Devuelve la clase `btn-sm` o `btn-lg` correspondiente al tamaño del botón.
|
|
||||||
///
|
|
||||||
/// Si no se ha definido ningún tamaño, devuelve `""`.
|
|
||||||
pub fn to_class(self) -> String {
|
|
||||||
match self.size {
|
|
||||||
ButtonSizeVariant::None => String::new(),
|
|
||||||
ButtonSizeVariant::Small => "btn-sm".to_owned(),
|
|
||||||
ButtonSizeVariant::Large => "btn-lg".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<CowStr> for ButtonSize {
|
|
||||||
fn into(self) -> CowStr {
|
|
||||||
self.to_class().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::theme::attrs::{ScaleSize, Side};
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let m = classes::Margin::with(Side::Top, ScaleSize::Three);
|
/// let m = classes::Margin::with(Side::Top, ScaleSize::Three);
|
||||||
|
|
@ -97,7 +97,7 @@ impl Margin {
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two);
|
/// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use crate::theme::attrs::RoundedRadius;
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// // Radio global:
|
/// // Radio global:
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::theme::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let main = Container::main()
|
/// let main = Container::main()
|
||||||
|
|
@ -20,8 +20,10 @@ use crate::theme::*;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS asociadas al contenedor.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve el tipo semántico del contenedor.
|
/// Devuelve el tipo semántico del contenedor.
|
||||||
container_kind: container::Kind,
|
container_kind: container::Kind,
|
||||||
/// Devuelve el comportamiento para el ancho del contenedor.
|
/// Devuelve el comportamiento para el ancho del contenedor.
|
||||||
|
|
@ -36,11 +38,11 @@ impl Component for Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes(self.container_width().to_class()));
|
self.alter_classes(ClassesOp::Prepend, self.container_width().to_class());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
|
@ -56,32 +58,32 @@ impl Component for Container {
|
||||||
};
|
};
|
||||||
Ok(match self.container_kind() {
|
Ok(match self.container_kind() {
|
||||||
container::Kind::Default => html! {
|
container::Kind::Default => html! {
|
||||||
div (self.props()) style=[style] {
|
div id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||||
(output)
|
(output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container::Kind::Main => html! {
|
container::Kind::Main => html! {
|
||||||
main (self.props()) style=[style] {
|
main id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||||
(output)
|
(output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container::Kind::Header => html! {
|
container::Kind::Header => html! {
|
||||||
header (self.props()) style=[style] {
|
header id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||||
(output)
|
(output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container::Kind::Footer => html! {
|
container::Kind::Footer => html! {
|
||||||
footer (self.props()) style=[style] {
|
footer id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||||
(output)
|
(output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container::Kind::Section => html! {
|
container::Kind::Section => html! {
|
||||||
section (self.props()) style=[style] {
|
section id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||||
(output)
|
(output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container::Kind::Article => html! {
|
container::Kind::Article => html! {
|
||||||
article (self.props()) style=[style] {
|
article id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||||
(output)
|
(output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -132,14 +134,14 @@ impl Container {
|
||||||
|
|
||||||
// **< Container BUILDER >**********************************************************************
|
// **< Container BUILDER >**********************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del contenedor.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al contenedor.
|
||||||
///
|
///
|
||||||
/// También acepta clases predefinidas para:
|
/// También acepta clases predefinidas para:
|
||||||
///
|
///
|
||||||
|
|
@ -148,8 +150,8 @@ impl Container {
|
||||||
/// - Establecer bordes ([`classes::Border`]).
|
/// - Establecer bordes ([`classes::Border`]).
|
||||||
/// - Redondear las esquinas ([`classes::Rounded`]).
|
/// - Redondear las esquinas ([`classes::Rounded`]).
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ use crate::theme::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// use pagetop::prelude::*;
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let dd = Dropdown::new()
|
/// let dd = Dropdown::new()
|
||||||
/// .with_title(L10n::n("Menu"))
|
/// .with_title(L10n::n("Menu"))
|
||||||
/// .with_button_color(classes::ButtonColor::solid(Color::Secondary))
|
/// .with_button_color(ButtonColor::Background(Color::Secondary))
|
||||||
/// .with_auto_close(dropdown::AutoClose::ClickableInside)
|
/// .with_auto_close(dropdown::AutoClose::ClickableInside)
|
||||||
/// .with_direction(dropdown::Direction::Dropend)
|
/// .with_direction(dropdown::Direction::Dropend)
|
||||||
/// .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into()))
|
/// .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into()))
|
||||||
|
|
@ -38,14 +38,16 @@ use crate::theme::*;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Dropdown {
|
pub struct Dropdown {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS asociadas al menú desplegable.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve el título del menú desplegable.
|
/// Devuelve el título del menú desplegable.
|
||||||
title: L10n,
|
title: L10n,
|
||||||
/// Devuelve el tamaño configurado del botón.
|
/// Devuelve el tamaño configurado del botón.
|
||||||
button_size: classes::ButtonSize,
|
button_size: ButtonSize,
|
||||||
/// Devuelve el color/estilo configurado del botón.
|
/// Devuelve el color/estilo configurado del botón.
|
||||||
button_color: classes::ButtonColor,
|
button_color: ButtonColor,
|
||||||
/// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*).
|
/// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*).
|
||||||
button_split: bool,
|
button_split: bool,
|
||||||
/// Devuelve si el botón del menú está integrado en un grupo de botones.
|
/// Devuelve si el botón del menú está integrado en un grupo de botones.
|
||||||
|
|
@ -68,13 +70,14 @@ impl Component for Dropdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes(
|
self.alter_classes(
|
||||||
|
ClassesOp::Prepend,
|
||||||
self.direction().class_with(*self.button_grouped()),
|
self.direction().class_with(*self.button_grouped()),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
|
@ -88,25 +91,23 @@ impl Component for Dropdown {
|
||||||
let title = self.title().using(cx);
|
let title = self.title().using(cx);
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
div (self.props()) {
|
div id=[self.id()] class=[self.classes().get()] {
|
||||||
@if !title.is_empty() {
|
@if !title.is_empty() {
|
||||||
@let btn_base = {
|
@let mut btn_classes = Classes::new({
|
||||||
let size = self.button_size().to_class();
|
let mut classes = "btn".to_string();
|
||||||
let color = self.button_color().to_class();
|
self.button_size().push_class(&mut classes);
|
||||||
let mut classes = String::from("btn");
|
self.button_color().push_class(&mut classes);
|
||||||
if !size.is_empty() { classes.push(' '); classes.push_str(&size); }
|
|
||||||
if !color.is_empty() { classes.push(' '); classes.push_str(&color); }
|
|
||||||
classes
|
classes
|
||||||
};
|
});
|
||||||
@let pos = self.menu_position();
|
@let pos = self.menu_position();
|
||||||
@let offset = pos.data_offset();
|
@let offset = pos.data_offset();
|
||||||
@let reference = pos.data_reference();
|
@let reference = pos.data_reference();
|
||||||
@let auto_close = self.auto_close.as_str();
|
@let auto_close = self.auto_close.as_str();
|
||||||
@let menu_classes = {
|
@let menu_classes = Classes::new({
|
||||||
let mut classes = "dropdown-menu".to_string();
|
let mut classes = "dropdown-menu".to_string();
|
||||||
self.menu_align().push_class(&mut classes);
|
self.menu_align().push_class(&mut classes);
|
||||||
classes
|
classes
|
||||||
};
|
});
|
||||||
|
|
||||||
// Renderizado en modo split (dos botones) o simple (un botón).
|
// Renderizado en modo split (dos botones) o simple (un botón).
|
||||||
@if *self.button_split() {
|
@if *self.button_split() {
|
||||||
|
|
@ -114,18 +115,18 @@ impl Component for Dropdown {
|
||||||
@let btn = html! {
|
@let btn = html! {
|
||||||
button
|
button
|
||||||
type="button"
|
type="button"
|
||||||
class=(&btn_base)
|
class=[btn_classes.get()]
|
||||||
{
|
{
|
||||||
(title)
|
(title)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Botón *toggle* que abre/cierra el menú asociado.
|
// Botón *toggle* que abre/cierra el menú asociado.
|
||||||
@let btn_toggle_classes =
|
|
||||||
util::join!(&btn_base, " dropdown-toggle dropdown-toggle-split");
|
|
||||||
@let btn_toggle = html! {
|
@let btn_toggle = html! {
|
||||||
button
|
button
|
||||||
type="button"
|
type="button"
|
||||||
class=(&btn_toggle_classes)
|
class=[btn_classes.alter_classes(
|
||||||
|
ClassesOp::Add, "dropdown-toggle dropdown-toggle-split"
|
||||||
|
).get()]
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
data-bs-offset=[offset]
|
data-bs-offset=[offset]
|
||||||
data-bs-reference=[reference]
|
data-bs-reference=[reference]
|
||||||
|
|
@ -141,21 +142,22 @@ impl Component for Dropdown {
|
||||||
@match self.direction() {
|
@match self.direction() {
|
||||||
dropdown::Direction::Dropstart => {
|
dropdown::Direction::Dropstart => {
|
||||||
(btn_toggle)
|
(btn_toggle)
|
||||||
ul class=(&menu_classes) { (items) }
|
ul class=[menu_classes.get()] { (items) }
|
||||||
(btn)
|
(btn)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
(btn)
|
(btn)
|
||||||
(btn_toggle)
|
(btn_toggle)
|
||||||
ul class=(&menu_classes) { (items) }
|
ul class=[menu_classes.get()] { (items) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
// Botón único con funcionalidad de *toggle*.
|
// Botón único con funcionalidad de *toggle*.
|
||||||
@let btn_toggle_classes = util::join!(&btn_base, " dropdown-toggle");
|
|
||||||
button
|
button
|
||||||
type="button"
|
type="button"
|
||||||
class=(&btn_toggle_classes)
|
class=[btn_classes.alter_classes(
|
||||||
|
ClassesOp::Add, "dropdown-toggle"
|
||||||
|
).get()]
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
data-bs-offset=[offset]
|
data-bs-offset=[offset]
|
||||||
data-bs-reference=[reference]
|
data-bs-reference=[reference]
|
||||||
|
|
@ -164,7 +166,7 @@ impl Component for Dropdown {
|
||||||
{
|
{
|
||||||
(title)
|
(title)
|
||||||
}
|
}
|
||||||
ul class=(&menu_classes) { (items) }
|
ul class=[menu_classes.get()] { (items) }
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
// Sin botón: sólo el listado como menú contextual.
|
// Sin botón: sólo el listado como menú contextual.
|
||||||
|
|
@ -178,17 +180,17 @@ impl Component for Dropdown {
|
||||||
impl Dropdown {
|
impl Dropdown {
|
||||||
// **< Dropdown BUILDER >***********************************************************************
|
// **< Dropdown BUILDER >***********************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del menú desplegable.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al menú desplegable.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,14 +203,14 @@ impl Dropdown {
|
||||||
|
|
||||||
/// Ajusta el tamaño del botón.
|
/// Ajusta el tamaño del botón.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_button_size(mut self, size: classes::ButtonSize) -> Self {
|
pub fn with_button_size(mut self, size: ButtonSize) -> Self {
|
||||||
self.button_size = size;
|
self.button_size = size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define el color/estilo del botón.
|
/// Define el color/estilo del botón.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_button_color(mut self, color: classes::ButtonColor) -> Self {
|
pub fn with_button_color(mut self, color: ButtonColor) -> Self {
|
||||||
self.button_color = color;
|
self.button_color = color;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,10 @@ pub enum ItemKind {
|
||||||
/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú.
|
/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú.
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS asociadas al elemento.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve el tipo de elemento representado.
|
/// Devuelve el tipo de elemento representado.
|
||||||
item_kind: ItemKind,
|
item_kind: ItemKind,
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +59,7 @@ impl Component for Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
|
@ -65,7 +67,7 @@ impl Component for Item {
|
||||||
ItemKind::Void => html! {},
|
ItemKind::Void => html! {},
|
||||||
|
|
||||||
ItemKind::Label(label) => html! {
|
ItemKind::Label(label) => html! {
|
||||||
li (self.props()) {
|
li id=[self.id()] class=[self.classes().get()] {
|
||||||
span class="dropdown-item-text" {
|
span class="dropdown-item-text" {
|
||||||
(label.using(cx))
|
(label.using(cx))
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +101,7 @@ impl Component for Item {
|
||||||
let tabindex = disabled.then_some("-1");
|
let tabindex = disabled.then_some("-1");
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
li (self.props()) {
|
li id=[self.id()] class=[self.classes().get()] {
|
||||||
a
|
a
|
||||||
class=(classes)
|
class=(classes)
|
||||||
href=[href]
|
href=[href]
|
||||||
|
|
@ -125,7 +127,7 @@ impl Component for Item {
|
||||||
let disabled_attr = disabled.then_some("disabled");
|
let disabled_attr = disabled.then_some("disabled");
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
li (self.props()) {
|
li id=[self.id()] class=[self.classes().get()] {
|
||||||
button
|
button
|
||||||
class=(classes)
|
class=(classes)
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -139,7 +141,7 @@ impl Component for Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemKind::Header(label) => html! {
|
ItemKind::Header(label) => html! {
|
||||||
li (self.props()) {
|
li id=[self.id()] class=[self.classes().get()] {
|
||||||
h6 class="dropdown-header" {
|
h6 class="dropdown-header" {
|
||||||
(label.using(cx))
|
(label.using(cx))
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +149,7 @@ impl Component for Item {
|
||||||
},
|
},
|
||||||
|
|
||||||
ItemKind::Divider => html! {
|
ItemKind::Divider => html! {
|
||||||
li (self.props()) { hr class="dropdown-divider" {} }
|
li id=[self.id()] class=[self.classes().get()] { hr class="dropdown-divider" {} }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -258,17 +260,17 @@ impl Item {
|
||||||
|
|
||||||
// **< Item BUILDER >***************************************************************************
|
// **< Item BUILDER >***************************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del elemento.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al elemento.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,30 @@
|
||||||
//! Definiciones para crear formularios ([`Form`]).
|
//! Definiciones para crear formularios ([`Form`]).
|
||||||
|
|
||||||
pub use pagetop::base::component::form::{Autocomplete, AutofillField, CheckboxKind, Method};
|
mod props;
|
||||||
|
pub use props::{Autocomplete, AutofillField, CheckboxKind, Method};
|
||||||
|
|
||||||
pub use pagetop::base::component::form::Form;
|
mod component;
|
||||||
|
pub use component::Form;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::Fieldset;
|
mod fieldset;
|
||||||
|
pub use fieldset::Fieldset;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::Checkbox;
|
mod checkbox;
|
||||||
|
pub use checkbox::Checkbox;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::check;
|
pub mod check;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::radio;
|
pub mod radio;
|
||||||
|
|
||||||
pub mod select;
|
pub mod select;
|
||||||
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
|
||||||
pub mod textarea;
|
mod textarea;
|
||||||
pub use textarea::Textarea;
|
pub use textarea::Textarea;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::Range;
|
mod range;
|
||||||
|
pub use range::Range;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::Hidden;
|
mod hidden;
|
||||||
|
pub use hidden::Hidden;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
//! Definiciones para crear grupos de casillas de verificación (*check buttons*).
|
//! Definiciones para crear grupos de casillas de verificación (*check buttons*).
|
||||||
|
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
// **< Item >***************************************************************************************
|
// **< Item >***************************************************************************************
|
||||||
|
|
||||||
/// Casilla de verificación individual de un [`Field`].
|
/// Casilla de verificación individual de un [`form::check::Field`](Field).
|
||||||
///
|
///
|
||||||
/// Representa cada casilla de un grupo de casillas de verificación, con una etiqueta localizable
|
/// Representa cada casilla de un grupo de casillas de verificación, con una etiqueta localizable
|
||||||
/// visible. Puede marcarse como seleccionada o deshabilitada de forma independiente al resto.
|
/// visible. Puede marcarse como seleccionada o deshabilitada de forma independiente al resto.
|
||||||
|
|
@ -15,9 +15,9 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let item = form::check::Item::new("apple", L10n::n("Apple")).with_checked(true);
|
/// let item = form::check::Item::new("apple", L10n::n("Apple")).with_checked(true);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
|
|
@ -65,21 +65,24 @@ impl Item {
|
||||||
|
|
||||||
/// Componente para crear un **grupo de casillas de verificación**.
|
/// Componente para crear un **grupo de casillas de verificación**.
|
||||||
///
|
///
|
||||||
/// Renderiza un conjunto de casillas de verificación donde cada casilla puede marcarse de forma
|
/// Renderiza un conjunto de casillas de verificación donde, a diferencia de un grupo de botones
|
||||||
/// independiente. Las casillas se añaden con [`with_item()`](Field::with_item) usando instancias
|
/// [`form::radio::Field`](crate::theme::form::radio::Field), cada casilla puede marcarse de forma
|
||||||
/// de [`form::check::Item`]. Si se activa el modo en línea con
|
/// independiente.
|
||||||
|
///
|
||||||
|
/// Las casillas se añaden mediante [`with_item()`](Field::with_item) usando instancias de
|
||||||
|
/// [`form::check::Item`](Item). Si se activa el modo en línea con
|
||||||
/// [`with_inline()`](Field::with_inline), las casillas se disponen horizontalmente.
|
/// [`with_inline()`](Field::with_inline), las casillas se disponen horizontalmente.
|
||||||
///
|
///
|
||||||
/// El atributo `name` de cada casilla se construye automáticamente combinando el `name` del grupo
|
/// El atributo `name` de cada casilla se construye automáticamente combinando el `name` del grupo
|
||||||
/// y el `name` del [`form::check::Item`] con un guion bajo. Por ejemplo, para el grupo con
|
/// y el `name` del [`form::check::Item`](Item) con un guion bajo. Por ejemplo, para el grupo con
|
||||||
/// `name=interests` y casillas con `name=art` y `name=tech`, se genera `name=interests_art` y
|
/// `name=interests` y casillas con `name=art` y `name=tech`, se genera `name=interests_art` y
|
||||||
/// `name=interests_tech`.
|
/// `name=interests_tech`.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let interests = form::check::Field::new()
|
/// let interests = form::check::Field::new()
|
||||||
/// .with_name("interests")
|
/// .with_name("interests")
|
||||||
/// .with_label(L10n::n("Areas of interest"))
|
/// .with_label(L10n::n("Areas of interest"))
|
||||||
|
|
@ -105,8 +108,10 @@ impl Item {
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del contenedor del grupo.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve el nombre base compartido por todas las casillas del grupo.
|
/// Devuelve el nombre base compartido por todas las casillas del grupo.
|
||||||
name: AttrName,
|
name: AttrName,
|
||||||
/// Devuelve la etiqueta del grupo.
|
/// Devuelve la etiqueta del grupo.
|
||||||
|
|
@ -127,31 +132,21 @@ impl Component for Field {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
// Asegura `name` e `id`.
|
self.alter_classes(ClassesOp::Prepend, "form-field form-field-checkboxes");
|
||||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
}
|
||||||
|
|
||||||
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
let name = self
|
let name = self
|
||||||
.name()
|
.name()
|
||||||
.get()
|
.get()
|
||||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 3));
|
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 3));
|
||||||
self.alter_name(&name);
|
|
||||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
|
||||||
|
|
||||||
// Clases CSS del contenedor del grupo de casillas.
|
|
||||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-checkboxes"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
|
||||||
// En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado.
|
|
||||||
let name = self.name().get().unwrap();
|
|
||||||
let container_id = self.id().unwrap();
|
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
div (self.props()) {
|
div id=(&container_id) class=[self.classes().get()] {
|
||||||
@if let Some(label) = self.label().lookup(cx) {
|
@if let Some(label) = self.label().lookup(cx) {
|
||||||
label class="form-label" { (label) }
|
label class="form-label" { (label) }
|
||||||
}
|
}
|
||||||
|
|
@ -193,17 +188,17 @@ impl Component for Field {
|
||||||
impl Field {
|
impl Field {
|
||||||
// **< Field BUILDER >**************************************************************************
|
// **< Field BUILDER >**************************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del grupo de casillas.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al contenedor del grupo de casillas.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::LOCALES_BOOTSIER;
|
||||||
|
use crate::theme::form;
|
||||||
|
|
||||||
/// Componente para crear una **casilla de verificación** o un **interruptor** (*toggle switch*).
|
/// Componente para crear una **casilla de verificación** o un **interruptor** (*toggle switch*).
|
||||||
///
|
///
|
||||||
/// Renderiza un control binario (marcado/no marcado) en dos variantes, por defecto como casilla de
|
/// Renderiza un control binario (marcado/no marcado) en dos variantes visuales, por defecto se
|
||||||
/// verificación estándar, y también como interruptor ([`Checkbox::switch()`]).
|
/// muestra como una casilla de verificación estándar, pero también puede renderizarse como un
|
||||||
|
/// interruptor de encendido/apagado ([`Checkbox::switch()`]).
|
||||||
///
|
///
|
||||||
/// Se puede mostrar en línea con otros controles usando [`with_inline()`](Checkbox::with_inline), o
|
/// Se puede mostrar en línea con otros controles usando [`with_inline()`](Checkbox::with_inline), o
|
||||||
/// justificar a la derecha del contenedor invirtiendo el orden de la etiqueta y el control usando
|
/// justificar a la derecha del contenedor invirtiendo el orden de la etiqueta y el control usando
|
||||||
|
|
@ -11,10 +15,10 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let accept_terms = form::Checkbox::new()
|
/// let accept_terms = form::Checkbox::check() // También sirve new() o default().
|
||||||
/// .with_name("terms_accepted")
|
/// .with_name("terms_accepted")
|
||||||
/// .with_label(L10n::n("I accept the terms and conditions"))
|
/// .with_label(L10n::n("I accept the terms and conditions"))
|
||||||
/// .with_required(true);
|
/// .with_required(true);
|
||||||
|
|
@ -39,8 +43,10 @@ use crate::prelude::*;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Checkbox {
|
pub struct Checkbox {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del contenedor del control.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve la variante visual del control.
|
/// Devuelve la variante visual del control.
|
||||||
checkbox_kind: form::CheckboxKind,
|
checkbox_kind: form::CheckboxKind,
|
||||||
/// Devuelve el nombre del campo.
|
/// Devuelve el nombre del campo.
|
||||||
|
|
@ -67,22 +73,11 @@ impl Component for Checkbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
// Asegura `name` e `id`.
|
let mut classes = "form-field form-check".to_string();
|
||||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
|
||||||
let name = self
|
|
||||||
.name()
|
|
||||||
.get()
|
|
||||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 1));
|
|
||||||
self.alter_name(&name);
|
|
||||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
|
||||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
|
||||||
|
|
||||||
// Clases CSS del contenedor de la casilla de verificación.
|
|
||||||
let mut classes = String::from("form-field form-check");
|
|
||||||
if *self.checkbox_kind() == form::CheckboxKind::Switch {
|
if *self.checkbox_kind() == form::CheckboxKind::Switch {
|
||||||
classes.push_str(" form-switch");
|
classes.push_str(" form-switch");
|
||||||
}
|
}
|
||||||
|
|
@ -92,19 +87,19 @@ impl Component for Checkbox {
|
||||||
if *self.reverse() {
|
if *self.reverse() {
|
||||||
classes.push_str(" form-check-reverse");
|
classes.push_str(" form-check-reverse");
|
||||||
}
|
}
|
||||||
self.alter_prop(PropsOp::prepend_classes(classes));
|
self.alter_classes(ClassesOp::Prepend, classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
// En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado.
|
let name = self
|
||||||
let name = self.name().get().unwrap();
|
.name()
|
||||||
let container_id = self.id().unwrap();
|
.get()
|
||||||
|
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 1));
|
||||||
|
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||||
let checkbox_id = util::join!(&container_id, "-checkbox");
|
let checkbox_id = util::join!(&container_id, "-checkbox");
|
||||||
let is_switch = *self.checkbox_kind() == form::CheckboxKind::Switch;
|
let is_switch = *self.checkbox_kind() == form::CheckboxKind::Switch;
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
div (self.props()) {
|
div id=(&container_id) class=[self.classes().get()] {
|
||||||
input
|
input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
role=[is_switch.then_some("switch")]
|
role=[is_switch.then_some("switch")]
|
||||||
|
|
@ -122,7 +117,7 @@ impl Component for Checkbox {
|
||||||
@if *self.required() {
|
@if *self.required() {
|
||||||
span
|
span
|
||||||
class="form-required"
|
class="form-required"
|
||||||
title=(L10n::l("field_required").using(cx))
|
title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx))
|
||||||
{
|
{
|
||||||
"*"
|
"*"
|
||||||
}
|
}
|
||||||
|
|
@ -150,17 +145,17 @@ impl Checkbox {
|
||||||
|
|
||||||
// **< Checkbox BUILDER >***********************************************************************
|
// **< Checkbox BUILDER >***********************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del control.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al contenedor del control.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::base::component::form;
|
use crate::theme::form;
|
||||||
|
|
||||||
/// Componente para crear un **formulario** HTML ([`form`]).
|
/// Componente para crear un **formulario** ([`form`]).
|
||||||
///
|
///
|
||||||
/// Renderiza un formulario estándar con soporte para los atributos más habituales:
|
/// Este componente renderiza un formulario estándar con soporte para los atributos más habituales:
|
||||||
///
|
///
|
||||||
/// - `id`: identificador opcional del formulario.
|
/// - `id`: identificador opcional del formulario.
|
||||||
/// - `classes`: clases CSS adicionales (p. ej. utilidades CSS).
|
/// - `classes`: clases CSS adicionales (p. ej. utilidades CSS).
|
||||||
/// - `action`: URL/ruta de destino para el envío.
|
/// - `action`: URL/ruta de destino para el envío.
|
||||||
/// - `method`: método usado por el formulario para el envío de los datos (ver [`form::Method`]).
|
/// - `method`: método usado por el formulario para el envío de los datos (ver explicaciones en
|
||||||
|
/// [`form::Method`](crate::theme::form::Method)).
|
||||||
/// - `accept-charset`: juego de caracteres aceptado (por defecto es `"UTF-8"`).
|
/// - `accept-charset`: juego de caracteres aceptado (por defecto es `"UTF-8"`).
|
||||||
/// - `children`: contenido del formulario.
|
/// - `children`: contenido del formulario.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// use pagetop::prelude::*;
|
||||||
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let form_login = Form::new()
|
/// let form_login = Form::new()
|
||||||
/// .with_id("login")
|
/// .with_id("login")
|
||||||
|
|
@ -40,12 +42,15 @@ use crate::base::component::form;
|
||||||
/// )
|
/// )
|
||||||
/// .with_child(
|
/// .with_child(
|
||||||
/// Button::submit(L10n::n("Sign in"))
|
/// Button::submit(L10n::n("Sign in"))
|
||||||
|
/// .with_color(ButtonColor::Background(Color::Primary)),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Form {
|
pub struct Form {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del formulario.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve la URL/ruta de destino del formulario.
|
/// Devuelve la URL/ruta de destino del formulario.
|
||||||
action: AttrValue,
|
action: AttrValue,
|
||||||
/// Devuelve el método para enviar el formulario.
|
/// Devuelve el método para enviar el formulario.
|
||||||
|
|
@ -63,11 +68,11 @@ impl Component for Form {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("form"));
|
self.alter_classes(ClassesOp::Prepend, "form");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
|
@ -77,7 +82,8 @@ impl Component for Form {
|
||||||
};
|
};
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
form
|
form
|
||||||
(self.props())
|
id=[self.id()]
|
||||||
|
class=[self.classes().get()]
|
||||||
action=[self.action().get()]
|
action=[self.action().get()]
|
||||||
method=[method]
|
method=[method]
|
||||||
accept-charset=[self.charset().get()]
|
accept-charset=[self.charset().get()]
|
||||||
|
|
@ -91,17 +97,17 @@ impl Component for Form {
|
||||||
impl Form {
|
impl Form {
|
||||||
// **< Form BUILDER >***************************************************************************
|
// **< Form BUILDER >***************************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del formulario.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al formulario.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
/// Componente para crear un **grupo de controles relacionados** en un formulario.
|
/// Componente para crear un **grupo de controles relacionados** en un formulario.
|
||||||
///
|
///
|
||||||
|
|
@ -13,9 +13,9 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let personal_data = form::Fieldset::new()
|
/// let personal_data = form::Fieldset::new()
|
||||||
/// .with_legend(L10n::n("Personal data"))
|
/// .with_legend(L10n::n("Personal data"))
|
||||||
/// .with_description(L10n::n("Enter your full name and contact email."))
|
/// .with_description(L10n::n("Enter your full name and contact email."))
|
||||||
|
|
@ -24,8 +24,10 @@ use crate::prelude::*;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Fieldset {
|
pub struct Fieldset {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del `fieldset`.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve la leyenda del `fieldset`.
|
/// Devuelve la leyenda del `fieldset`.
|
||||||
legend: Attr<L10n>,
|
legend: Attr<L10n>,
|
||||||
/// Devuelve la descripción del `fieldset`.
|
/// Devuelve la descripción del `fieldset`.
|
||||||
|
|
@ -42,7 +44,7 @@ impl Component for Fieldset {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
|
@ -53,7 +55,7 @@ impl Component for Fieldset {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
fieldset (self.props()) disabled[*self.disabled()] {
|
fieldset id=[self.id()] class=[self.classes().get()] disabled[*self.disabled()] {
|
||||||
@if let Some(legend) = self.legend().lookup(cx) {
|
@if let Some(legend) = self.legend().lookup(cx) {
|
||||||
legend { (legend) }
|
legend { (legend) }
|
||||||
}
|
}
|
||||||
|
|
@ -69,17 +71,17 @@ impl Component for Fieldset {
|
||||||
impl Fieldset {
|
impl Fieldset {
|
||||||
// **< Fieldset BUILDER >***********************************************************************
|
// **< Fieldset BUILDER >***********************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del `fieldset` (grupo de controles).
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al `fieldset`.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
/// Componente para crear un **campo oculto** del formulario.
|
/// Componente para crear un **campo oculto** del formulario.
|
||||||
///
|
///
|
||||||
|
|
@ -10,9 +10,9 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let token = form::Hidden::new()
|
/// let token = form::Hidden::new()
|
||||||
/// .with_name("csrf_token")
|
/// .with_name("csrf_token")
|
||||||
/// .with_value("a1b2c3d4e5");
|
/// .with_value("a1b2c3d4e5");
|
||||||
|
|
@ -2,39 +2,452 @@
|
||||||
|
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
pub use pagetop::base::component::form::input::{Field, Kind, Mode};
|
use crate::LOCALES_BOOTSIER;
|
||||||
|
use crate::theme::form;
|
||||||
|
|
||||||
/// Extensión de Bootsier para [`form::input::Field`].
|
use std::fmt;
|
||||||
|
|
||||||
|
// **< Kind >***************************************************************************************
|
||||||
|
|
||||||
|
/// Tipo de campo para un [`form::input::Field`].
|
||||||
///
|
///
|
||||||
/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se
|
/// Determina el tipo de entrada que acepta, así como el comportamiento del navegador al interactuar
|
||||||
/// superpone al control mientras está vacío y permanece flotante cuando tiene contenido o está
|
/// con el campo. Implícitamente se aplica al crear el control: [`text()`](Field::text),
|
||||||
/// enfocado.
|
/// [`password()`](Field::password), [`search()`](Field::search), [`email()`](Field::email),
|
||||||
|
/// [`telephone()`](Field::telephone) o [`url()`](Field::url).
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Kind {
|
||||||
|
/// Entrada de texto genérico (`type="text"`). Es el tipo por defecto.
|
||||||
|
#[default]
|
||||||
|
Text,
|
||||||
|
/// Entrada de una contraseña (`type="password"`). El contenido aparece enmascarado.
|
||||||
|
Password,
|
||||||
|
/// Campo de búsqueda (`type="search"`). Es un tipo semántico para los cuadros de búsqueda.
|
||||||
|
Search,
|
||||||
|
/// Entrada de un correo electrónico (`type="email"`). Permite validar el formato del correo.
|
||||||
|
Email,
|
||||||
|
/// Entrada de un teléfono (`type="tel"`). Activa el teclado de llamadas en móviles.
|
||||||
|
Telephone,
|
||||||
|
/// Entrada de una URL (`type="url"`). Comprueba que la entrada sea una URL bien formada.
|
||||||
|
Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Kind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Kind::Text => "text",
|
||||||
|
Kind::Password => "password",
|
||||||
|
Kind::Search => "search",
|
||||||
|
Kind::Email => "email",
|
||||||
|
Kind::Telephone => "tel",
|
||||||
|
Kind::Url => "url",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Mode >***************************************************************************************
|
||||||
|
|
||||||
|
/// Sugerencia para el teclado virtual de un [`form::input::Field`].
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// Indica al navegador qué tipo de teclado virtual mostrar en dispositivos móviles o táctiles al
|
||||||
/// # use pagetop::locale::L10n;
|
/// editar el campo. A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// valores aceptados ni activa la validación del navegador; es sólo una sugerencia de presentación.
|
||||||
///
|
///
|
||||||
/// let nombre = form::input::Field::text()
|
/// Se establece con [`form::input::Field::with_inputmode()`].
|
||||||
/// .with_name("name")
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
/// .with_label(L10n::n("Name"))
|
pub enum Mode {
|
||||||
/// .with_placeholder(L10n::n("Enter your name"))
|
/// Suprime el teclado virtual. Útil en campos con teclado personalizado basado en JavaScript.
|
||||||
/// .with_floating_label(true);
|
None,
|
||||||
|
/// Teclado de texto genérico.
|
||||||
|
Text,
|
||||||
|
/// Teclado decimal, con dígitos y separador decimal.
|
||||||
|
Decimal,
|
||||||
|
/// Teclado numérico, con sólo dígitos.
|
||||||
|
Numeric,
|
||||||
|
/// Teclado de teléfono, con dígitos y símbolos `+`, `*` y `#`.
|
||||||
|
Tel,
|
||||||
|
/// Teclado optimizado para búsquedas (puede incluir tecla de búsqueda).
|
||||||
|
Search,
|
||||||
|
/// Teclado optimizado para correo electrónico (incluye `@` y `.`).
|
||||||
|
Email,
|
||||||
|
/// Teclado optimizado para URL (incluye `/`, `.` y `.com`).
|
||||||
|
Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Mode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Mode::None => "none",
|
||||||
|
Mode::Text => "text",
|
||||||
|
Mode::Decimal => "decimal",
|
||||||
|
Mode::Numeric => "numeric",
|
||||||
|
Mode::Tel => "tel",
|
||||||
|
Mode::Search => "search",
|
||||||
|
Mode::Email => "email",
|
||||||
|
Mode::Url => "url",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Field >**************************************************************************************
|
||||||
|
|
||||||
|
/// Componente para crear un **campo de texto de una línea**.
|
||||||
|
///
|
||||||
|
/// Renderiza los tipos más habituales en formularios:
|
||||||
|
///
|
||||||
|
/// - [`form::input::Field::text()`]: campo de texto genérico (`type="text"`, por defecto).
|
||||||
|
/// - [`form::input::Field::password()`]: contraseña (`type="password"`).
|
||||||
|
/// - [`form::input::Field::search()`]: búsqueda (`type="search"`).
|
||||||
|
/// - [`form::input::Field::email()`]: correo electrónico (`type="email"`).
|
||||||
|
/// - [`form::input::Field::telephone()`]: teléfono (`type="tel"`).
|
||||||
|
/// - [`form::input::Field::url()`]: URL (`type="url"`).
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let email = form::input::Field::email()
|
||||||
|
/// .with_name("email")
|
||||||
|
/// .with_label(L10n::n("Email address"))
|
||||||
|
/// .with_placeholder(L10n::n("user@example.com"))
|
||||||
|
/// .with_autocomplete(Some(form::Autocomplete::email()))
|
||||||
|
/// .with_required(true);
|
||||||
/// ```
|
/// ```
|
||||||
pub trait InputBootsier {
|
///
|
||||||
|
/// Al enviar el formulario el navegador transmite `name=valor`. Un campo de texto siempre envía su
|
||||||
|
/// valor, incluso si está vacío. En el servidor se deserializa como `String`:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[derive(serde::Deserialize)]
|
||||||
|
/// struct FormData {
|
||||||
|
/// email: String, // Siempre presente; cadena vacía si el usuario no escribió nada.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
|
pub struct Field {
|
||||||
|
#[getters(skip)]
|
||||||
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del contenedor del campo.
|
||||||
|
classes: Classes,
|
||||||
|
/// Devuelve el tipo de campo.
|
||||||
|
kind: Kind,
|
||||||
|
/// Devuelve el nombre del campo.
|
||||||
|
name: AttrName,
|
||||||
|
/// Devuelve el valor inicial del campo.
|
||||||
|
value: AttrValue,
|
||||||
|
/// Devuelve la etiqueta del campo.
|
||||||
|
label: Attr<L10n>,
|
||||||
|
/// Devuelve si la etiqueta se muestra flotante sobre el campo.
|
||||||
|
floating_label: bool,
|
||||||
|
/// Devuelve el texto de ayuda del campo.
|
||||||
|
help_text: Attr<L10n>,
|
||||||
|
/// Devuelve la longitud mínima permitida en caracteres.
|
||||||
|
minlength: Attr<u16>,
|
||||||
|
/// Devuelve la longitud máxima permitida en caracteres.
|
||||||
|
maxlength: Attr<u16>,
|
||||||
|
/// Devuelve el texto indicativo del campo.
|
||||||
|
placeholder: Attr<L10n>,
|
||||||
|
/// Devuelve la configuración de autocompletado del campo.
|
||||||
|
autocomplete: Attr<form::Autocomplete>,
|
||||||
|
/// Devuelve si el campo recibe el foco automáticamente al cargar la página.
|
||||||
|
autofocus: bool,
|
||||||
|
/// Devuelve si el campo es de sólo lectura.
|
||||||
|
readonly: bool,
|
||||||
|
/// Devuelve si el campo es obligatorio.
|
||||||
|
required: bool,
|
||||||
|
/// Devuelve si el campo está deshabilitado.
|
||||||
|
disabled: bool,
|
||||||
|
/// Devuelve si el campo se muestra como texto plano sin bordes ni fondo.
|
||||||
|
plaintext: bool,
|
||||||
|
/// Devuelve la sugerencia de teclado virtual para el campo.
|
||||||
|
inputmode: Attr<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Field {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<String> {
|
||||||
|
self.id.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&mut self, _cx: &Context) {
|
||||||
|
if *self.floating_label() {
|
||||||
|
self.alter_classes(ClassesOp::Prepend, "form-floating");
|
||||||
|
}
|
||||||
|
self.alter_classes(
|
||||||
|
ClassesOp::Prepend,
|
||||||
|
util::join!("form-field form-field-", self.kind().to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
let container_id = self
|
||||||
|
.id()
|
||||||
|
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)));
|
||||||
|
let input_id = container_id.as_deref().map(|id| util::join!(id, "-input"));
|
||||||
|
let input_class = if *self.plaintext() {
|
||||||
|
"form-control-plaintext"
|
||||||
|
} else {
|
||||||
|
"form-control"
|
||||||
|
};
|
||||||
|
// La etiqueta flotante requiere el atributo `placeholder` para detectar cuándo el campo
|
||||||
|
// está vacío y animar la etiqueta; si no está definido, se fuerza `placeholder=""`.
|
||||||
|
let placeholder = if *self.floating_label() {
|
||||||
|
Some(self.placeholder().lookup(cx).unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
self.placeholder().lookup(cx)
|
||||||
|
};
|
||||||
|
let label = match self.label().lookup(cx) {
|
||||||
|
Some(text) => html! {
|
||||||
|
label for=[input_id.as_deref()] class="form-label" {
|
||||||
|
(text)
|
||||||
|
@if *self.required() {
|
||||||
|
span
|
||||||
|
class="form-required"
|
||||||
|
title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx))
|
||||||
|
{
|
||||||
|
"*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => html! {},
|
||||||
|
};
|
||||||
|
Ok(html! {
|
||||||
|
div id=[container_id.as_deref()] class=[self.classes().get()] {
|
||||||
|
@if !*self.floating_label() {
|
||||||
|
(label)
|
||||||
|
}
|
||||||
|
input
|
||||||
|
type=(self.kind())
|
||||||
|
id=[input_id.as_deref()]
|
||||||
|
class=(input_class)
|
||||||
|
name=[self.name().get()]
|
||||||
|
value=[self.value().get()]
|
||||||
|
minlength=[self.minlength().get()]
|
||||||
|
maxlength=[self.maxlength().get()]
|
||||||
|
placeholder=[placeholder]
|
||||||
|
inputmode=[self.inputmode().get()]
|
||||||
|
autocomplete=[self.autocomplete().get()]
|
||||||
|
autofocus[*self.autofocus()]
|
||||||
|
readonly[*self.readonly() || *self.plaintext()]
|
||||||
|
required[*self.required()]
|
||||||
|
disabled[*self.disabled()];
|
||||||
|
@if *self.floating_label() {
|
||||||
|
(label)
|
||||||
|
}
|
||||||
|
@if let Some(description) = self.help_text().lookup(cx) {
|
||||||
|
div class="form-text" { (description) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Field {
|
||||||
|
/// Crea un campo de **texto genérico** (`type="text"`).
|
||||||
|
///
|
||||||
|
/// Es el tipo por defecto. Adecuado para nombres, apellidos, ciudades y cualquier entrada
|
||||||
|
/// textual sin restricciones de formato específicas.
|
||||||
|
pub fn text() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un campo de **contraseña** (`type="password"`).
|
||||||
|
///
|
||||||
|
/// El navegador oculta los caracteres introducidos. Se recomienda usar con
|
||||||
|
/// [`with_autocomplete()`](Self::with_autocomplete) para permitir autorrellenar con una
|
||||||
|
/// contraseña guardada o dejar al usuario recibir sugerencias o crear una nueva.
|
||||||
|
pub fn password() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Kind::Password,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un campo de **búsqueda** (`type="search"`).
|
||||||
|
///
|
||||||
|
/// Semánticamente equivalente a `text` pero optimizado para búsquedas: algunos
|
||||||
|
/// navegadores añaden un botón para borrar el contenido.
|
||||||
|
pub fn search() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Kind::Search,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un campo de **correo electrónico** (`type="email"`).
|
||||||
|
///
|
||||||
|
/// El navegador valida el formato de la dirección antes de enviar el formulario. En
|
||||||
|
/// dispositivos móviles muestra un teclado adaptado para introducir direcciones de correo.
|
||||||
|
pub fn email() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Kind::Email,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un campo de **teléfono** (`type="tel"`).
|
||||||
|
///
|
||||||
|
/// No impone ninguna restricción de formato (los formatos de teléfono varían por país), pero
|
||||||
|
/// en dispositivos móviles muestra el teclado numérico de llamadas.
|
||||||
|
pub fn telephone() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Kind::Telephone,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un campo de **URL** (`type="url"`).
|
||||||
|
///
|
||||||
|
/// El navegador valida que el valor sea una URL bien formada antes de enviar el formulario.
|
||||||
|
pub fn url() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Kind::Url,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Field BUILDER >**************************************************************************
|
||||||
|
|
||||||
|
/// Establece el identificador único (`id`) del contenedor del campo.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
|
self.id.alter_id(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modifica la lista de clases CSS aplicadas al contenedor del campo.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
|
self.classes.alter_classes(op, classes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el nombre del campo (atributo `name`).
|
||||||
|
///
|
||||||
|
/// Sin él, el valor del campo no se transmite al servidor al enviar el formulario. Para
|
||||||
|
/// deserializar el campo en el servidor es recomendable establecer un `name` explícito.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
|
||||||
|
self.name.alter_name(name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el valor inicial del campo.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
|
||||||
|
self.value.alter_str(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_label(mut self, label: impl Into<Option<L10n>>) -> Self {
|
||||||
|
self.label.alter_opt(label.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Establece si la etiqueta se muestra flotante sobre el campo.
|
/// Establece si la etiqueta se muestra flotante sobre el campo.
|
||||||
///
|
///
|
||||||
/// Cuando está activo, la etiqueta se superpone al campo y asciende al enfocarlo o cuando tiene
|
/// Cuando está activo, la etiqueta se superpone al campo y asciende al enfocarlo o cuando tiene
|
||||||
/// contenido. Requiere que el campo tenga un atributo `placeholder` definido; si no se
|
/// contenido.
|
||||||
/// especifica, se fuerza `placeholder=""` antes del renderizado.
|
#[builder_fn]
|
||||||
fn with_floating_label(self, floating: bool) -> Self;
|
pub fn with_floating_label(mut self, floating_label: bool) -> Self {
|
||||||
|
self.floating_label = floating_label;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputBootsier for Field {
|
/// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo).
|
||||||
fn with_floating_label(self, floating: bool) -> Self {
|
#[builder_fn]
|
||||||
if floating {
|
pub fn with_help_text(mut self, help_text: impl Into<Option<L10n>>) -> Self {
|
||||||
self.with_prop(PropsOp::add_classes("form-floating"))
|
self.help_text.alter_opt(help_text.into());
|
||||||
} else {
|
self
|
||||||
self.with_prop(PropsOp::remove_classes("form-floating"))
|
}
|
||||||
}
|
|
||||||
|
/// Establece la longitud mínima permitida en caracteres (`None` para no imponer mínimo).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_minlength(mut self, minlength: Option<u16>) -> Self {
|
||||||
|
self.minlength.alter_opt(minlength);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece la longitud máxima permitida en caracteres (`None` para no imponer límite).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_maxlength(mut self, maxlength: Option<u16>) -> Self {
|
||||||
|
self.maxlength.alter_opt(maxlength);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece o elimina el texto indicativo del campo (`None` para quitarlo).
|
||||||
|
///
|
||||||
|
/// Este texto aparece en el mismo campo y desaparece en cuanto el usuario empieza a escribir.
|
||||||
|
/// Al ser texto visible para el usuario se acepta [`L10n`] para poder localizarlo.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_placeholder(mut self, placeholder: impl Into<Option<L10n>>) -> Self {
|
||||||
|
self.placeholder.alter_opt(placeholder.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece la configuración de autocompletado del campo.
|
||||||
|
///
|
||||||
|
/// Usar los métodos de [`form::Autocomplete`] para los valores más habituales (p. ej.
|
||||||
|
/// [`Autocomplete::email()`](form::Autocomplete::email) o
|
||||||
|
/// [`Autocomplete::current_password()`](form::Autocomplete::current_password)).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_autocomplete(mut self, autocomplete: Option<form::Autocomplete>) -> Self {
|
||||||
|
self.autocomplete.alter_opt(autocomplete);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el campo recibe el foco automáticamente al cargar la página.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_autofocus(mut self, autofocus: bool) -> Self {
|
||||||
|
self.autofocus = autofocus;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el campo es de sólo lectura.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_readonly(mut self, readonly: bool) -> Self {
|
||||||
|
self.readonly = readonly;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el campo es obligatorio.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_required(mut self, required: bool) -> Self {
|
||||||
|
self.required = required;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el campo está deshabilitado.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_disabled(mut self, disabled: bool) -> Self {
|
||||||
|
self.disabled = disabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el campo se muestra como texto plano (sin bordes ni fondo).
|
||||||
|
///
|
||||||
|
/// Útil para mostrar un valor no editable en pantalla que sí se envía al servidor con el
|
||||||
|
/// formulario.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_plaintext(mut self, plaintext: bool) -> Self {
|
||||||
|
self.plaintext = plaintext;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el modo de entrada sugerido para el teclado virtual en dispositivos móviles.
|
||||||
|
///
|
||||||
|
/// A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los valores aceptados
|
||||||
|
/// ni activa la validación del navegador; es sólo una sugerencia de presentación.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_inputmode(mut self, inputmode: Option<Mode>) -> Self {
|
||||||
|
self.inputmode.alter_opt(inputmode);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
// **< CheckboxKind >*******************************************************************************
|
// **< CheckboxKind >*******************************************************************************
|
||||||
|
|
||||||
/// Variante visual para un [`form::Checkbox`] en un formulario.
|
/// Variante visual para [`form::Checkbox`](crate::theme::form::Checkbox) en un formulario.
|
||||||
///
|
///
|
||||||
/// Determina si el control se renderiza como una casilla de verificación estándar o como un
|
/// Determina si el control se renderiza como una casilla de verificación estándar o como un
|
||||||
/// interruptor (*toggle switch*). La variante [`Switch`](Self::Switch) añade la clase `form-switch`
|
/// interruptor (*toggle switch*).
|
||||||
/// al contenedor y el atributo `role="switch"` al control para accesibilidad.
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum CheckboxKind {
|
pub enum CheckboxKind {
|
||||||
/// Casilla de verificación estándar. Es el tipo por defecto.
|
/// Casilla de verificación estándar. Es el tipo por defecto.
|
||||||
#[default]
|
#[default]
|
||||||
Check,
|
Check,
|
||||||
/// Interruptor de encendido/apagado (*toggle switch*).
|
/// Interruptor de encendido/apagado.
|
||||||
Switch,
|
Switch,
|
||||||
// TODO: Añadir variante `NativeSwitch` cuando el atributo `switch` de la propuesta WHATWG
|
// TODO: Añadir variante `NativeSwitch` cuando el atributo `switch` de la propuesta WHATWG
|
||||||
// (https://github.com/whatwg/html/issues/9546) sea estándar y tenga soporte amplio. Safari ya
|
// (https://github.com/whatwg/html/issues/9546) sea estándar y tenga soporte amplio. Safari ya
|
||||||
|
|
@ -51,9 +50,9 @@ pub enum CheckboxKind {
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// // Correo electrónico con sugerencia semántica del navegador.
|
/// // Correo electrónico con sugerencia semántica del navegador.
|
||||||
/// let ac = form::Autocomplete::email();
|
/// let ac = form::Autocomplete::email();
|
||||||
///
|
///
|
||||||
|
|
@ -88,7 +87,7 @@ impl Autocomplete {
|
||||||
// --< Secciones >------------------------------------------------------------------------------
|
// --< Secciones >------------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Construye `autocomplete` con un prefijo de sección y un token o tokens del
|
/// Construye `autocomplete` con un prefijo de sección y un token o tokens del
|
||||||
/// [`AutofillField`] indicado.
|
/// [`form::AutofillField`](AutofillField) indicado.
|
||||||
///
|
///
|
||||||
/// Genera `autocomplete="section-<name> <field>"`. Si `name` no es ASCII o contiene espacios,
|
/// Genera `autocomplete="section-<name> <field>"`. Si `name` no es ASCII o contiene espacios,
|
||||||
/// se ignora la sección y se genera sólo el token indicado.
|
/// se ignora la sección y se genera sólo el token indicado.
|
||||||
|
|
@ -244,9 +243,8 @@ impl fmt::Display for Autocomplete {
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
///
|
|
||||||
/// let ac = form::Autocomplete::token(form::AutofillField::Username);
|
/// let ac = form::Autocomplete::token(form::AutofillField::Username);
|
||||||
/// let ac = form::Autocomplete::shipping(form::AutofillField::StreetAddress);
|
/// let ac = form::Autocomplete::shipping(form::AutofillField::StreetAddress);
|
||||||
/// let ac = form::Autocomplete::section("job", form::AutofillField::Email);
|
/// let ac = form::Autocomplete::section("job", form::AutofillField::Email);
|
||||||
|
|
@ -449,7 +447,7 @@ impl AutofillField {
|
||||||
|
|
||||||
// **< Method >*************************************************************************************
|
// **< Method >*************************************************************************************
|
||||||
|
|
||||||
/// Método HTTP usado por un [`Form`](super::Form) para el envío de los datos.
|
/// Método HTTP usado por un formulario ([`Form`](crate::theme::Form)) para el envío de los datos.
|
||||||
///
|
///
|
||||||
/// En HTML, el atributo `method` del formulario indica **cómo** se envían los datos:
|
/// En HTML, el atributo `method` del formulario indica **cómo** se envían los datos:
|
||||||
///
|
///
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
//! Definiciones para crear grupos de botones de opción (*radio buttons*).
|
//! Definiciones para crear grupos de botones de opción (*radio buttons*).
|
||||||
|
|
||||||
use crate::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::LOCALES_BOOTSIER;
|
||||||
|
|
||||||
// **< Item >***************************************************************************************
|
// **< Item >***************************************************************************************
|
||||||
|
|
||||||
/// Botón de opción individual de un [`Field`].
|
/// Botón de opción individual de un [`form::radio::Field`](Field).
|
||||||
///
|
///
|
||||||
/// Representa cada opción de un grupo de opciones exclusivas entre sí, con un valor (el que se
|
/// Representa cada opción de un grupo de opciones exclusivas entre sí, con un valor (el que se
|
||||||
/// envía al servidor), una etiqueta localizable visible y puede marcarse como seleccionada o
|
/// envía al servidor), una etiqueta localizable visible y puede marcarse como seleccionada o
|
||||||
|
|
@ -12,9 +14,9 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let item = form::radio::Item::new("monthly", L10n::n("Monthly")).with_checked(true);
|
/// let item = form::radio::Item::new("monthly", L10n::n("Monthly")).with_checked(true);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
|
|
@ -62,8 +64,8 @@ impl Item {
|
||||||
|
|
||||||
/// Componente para crear un **grupo de botones de opción**.
|
/// Componente para crear un **grupo de botones de opción**.
|
||||||
///
|
///
|
||||||
/// Renderiza un grupo de botones de opción [`form::radio::Item`] que comparten el mismo atributo
|
/// Renderiza un grupo de botones de opción [`form::radio::Item`](Item) que comparten el mismo
|
||||||
/// `name`, por lo que sólo puede seleccionarse uno a la vez. Las opciones se añaden con
|
/// atributo `name`, por lo que sólo puede seleccionarse uno a la vez. Las opciones se añaden con
|
||||||
/// [`with_item()`](Field::with_item).
|
/// [`with_item()`](Field::with_item).
|
||||||
///
|
///
|
||||||
/// Si se activa el modo en línea [`with_inline()`](Field::with_inline), los botones se disponen
|
/// Si se activa el modo en línea [`with_inline()`](Field::with_inline), los botones se disponen
|
||||||
|
|
@ -72,9 +74,9 @@ impl Item {
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
///
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let plan = form::radio::Field::new()
|
/// let plan = form::radio::Field::new()
|
||||||
/// .with_name("plan")
|
/// .with_name("plan")
|
||||||
/// .with_label(L10n::n("Subscription plan"))
|
/// .with_label(L10n::n("Subscription plan"))
|
||||||
|
|
@ -94,8 +96,10 @@ impl Item {
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
#[getters(skip)]
|
||||||
props: Props,
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del contenedor del grupo.
|
||||||
|
classes: Classes,
|
||||||
/// Devuelve el nombre compartido por todos los botones de opción del grupo.
|
/// Devuelve el nombre compartido por todos los botones de opción del grupo.
|
||||||
name: AttrName,
|
name: AttrName,
|
||||||
/// Devuelve la etiqueta del grupo.
|
/// Devuelve la etiqueta del grupo.
|
||||||
|
|
@ -118,38 +122,28 @@ impl Component for Field {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
self.props.get_id()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
// Asegura `name` e `id`.
|
self.alter_classes(ClassesOp::Prepend, "form-field form-field-radios");
|
||||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
}
|
||||||
|
|
||||||
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
let name = self
|
let name = self
|
||||||
.name()
|
.name()
|
||||||
.get()
|
.get()
|
||||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 3));
|
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 3));
|
||||||
self.alter_name(&name);
|
|
||||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
|
||||||
|
|
||||||
// Clases CSS del contenedor del grupo de opciones.
|
|
||||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-radios"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
|
||||||
// En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado.
|
|
||||||
let name = self.name().get().unwrap();
|
|
||||||
let container_id = self.id().unwrap();
|
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
div (self.props()) {
|
div id=(&container_id) class=[self.classes().get()] {
|
||||||
@if let Some(label) = self.label().lookup(cx) {
|
@if let Some(label) = self.label().lookup(cx) {
|
||||||
label class="form-label" {
|
label class="form-label" {
|
||||||
(label)
|
(label)
|
||||||
@if *self.required() {
|
@if *self.required() {
|
||||||
span
|
span
|
||||||
class="form-required"
|
class="form-required"
|
||||||
title=(L10n::l("field_required").using(cx))
|
title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx))
|
||||||
{
|
{
|
||||||
"*"
|
"*"
|
||||||
}
|
}
|
||||||
|
|
@ -196,17 +190,17 @@ impl Component for Field {
|
||||||
impl Field {
|
impl Field {
|
||||||
// **< Field BUILDER >**************************************************************************
|
// **< Field BUILDER >**************************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único (`id`) del grupo de opciones.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_id(id);
|
self.id.alter_id(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
/// Modifica la lista de clases CSS aplicadas al contenedor del grupo de opciones.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
self.props.alter_prop(op);
|
self.classes.alter_classes(op, classes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue