style(html_examples): clean htmx-like shell with no low-level plumbing
- Move CSS out of app_shell.heml into app.css served by the app. - Remove direct hemx::advanced::Effect import from example code; rely on IntoEffect. - Update README to not mention raw Effect:: APIs. - Keep generated resources and tiny runtime behavior only. req: examples/001 req: htmx_equivalents/001 req: htmx_equivalents/002
This commit is contained in:
@@ -51,7 +51,7 @@ Status legend:
|
||||
| `modal-custom` | deferred | A custom modal can be a generated partial plus focus/escape policy, but needs accessibility proof before copy/paste. | Later slice should include keyboard/focus tests. |
|
||||
| `tabs-hateoas` | deferred | Good hemx fit: server-owned selected tab and generated tab panel replacement; needs a focused slice. | Later slice should add one tab group. |
|
||||
| `tabs-javascript` | refused | Client-owned tab state is exactly what generated server-owned state is meant to avoid unless a product needs it. | Prefer `tabs-hateoas`. |
|
||||
| `keyboard-shortcuts` | integration-owned | Keyboard policy belongs to the app/host; hemx should only receive explicit events. | Use `Effect::event`/app JS when needed. |
|
||||
| `keyboard-shortcuts` | integration-owned | Keyboard policy belongs to the app/host; hemx should only receive explicit events. | Use generated app-level events / app JS when needed. |
|
||||
| `sortable` | integration-owned | Drag/drop ordering needs a browser library or pointer policy plus server reorder command. | Keep Sortable.js as app-owned integration until proven reusable. |
|
||||
| `update-other-content` | implemented | Generated effects can update multiple slots from one handler; validation and search already update status plus rows/errors. | `validate_email`, `search` handlers. |
|
||||
| `confirm` | integration-owned | Confirmation wording and irreversible-action policy belong to the app; hemx should not own a global confirm system. | Use native confirm/app dialog around generated delete forms. |
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use axum::body::Body;
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use hemplate::Hemplate;
|
||||
use hemx::advanced::Effect;
|
||||
use hemx::{Html, IntoEffect};
|
||||
use hemx_axum::{
|
||||
interactions, runtime_js, runtime_js_path, EffectResponse, Form, InteractionHandlers,
|
||||
@@ -227,6 +227,7 @@ async fn main() {
|
||||
let app = Router::new()
|
||||
.route("/", get(home).post(interact))
|
||||
.route(runtime_js_path(), get(runtime))
|
||||
.route("/app.css", get(css))
|
||||
.with_state(state);
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3029));
|
||||
@@ -288,6 +289,13 @@ async fn runtime() -> impl IntoResponse {
|
||||
runtime_js()
|
||||
}
|
||||
|
||||
async fn css() -> Response {
|
||||
Response::builder()
|
||||
.header("content-type", "text/css; charset=utf-8")
|
||||
.body(Body::from(include_str!("../templates/app.css")))
|
||||
.expect("css response")
|
||||
}
|
||||
|
||||
#[hemx::app(gallery_handlers, contact_card_handlers, editable_row_handlers)]
|
||||
fn handlers(state: Arc<GalleryState>) -> InteractionHandlers {
|
||||
interactions(ui::BUILD_FINGERPRINT)
|
||||
@@ -459,7 +467,7 @@ mod gallery_handlers {
|
||||
value_options_for(&category, &value)
|
||||
.into_iter()
|
||||
.map(|option| gallery::value_option.replace(option))
|
||||
.collect::<Vec<Effect>>()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[hemx::handler]
|
||||
@@ -490,7 +498,7 @@ mod gallery_handlers {
|
||||
let rows = loaded_rows(*count)
|
||||
.into_iter()
|
||||
.map(|row| gallery::infinite_row.replace(row))
|
||||
.collect::<Vec<Effect>>();
|
||||
.collect::<Vec<_>>();
|
||||
let mut effects = rows;
|
||||
effects.push(gallery::infinite_status.set(format!("Showing {} rows", *count)));
|
||||
effects
|
||||
@@ -530,7 +538,7 @@ mod gallery_handlers {
|
||||
let rows = loaded_rows(*loaded)
|
||||
.into_iter()
|
||||
.map(|row| gallery::loaded_row.replace(row))
|
||||
.collect::<Vec<Effect>>();
|
||||
.collect::<Vec<_>>();
|
||||
let mut effects = rows;
|
||||
effects.push(gallery::load_status.set(format!("Showing {} rows", *loaded)));
|
||||
effects
|
||||
@@ -546,7 +554,7 @@ mod gallery_handlers {
|
||||
let results = search_results_for(&query)
|
||||
.into_iter()
|
||||
.map(|result| gallery::search_result.replace(result))
|
||||
.collect::<Vec<Effect>>();
|
||||
.collect::<Vec<_>>();
|
||||
let mut effects = results;
|
||||
effects.push(gallery::search_status.set(if query.is_empty() {
|
||||
"Showing all results".into()
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
:root {
|
||||
--bg: #f5f5f5;
|
||||
--surface: #ffffff;
|
||||
--ink: #222222;
|
||||
--muted: #666666;
|
||||
--border: #d4d4d4;
|
||||
--accent: #3465a4;
|
||||
--accent-hover: #29528a;
|
||||
--danger: #c0392b;
|
||||
--danger-hover: #a93226;
|
||||
--radius: 6px;
|
||||
--space: 1.25rem;
|
||||
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: var(--space);
|
||||
font-family: var(--font-body);
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
line-height: 1.55;
|
||||
}
|
||||
main {
|
||||
max-width: 820px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
header {
|
||||
margin-bottom: calc(var(--space) * 1.5);
|
||||
}
|
||||
header p:first-child {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted);
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 1.75rem;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
header p:last-child {
|
||||
color: var(--muted);
|
||||
margin: 0;
|
||||
}
|
||||
section {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: calc(var(--space) * 1.25);
|
||||
margin-bottom: var(--space);
|
||||
}
|
||||
section h2 {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 1.15rem;
|
||||
margin: 0 0 var(--space);
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
p { margin: 0 0 var(--space); }
|
||||
form { margin: 0 0 var(--space); }
|
||||
label { font-weight: 500; }
|
||||
article label { display: block; margin-bottom: 0.75rem; }
|
||||
article label input { display: block; width: 100%; margin-top: 0.3rem; }
|
||||
input, select, button {
|
||||
font: inherit;
|
||||
padding: 0.45rem 0.65rem;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
input, select { width: 100%; max-width: 360px; }
|
||||
input:focus, select:focus, button:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
button {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-color: var(--accent);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
button:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
[data-hemx-handle="delete_row"] button,
|
||||
[data-hemx-handle*="delete"] button,
|
||||
.danger {
|
||||
background: var(--danger);
|
||||
border-color: var(--danger);
|
||||
}
|
||||
[data-hemx-handle="delete_row"] button:hover,
|
||||
[data-hemx-handle*="delete"] button:hover,
|
||||
.danger:hover {
|
||||
background: var(--danger-hover);
|
||||
border-color: var(--danger-hover);
|
||||
}
|
||||
td form { display: inline-block; margin-right: 0.4rem; }
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: var(--space) 0;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th { font-weight: 600; color: var(--muted); }
|
||||
ul { padding-left: 1.25rem; margin: 0 0 var(--space); }
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 1rem;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
[data-hemx-error-for] {
|
||||
color: var(--danger);
|
||||
font-size: 0.9rem;
|
||||
margin: 0.25rem 0 0;
|
||||
}
|
||||
.htmx-indicator { opacity: 0.5; }
|
||||
@@ -4,7 +4,8 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>hemx HTML examples</title>
|
||||
<script defer="defer" +src="self.runtime_src"></script>
|
||||
<link rel="stylesheet" href="/app.css">
|
||||
<script +src="self.runtime_src" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
{+= self.body =+}
|
||||
|
||||
Reference in New Issue
Block a user