tighten whitespace, DRY shell and buttons
This commit is contained in:
parent
0b3b1b2731
commit
a88eba7dd1
9 changed files with 439 additions and 225 deletions
|
|
@ -4,6 +4,41 @@ import htpy as h
|
|||
from htpy import Node, Renderable
|
||||
|
||||
|
||||
def _button_classes(*, tone: str, emphasis: str, disabled: bool = False) -> str:
|
||||
base = (
|
||||
"inline-flex items-center justify-center rounded-full font-semibold transition "
|
||||
)
|
||||
emphasis_classes = {
|
||||
"compact": "px-3 py-1.5 text-sm",
|
||||
"regular": "px-4 py-2.5 text-sm",
|
||||
"soft": "px-3.5 py-2 text-sm",
|
||||
}
|
||||
tone_classes = {
|
||||
"amber": "bg-amber-400 text-slate-950 hover:bg-amber-300",
|
||||
"header-secondary": (
|
||||
"border border-white/15 bg-white/5 text-white hover:bg-white/10"
|
||||
),
|
||||
"muted": "border border-slate-200 bg-white text-slate-700 shadow-sm hover:bg-slate-50",
|
||||
"default": "bg-stone-100 text-slate-700 hover:bg-stone-200",
|
||||
"danger": "bg-rose-50 text-rose-700 hover:bg-rose-100",
|
||||
"success": "bg-emerald-100 text-emerald-800 hover:bg-emerald-200",
|
||||
"dark": "bg-slate-950 text-white hover:bg-slate-800",
|
||||
}
|
||||
disabled_classes = {
|
||||
"default": "bg-slate-100 text-slate-400",
|
||||
"danger": "bg-slate-100 text-slate-400",
|
||||
"success": "bg-slate-100 text-slate-400",
|
||||
"dark": "bg-slate-300 text-white/80",
|
||||
}
|
||||
interactive = "cursor-not-allowed" if disabled else "cursor-pointer"
|
||||
colors = (
|
||||
disabled_classes.get(tone, "bg-slate-100 text-slate-400")
|
||||
if disabled
|
||||
else tone_classes[tone]
|
||||
)
|
||||
return f"{base}{emphasis_classes[emphasis]} {interactive} {colors}"
|
||||
|
||||
|
||||
def base_layout(*, page_title: str, stylesheet_href: str, content: Node) -> Renderable:
|
||||
return h.html(lang="en", class_="h-full bg-slate-100")[
|
||||
h.head[
|
||||
|
|
@ -43,15 +78,15 @@ def admin_sidebar(
|
|||
*, current_path: str, source_count: int = 0, running_count: int = 0
|
||||
) -> Renderable:
|
||||
return h.aside(
|
||||
class_="relative overflow-hidden bg-slate-950 px-6 py-8 text-white lg:min-h-screen"
|
||||
class_="relative overflow-hidden bg-slate-950 px-4 py-6 text-white lg:min-h-screen"
|
||||
)[
|
||||
h.div(
|
||||
class_="absolute inset-x-0 top-0 h-40 bg-radial from-amber-400/25 via-amber-400/10 to-transparent"
|
||||
),
|
||||
h.div(class_="relative flex h-full flex-col")[
|
||||
h.div(class_="flex items-center gap-3")[
|
||||
h.div(class_="flex items-center gap-2.5")[
|
||||
h.div(
|
||||
class_="flex size-11 items-center justify-center rounded-2xl bg-amber-400 text-base font-black text-slate-950"
|
||||
class_="flex size-10 items-center justify-center rounded-2xl bg-amber-400 text-base font-black text-slate-950"
|
||||
)["AR"],
|
||||
h.div[
|
||||
h.p(
|
||||
|
|
@ -59,7 +94,7 @@ def admin_sidebar(
|
|||
)["Republisher"],
|
||||
],
|
||||
],
|
||||
h.nav(class_="mt-10 space-y-2")[
|
||||
h.nav(class_="mt-8 space-y-2")[
|
||||
nav_link(
|
||||
label="Dashboard",
|
||||
href="/",
|
||||
|
|
@ -86,7 +121,7 @@ def admin_sidebar(
|
|||
badge="App",
|
||||
),
|
||||
],
|
||||
h.div(class_="mt-auto rounded-3xl bg-white/5 p-5 ring-1 ring-white/10")[
|
||||
h.div(class_="mt-auto rounded-3xl bg-white/5 p-4 ring-1 ring-white/10")[
|
||||
h.p(class_="text-sm font-semibold text-white")[
|
||||
"AnyNews Republisher v2.0"
|
||||
],
|
||||
|
|
@ -101,21 +136,21 @@ def admin_sidebar(
|
|||
def header_action_link(*, href: str, label: str) -> Renderable:
|
||||
return h.a(
|
||||
href=href,
|
||||
class_="inline-flex items-center rounded-full bg-amber-400 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-sm transition hover:bg-amber-300",
|
||||
class_=_button_classes(tone="amber", emphasis="regular"),
|
||||
)[label]
|
||||
|
||||
|
||||
def header_secondary_link(*, href: str, label: str) -> Renderable:
|
||||
return h.a(
|
||||
href=href,
|
||||
class_="inline-flex items-center rounded-full border border-white/15 bg-white/5 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/10",
|
||||
class_=_button_classes(tone="header-secondary", emphasis="regular"),
|
||||
)[label]
|
||||
|
||||
|
||||
def muted_action_link(*, href: str, label: str) -> Renderable:
|
||||
return h.a(
|
||||
href=href,
|
||||
class_="inline-flex items-center rounded-full border border-slate-200 bg-white px-3.5 py-2 text-sm font-semibold text-slate-700 shadow-sm transition hover:bg-slate-50",
|
||||
class_=_button_classes(tone="muted", emphasis="soft"),
|
||||
)[label]
|
||||
|
||||
|
||||
|
|
@ -131,22 +166,58 @@ def inline_link(*, href: str, label: str, tone: str = "default") -> Renderable:
|
|||
)[label]
|
||||
|
||||
|
||||
def action_button(
|
||||
*,
|
||||
label: str,
|
||||
tone: str = "default",
|
||||
emphasis: str = "compact",
|
||||
disabled: bool = False,
|
||||
button_type: str = "button",
|
||||
post_path: str | None = None,
|
||||
) -> Renderable:
|
||||
attributes: dict[str, str] = {}
|
||||
if post_path is not None and not disabled:
|
||||
attributes["data-on:pointerdown"] = f"@post('{post_path}')"
|
||||
return h.button(
|
||||
attributes,
|
||||
type=button_type,
|
||||
disabled=disabled,
|
||||
class_=_button_classes(tone=tone, emphasis=emphasis, disabled=disabled),
|
||||
)[label]
|
||||
|
||||
|
||||
def inline_button(
|
||||
*, label: str, tone: str = "default", disabled: bool = False
|
||||
) -> Renderable:
|
||||
classes = {
|
||||
"default": "bg-stone-100 text-slate-700 hover:bg-stone-200",
|
||||
"danger": "bg-rose-50 text-rose-700 hover:bg-rose-100",
|
||||
"success": "bg-emerald-100 text-emerald-800 hover:bg-emerald-200",
|
||||
}
|
||||
class_name = (
|
||||
"cursor-not-allowed bg-slate-100 text-slate-400" if disabled else classes[tone]
|
||||
)
|
||||
return h.button(
|
||||
type="button",
|
||||
return action_button(
|
||||
label=label,
|
||||
tone=tone,
|
||||
emphasis="compact",
|
||||
button_type="button",
|
||||
disabled=disabled,
|
||||
class_=f"inline-flex items-center whitespace-nowrap rounded-full px-3 py-1.5 text-sm font-semibold transition {class_name}",
|
||||
)[label]
|
||||
)
|
||||
|
||||
|
||||
def app_shell(
|
||||
*,
|
||||
current_path: str,
|
||||
source_count: int = 0,
|
||||
running_count: int = 0,
|
||||
content: Node,
|
||||
) -> Renderable:
|
||||
return h.main(
|
||||
id="morph",
|
||||
class_="min-h-screen lg:grid lg:grid-cols-[14rem_minmax(0,1fr)]",
|
||||
)[
|
||||
admin_sidebar(
|
||||
current_path=current_path,
|
||||
source_count=source_count,
|
||||
running_count=running_count,
|
||||
),
|
||||
h.div(class_="px-4 py-4 sm:px-4 lg:px-5 lg:py-4")[
|
||||
h.div(class_="mx-auto max-w-7xl space-y-4")[content]
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
def page_shell(
|
||||
|
|
@ -160,39 +231,30 @@ def page_shell(
|
|||
running_count: int = 0,
|
||||
content: Node,
|
||||
) -> Renderable:
|
||||
return h.main(
|
||||
id="morph",
|
||||
class_="min-h-screen lg:grid lg:grid-cols-[18rem_minmax(0,1fr)]",
|
||||
)[
|
||||
admin_sidebar(
|
||||
current_path=current_path,
|
||||
source_count=source_count,
|
||||
running_count=running_count,
|
||||
return app_shell(
|
||||
current_path=current_path,
|
||||
source_count=source_count,
|
||||
running_count=running_count,
|
||||
content=(
|
||||
h.section[
|
||||
h.div(
|
||||
class_="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
||||
)[
|
||||
h.div(class_="max-w-3xl")[
|
||||
h.h1(
|
||||
class_="text-3xl font-semibold tracking-tight text-slate-950"
|
||||
)[title],
|
||||
(
|
||||
description
|
||||
and h.p(class_="mt-1 text-sm text-slate-600")[description]
|
||||
),
|
||||
],
|
||||
actions and h.div(class_="flex flex-wrap gap-2")[actions],
|
||||
]
|
||||
],
|
||||
content,
|
||||
),
|
||||
h.div(class_="px-4 py-4 sm:px-5 lg:px-6 lg:py-5")[
|
||||
h.div(class_="mx-auto max-w-7xl space-y-5")[
|
||||
h.section[
|
||||
h.div(
|
||||
class_="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
||||
)[
|
||||
h.div(class_="max-w-3xl")[
|
||||
h.h1(
|
||||
class_="text-3xl font-semibold tracking-tight text-slate-950"
|
||||
)[title],
|
||||
(
|
||||
description
|
||||
and h.p(class_="mt-1 text-sm text-slate-600")[
|
||||
description
|
||||
]
|
||||
),
|
||||
],
|
||||
actions and h.div(class_="flex flex-wrap gap-2")[actions],
|
||||
]
|
||||
],
|
||||
content,
|
||||
]
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def section_card(*, content: Node) -> Renderable:
|
||||
|
|
@ -212,12 +274,12 @@ def table_section(
|
|||
def render_row(row: tuple[Node, ...]) -> Renderable:
|
||||
first_cell, *other_cells = row
|
||||
return h.tr(class_="align-top")[
|
||||
h.td(class_="py-4 pr-6 pl-4 text-sm font-medium text-slate-950 sm:pl-6")[
|
||||
h.td(class_="py-3 pr-5 pl-3 text-sm font-medium text-slate-950 sm:pl-4")[
|
||||
first_cell
|
||||
],
|
||||
(
|
||||
h.td(
|
||||
class_="px-3 py-4 align-top text-sm whitespace-nowrap text-slate-600"
|
||||
class_="px-2.5 py-3 align-top text-sm whitespace-nowrap text-slate-600"
|
||||
)[cell]
|
||||
for cell in other_cells
|
||||
),
|
||||
|
|
@ -230,12 +292,14 @@ def table_section(
|
|||
body_rows = h.tr[
|
||||
h.td(
|
||||
colspan=str(len(headers)),
|
||||
class_="px-4 py-8 text-center text-sm text-slate-500 sm:px-6",
|
||||
class_="px-3 py-8 text-center text-sm text-slate-500 sm:px-4",
|
||||
)[empty_message]
|
||||
]
|
||||
|
||||
return h.section[
|
||||
h.div(class_="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between")[
|
||||
h.div(
|
||||
class_="flex flex-col gap-2.5 sm:flex-row sm:items-end sm:justify-between"
|
||||
)[
|
||||
h.div[
|
||||
eyebrow
|
||||
and h.p(
|
||||
|
|
@ -251,14 +315,14 @@ def table_section(
|
|||
)[
|
||||
h.div(class_="overflow-x-auto")[
|
||||
h.table(
|
||||
class_="relative w-full min-w-[72rem] divide-y divide-slate-200 table-auto"
|
||||
class_="relative w-full min-w-[64rem] divide-y divide-slate-200 table-auto"
|
||||
)[
|
||||
h.thead(class_="bg-stone-50")[
|
||||
h.tr[
|
||||
(
|
||||
h.th(
|
||||
scope="col",
|
||||
class_="px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 first:pl-4 sm:first:pl-6",
|
||||
class_="px-2.5 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 first:pl-3 sm:first:pl-4",
|
||||
)[header]
|
||||
for header in headers
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue