mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
UI: Cleansheet CSS redesign
Initial skeleton and layout is working. Still needs a bunch of tweaks.
This commit is contained in:
parent
61634cd682
commit
45737f24e4
@ -19,7 +19,6 @@
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" name="viewport"
|
||||
content="width=device-width, initial-scale=1.0" charset="UTF-8">
|
||||
<link rel="stylesheet" href="/ui/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/ui/static/app.css">
|
||||
</head>
|
||||
|
||||
@ -35,4 +34,4 @@
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -23,9 +23,9 @@ pub fn Header<'ctx, G: Html>(cx: Scope<'ctx>, h: StateHandler<'ctx>) -> View<G>
|
||||
None => "Login".to_owned(),
|
||||
});
|
||||
view! {cx,
|
||||
nav(class="no-print") {
|
||||
nav(class="no-print row-flex align-center header-bg heavy-bottom-border") {
|
||||
h1(class="title") { "Kitchen" }
|
||||
ul {
|
||||
ul(class="row-flex align-center") {
|
||||
li { a(href="/ui/planning/select") { "MealPlan" } }
|
||||
li { a(href="/ui/manage/ingredients") { "Manage" } }
|
||||
li { a(href="/ui/login") { (login.get()) } }
|
||||
|
@ -211,6 +211,7 @@ where
|
||||
F: Fn(Event),
|
||||
{
|
||||
name: String,
|
||||
class: String,
|
||||
on_change: Option<F>,
|
||||
min: f64,
|
||||
counter: &'ctx Signal<f64>,
|
||||
@ -223,6 +224,7 @@ where
|
||||
{
|
||||
let NumberProps {
|
||||
name,
|
||||
class,
|
||||
on_change,
|
||||
min,
|
||||
counter,
|
||||
@ -241,7 +243,7 @@ where
|
||||
});
|
||||
let id = name.clone();
|
||||
view! {cx,
|
||||
number-spinner(id=id, val=*counter.get(), min=min, on:updated=move |evt: Event| {
|
||||
number-spinner(id=id, class=(class), val=*counter.get(), min=min, on:updated=move |evt: Event| {
|
||||
let target: HtmlElement = evt.target().unwrap().dyn_into().unwrap();
|
||||
let val: f64 = target.get_attribute("val").unwrap().parse().unwrap();
|
||||
counter.set(val);
|
||||
|
@ -38,12 +38,12 @@ pub fn PlanList<'ctx, G: Html>(cx: Scope<'ctx>, props: PlanListProps<'ctx>) -> V
|
||||
view!{cx,
|
||||
tr() {
|
||||
td() {
|
||||
span(role="button", class="outline", on:click=move |_| {
|
||||
button(class="outline", on:click=move |_| {
|
||||
sh.dispatch(cx, Message::SelectPlanDate(date, None))
|
||||
}) { (date_display) }
|
||||
}
|
||||
td() {
|
||||
span(role="button", class="destructive", on:click=move |_| {
|
||||
button(class="destructive", on:click=move |_| {
|
||||
sh.dispatch(cx, Message::DeletePlan(date, None))
|
||||
}) { "Delete Plan" }
|
||||
}
|
||||
|
@ -79,12 +79,14 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
||||
|
||||
debug!("creating editor view");
|
||||
view! {cx,
|
||||
label(for="recipe_category") { "Category" }
|
||||
input(name="recipe_category", bind:value=category, on:change=move |_| dirty.set(true))
|
||||
div(class="grid") {
|
||||
div {
|
||||
label(for="recipe_text") { "Recipe" }
|
||||
textarea(name="recipe_text", bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||
div {
|
||||
label(for="recipe_category") { "Category" }
|
||||
input(name="recipe_category", bind:value=category, on:change=move |_| dirty.set(true))
|
||||
}
|
||||
div {
|
||||
div(class="row-flex") {
|
||||
label(for="recipe_text", class="block align-stretch expand-height") { "Recipe: " }
|
||||
textarea(class="width-third", name="recipe_text", bind:value=text, aria-invalid=aria_hint.get(), cols="50", rows=20, on:change=move |_| {
|
||||
dirty.set(true);
|
||||
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||
}, on:input=move |_| {
|
||||
@ -97,34 +99,36 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
||||
}
|
||||
div(class="parse") { (error_text.get()) }
|
||||
}
|
||||
span(role="button", on:click=move |_| {
|
||||
let unparsed = text.get_untracked();
|
||||
if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) {
|
||||
debug!("triggering a save");
|
||||
if !*dirty.get_untracked() {
|
||||
debug!("Recipe text is unchanged");
|
||||
return;
|
||||
div {
|
||||
button(on:click=move |_| {
|
||||
let unparsed = text.get_untracked();
|
||||
if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) {
|
||||
debug!("triggering a save");
|
||||
if !*dirty.get_untracked() {
|
||||
debug!("Recipe text is unchanged");
|
||||
return;
|
||||
}
|
||||
debug!("Recipe text is changed");
|
||||
let category = category.get_untracked();
|
||||
let category = if category.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(category.as_ref().clone())
|
||||
};
|
||||
let recipe_entry = RecipeEntry(
|
||||
id.get_untracked().as_ref().clone(),
|
||||
text.get_untracked().as_ref().clone(),
|
||||
category,
|
||||
);
|
||||
sh.dispatch(cx, Message::SaveRecipe(recipe_entry, None));
|
||||
dirty.set(false);
|
||||
}
|
||||
debug!("Recipe text is changed");
|
||||
let category = category.get_untracked();
|
||||
let category = if category.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(category.as_ref().clone())
|
||||
};
|
||||
let recipe_entry = RecipeEntry(
|
||||
id.get_untracked().as_ref().clone(),
|
||||
text.get_untracked().as_ref().clone(),
|
||||
category,
|
||||
);
|
||||
sh.dispatch(cx, Message::SaveRecipe(recipe_entry, None));
|
||||
dirty.set(false);
|
||||
}
|
||||
// TODO(jwall): Show error message if trying to save when recipe doesn't parse.
|
||||
}) { "Save" } " "
|
||||
span(role="button", on:click=move |_| {
|
||||
sh.dispatch(cx, Message::RemoveRecipe(id.get_untracked().as_ref().to_owned(), Some(Box::new(|| sycamore_router::navigate("/ui/planning/plan")))));
|
||||
}) { "delete" } " "
|
||||
// TODO(jwall): Show error message if trying to save when recipe doesn't parse.
|
||||
}) { "Save" } " "
|
||||
button(on:click=move |_| {
|
||||
sh.dispatch(cx, Message::RemoveRecipe(id.get_untracked().as_ref().to_owned(), Some(Box::new(|| sycamore_router::navigate("/ui/planning/plan")))));
|
||||
}) { "delete" } " "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ pub fn CategoryGroup<'ctx, G: Html>(
|
||||
});
|
||||
view! {cx,
|
||||
h2 { (category) }
|
||||
div(class="recipe_selector no-print") {
|
||||
div(class="no-print flex-wrap-start align-stretch") {
|
||||
(View::new_fragment(
|
||||
rows.get().iter().cloned().map(|r| {
|
||||
view ! {cx,
|
||||
@ -61,7 +61,7 @@ pub fn CategoryGroup<'ctx, G: Html>(
|
||||
view=move |cx, sig| {
|
||||
let title = create_memo(cx, move || sig.get().1.title.clone());
|
||||
view! {cx,
|
||||
div(class="cell") { RecipeSelection(i=sig.get().0.to_owned(), title=title, sh=sh) }
|
||||
div(class="cell column-flex justify-end align-stretch") { RecipeSelection(i=sig.get().0.to_owned(), title=title, sh=sh) }
|
||||
}
|
||||
},
|
||||
key=|sig| sig.get().0.to_owned(),
|
||||
@ -108,13 +108,13 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie
|
||||
},
|
||||
key=|(ref cat, _)| cat.clone(),
|
||||
)
|
||||
span(role="button", on:click=move |_| {
|
||||
button(on:click=move |_| {
|
||||
sh.dispatch(cx, Message::LoadState(None));
|
||||
}) { "Reset" } " "
|
||||
span(role="button", on:click=move |_| {
|
||||
button(on:click=move |_| {
|
||||
sh.dispatch(cx, Message::ResetRecipeCounts);
|
||||
}) { "Clear All" } " "
|
||||
span(role="button", on:click=move |_| {
|
||||
button(on:click=move |_| {
|
||||
// Poor man's click event signaling.
|
||||
sh.dispatch(cx, Message::SaveState(None));
|
||||
}) { "Save Plan" } " "
|
||||
|
@ -65,8 +65,8 @@ pub fn RecipeSelection<'ctx, G: Html>(
|
||||
let name = format!("recipe_id:{}", id);
|
||||
let for_id = name.clone();
|
||||
view! {cx,
|
||||
label(for=for_id) { a(href=href) { (*title) } }
|
||||
NumberField(name=name, counter=count, min=0.0, on_change=Some(move |_| {
|
||||
label(for=for_id, class="flex-item-grow") { a(href=href) { (*title) } }
|
||||
NumberField(name=name, class="flex-item-shrink".to_string(), counter=count, min=0.0, on_change=Some(move |_| {
|
||||
debug!(idx=%id, count=%(*count.get_untracked()), "setting recipe count");
|
||||
sh.dispatch(cx, Message::UpdateRecipeCount(id.as_ref().clone(), *count.get_untracked() as usize));
|
||||
}))
|
||||
|
@ -205,15 +205,15 @@ pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> V
|
||||
sh.dispatch(cx, Message::UpdateUseStaples(value));
|
||||
})
|
||||
(make_shopping_table(cx, sh, show_staples))
|
||||
span(role="button", class="no-print", on:click=move |_| {
|
||||
button(class="no-print", on:click=move |_| {
|
||||
info!("Registering add item request for inventory");
|
||||
sh.dispatch(cx, Message::AddExtra(String::new(), String::new()));
|
||||
}) { "Add Item" } " "
|
||||
span(role="button", class="no-print", on:click=move |_| {
|
||||
button(class="no-print", on:click=move |_| {
|
||||
info!("Registering reset request for inventory");
|
||||
sh.dispatch(cx, Message::ResetInventory);
|
||||
}) { "Reset" } " "
|
||||
span(role="button", class="no-print", on:click=move |_| {
|
||||
button(class="no-print", on:click=move |_| {
|
||||
info!("Registering save request for inventory");
|
||||
sh.dispatch(cx, Message::SaveState(None));
|
||||
}) { "Save" } " "
|
||||
|
@ -72,8 +72,8 @@ pub fn IngredientsEditor<'ctx, G: Html>(
|
||||
|
||||
debug!("creating editor view");
|
||||
view! {cx,
|
||||
div(class="grid") {
|
||||
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||
div {
|
||||
textarea(class="width-third", bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||
dirty.set(true);
|
||||
}, on:input=move |_| {
|
||||
let current_ts = js_lib::get_ms_timestamp();
|
||||
@ -84,7 +84,7 @@ pub fn IngredientsEditor<'ctx, G: Html>(
|
||||
})
|
||||
div(class="parse") { (error_text.get()) }
|
||||
}
|
||||
span(role="button", on:click=move |_| {
|
||||
button(on:click=move |_| {
|
||||
let unparsed = text.get();
|
||||
if !*dirty.get_untracked() {
|
||||
debug!("Staples text is unchanged");
|
||||
|
@ -47,12 +47,12 @@ pub fn TabbedView<'a, G: Html>(cx: Scope<'a>, state: TabState<'a, G>) -> View<G>
|
||||
.collect(),
|
||||
);
|
||||
view! {cx,
|
||||
nav {
|
||||
ul(class="tabs") {
|
||||
nav(class="menu-bg expand-height") {
|
||||
ul(class="tabs pad-left") {
|
||||
(menu)
|
||||
}
|
||||
}
|
||||
main(class=".conatiner-fluid") {
|
||||
main {
|
||||
(children)
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ pub fn LoginForm<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View
|
||||
input(type="text", id="username", bind:value=username)
|
||||
label(for="password") { "Password" }
|
||||
input(type="password", bind:value=password)
|
||||
span(role="button", on:click=move |_| {
|
||||
button(on:click=move |_| {
|
||||
info!("Attempting login request");
|
||||
let (username, password) = ((*username.get_untracked()).clone(), (*password.get_untracked()).clone());
|
||||
if username != "" && password != "" {
|
||||
|
@ -37,7 +37,7 @@ pub fn SelectPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie
|
||||
selected=Some("Select".to_owned()),
|
||||
) {
|
||||
PlanList(sh=sh, list=plan_dates)
|
||||
span(role="button", on:click=move |_| {
|
||||
button(on:click=move |_| {
|
||||
sh.dispatch(cx, Message::SelectPlanDate(chrono::offset::Local::now().naive_local().date(), Some(Box::new(|| {
|
||||
sycamore_router::navigate("/ui/planning/plan");
|
||||
}))))
|
||||
|
@ -136,11 +136,12 @@ pub fn Handler<'ctx, G: Html>(cx: Scope<'ctx>, props: HandlerProps<'ctx>) -> Vie
|
||||
integration=HistoryIntegration::new(),
|
||||
view=move |cx: Scope, route: &ReadSignal<Routes>| {
|
||||
view!{cx,
|
||||
div(class="app") {
|
||||
Header(sh)
|
||||
div {
|
||||
Header(sh)
|
||||
div(class="app row-flex flex-item-grow expand-height align-stretch") {
|
||||
(route_switch(route.get().as_ref(), cx, sh))
|
||||
Footer { }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2022 Jeremy Wall
|
||||
* Copyright 2023 Jeremy Wall
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,12 +21,33 @@
|
||||
--unicode-button-size: 2em;
|
||||
--toast-anim-duration: 3s;
|
||||
--notification-font-size: calc(var(--font-size) / 2);
|
||||
--error-message-color: rgba(255, 98, 0, 0.797);
|
||||
--error-message-color: #CD5C08;
|
||||
--error-message-bg: grey;
|
||||
--border-width: 2px;
|
||||
--border-width: 3px;
|
||||
--cell-margin: 1em;
|
||||
--nav-margin: 2em;
|
||||
--main-color: #A9907E;
|
||||
--light-accent: #F3DEBA;
|
||||
--dark-accent: #ABC4AA;
|
||||
--heavy-accent: #675D50;
|
||||
--text-color: black;
|
||||
--menu-bg: var(--main-color);
|
||||
--header-bg: var(--light-accent);
|
||||
--font-size: 20px;
|
||||
--cell-target: 30%;
|
||||
}
|
||||
|
||||
/** TODO(jwall): Dark color scheme?
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text-color: white;
|
||||
--menu-bg: var(--main-color);
|
||||
--header-bg: var(--dark-accent);
|
||||
}
|
||||
}
|
||||
**/
|
||||
|
||||
/** TODO(jwall): Seperate these out into composable classes **/
|
||||
@media print {
|
||||
|
||||
.no-print,
|
||||
@ -39,28 +60,101 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
:root {
|
||||
--font-size: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--tab-border-color: lightgrey;
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets **/
|
||||
body {
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background-color: var(--header-bg);
|
||||
font-size: var(--font-size)
|
||||
}
|
||||
|
||||
nav>ul.tabs>li {
|
||||
border-style: none;
|
||||
body, body * {
|
||||
color: black;
|
||||
}
|
||||
|
||||
nav>ul.tabs>li.selected {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/** Our specific page elements **/
|
||||
|
||||
/** TODO(jwall): Move these onto the html elements themselves. **/
|
||||
.column-flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row-flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-item-grow {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.flex-item-shrink {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.flex-wrap-start {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.expand-height {
|
||||
height: 100%;
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.align-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.width-third {
|
||||
min-width: fit-content;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/** nav elements **/
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
nav li {
|
||||
margin-right: var(--nav-margin);
|
||||
}
|
||||
|
||||
nav li a::after {
|
||||
content: '| ";
|
||||
}
|
||||
|
||||
nav li a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
background-color: var(--header-bg);
|
||||
}
|
||||
|
||||
.heavy-bottom-border {
|
||||
border-bottom: var(--border-width) solid var(--heavy-accent)
|
||||
}
|
||||
|
||||
.selected {
|
||||
border-style: none;
|
||||
border-bottom-style: var(--tab-border-style);
|
||||
border-bottom-color: var(--tab-border-color);
|
||||
@ -74,10 +168,40 @@ nav>h1 {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
|
||||
main {
|
||||
border-bottom-left-radius: 1em;
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
overflow-block: scroll;
|
||||
}
|
||||
|
||||
.cell {
|
||||
margin: 1em;
|
||||
width: var(--cell-target);
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.menu-bg {
|
||||
background-color: var(--menu-bg);
|
||||
}
|
||||
|
||||
.pad-left {
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
.app nav li {
|
||||
margin-bottom: var(--nav-margin);
|
||||
}
|
||||
|
||||
.destructive {
|
||||
background-color: firebrick !important;
|
||||
background-color: #CD5C08 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-count-inc-dec {
|
||||
@ -129,24 +253,3 @@ nav>h1 {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
.recipe_selector {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
align-content: stretch;
|
||||
}
|
||||
|
||||
.recipe_selector .cell {
|
||||
margin: 1em;
|
||||
width: calc(100% / 5);
|
||||
}
|
||||
|
||||
.cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
align-content: stretch;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user