use super::layout::into_node;
use super::widgets::screen_href;
use crate::i18n::Text;
use crate::render::{Render, escape};
use crate::screen::{Component, Node, ScreenDef};
use serde_json::{Value as Json, json};
into_node!(AlphaIndex);
const LATIN: &[&str] = &[
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
"T", "U", "V", "W", "X", "Y", "Z",
];
const TURKISH: &[&str] = &[
"A", "B", "C", "Ç", "D", "E", "F", "G", "Ğ", "H", "I", "İ", "J", "K", "L", "M", "N", "O", "Ö",
"P", "R", "S", "Ş", "T", "U", "Ü", "V", "Y", "Z",
];
const SPANISH: &[&str] = &[
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "Ñ", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z",
];
const RUSSIAN: &[&str] = &[
"А", "Б", "В", "Г", "Д", "Е", "Ё", "Ж", "З", "И", "Й", "К", "Л", "М", "Н", "О", "П", "Р", "С",
"Т", "У", "Ф", "Х", "Ц", "Ч", "Ш", "Щ", "Ъ", "Ы", "Ь", "Э", "Ю", "Я",
];
const ARABIC: &[&str] = &[
"ا", "ب", "ت", "ث", "ج", "ح", "خ", "د", "ذ", "ر", "ز", "س", "ش", "ص", "ض", "ط", "ظ", "ع", "غ",
"ف", "ق", "ك", "ل", "م", "ن", "ه", "و", "ي",
];
pub fn alphabet(locale: &str) -> &'static [&'static str] {
match locale
.split(['-', '_'])
.next()
.unwrap_or("")
{
"tr" | "az" => TURKISH,
"es" => SPANISH,
"ru" => RUSSIAN,
"ar" => ARABIC,
_ => LATIN,
}
}
type ParamsFn = Box<dyn Fn(&str) -> Json + Send + Sync>;
pub struct AlphaIndex {
screen: &'static str,
params: ParamsFn,
current: Option<String>,
other: bool,
}
pub const OTHER_LETTER: &str = "#";
impl AlphaIndex {
pub fn new<S: ScreenDef>(
_screen: S,
params: impl Fn(&str) -> S::Params + Send + Sync + 'static,
) -> Self {
AlphaIndex {
screen: S::META.name,
params: Box::new(move |l| serde_json::to_value(params(l)).unwrap_or(Json::Null)),
current: None,
other: false,
}
}
pub fn current(mut self, letter: &str) -> Self {
self.current = (!letter.is_empty()).then(|| letter.to_string());
self
}
pub fn other(mut self) -> Self {
self.other = true;
self
}
}
impl Component for AlphaIndex {
fn render(&self, out: &mut String, r: &Render) {
let t = |key: &str| r.text(&Text::key(format!("zeyn.ui.alpha.{key}")));
out.push_str(&format!(
r#"<nav class="z-alpha" aria-label="{}"><ul class="z-alpha-list">"#,
t("label")
));
let alphabet = alphabet(r.locale);
let current = self
.current
.as_deref()
.map(|c| match c {
"i" if alphabet == TURKISH => "İ".to_string(),
"ı" if alphabet == TURKISH => "I".to_string(),
c => c.to_uppercase(),
});
for letter in alphabet
.iter()
.copied()
.chain(self.other.then_some(OTHER_LETTER))
{
let current = current.as_deref() == Some(letter);
let named = if letter == OTHER_LETTER {
format!(r#" aria-label="{}""#, t("other"))
} else {
String::new()
};
out.push_str(&format!(
r#"<li><a class="z-alpha-link" href="{}" data-zeyn-base-href{}{named}>{}</a></li>"#,
screen_href(r, self.screen, &(self.params)(letter)),
if current {
r#" aria-current="page""#
} else {
""
},
escape(letter)
));
}
out.push_str("</ul></nav>");
}
fn schema(&self) -> Json {
json!({ "type": "alpha_index", "screen": self.screen, "current": self.current, "other": self.other })
}
}