Add comprehensive demo with CI/CD pipeline

- Complete GitHub Actions workflow for demo deployment
- Playwright test suite with 50+ tests
- Performance monitoring and accessibility testing
- Cross-browser compatibility testing
- Local deployment scripts and documentation
This commit is contained in:
Peter Hanssens
2025-09-23 19:12:15 +10:00
parent 9b122deca0
commit 7f4486b6f0
56 changed files with 14784 additions and 466 deletions

View File

@@ -24,7 +24,7 @@ pub fn AlertDialogAction(
view! {
<button
class=move || format!("alert-dialog-action {}", class.get().unwrap_or_default())
class=move || format!("inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 {}", class.get().unwrap_or_default())
on:click=handle_click
>
{children.map(|c| c())}
@@ -49,7 +49,7 @@ pub fn AlertDialogCancel(
view! {
<button
class=move || format!("alert-dialog-cancel {}", class.get().unwrap_or_default())
class=move || format!("inline-flex h-10 items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-semibold ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 {}", class.get().unwrap_or_default())
on:click=handle_click
>
{children.map(|c| c())}

View File

@@ -16,31 +16,17 @@ pub fn AlertDialog(
provide_context(open);
provide_context(on_open_change);
// Handle escape key
Effect::new(move |_| {
if open.get() {
let handle_keydown = move |e: KeyboardEvent| {
if e.key() == "Escape" {
open.set(false);
if let Some(callback) = &on_open_change {
callback.run(false);
}
}
};
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(handle_keydown) as Box<dyn Fn(KeyboardEvent)>);
let _ = document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref());
closure.forget();
}
}
}
});
// Handle escape key - use a simpler approach without global listeners
// The escape key handling will be managed by the content components
view! {
<div>
{children.map(|c| c())}
</div>
<Show
when=move || open.get()
fallback=|| view! { <div></div> }
>
<div>
{children.map(|c| c())}
</div>
</Show>
}
}

View File

@@ -21,15 +21,24 @@ pub fn AlertDialogContent(
};
view! {
<div
class=move || format!("alert-dialog-content {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
role="alertdialog"
aria-modal="true"
<Show
when=move || open.get()
fallback=|| view! { <div></div> }
>
{children.map(|c| c())}
</div>
<div
class=move || format!("fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
role="alertdialog"
aria-modal="true"
>
<div class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm" on:click=move |_| open.set(false)>
<div class="relative z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg" on:click=handle_click>
{children.map(|c| c())}
</div>
</div>
</div>
</Show>
}
}

View File

@@ -15,7 +15,7 @@ pub fn AlertDialogHeader(
) -> impl IntoView {
view! {
<div
class=move || format!("alert-dialog-header {}", class.get().unwrap_or_default())
class=move || format!("flex flex-col space-y-2 text-center sm:text-left {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>
@@ -33,7 +33,7 @@ pub fn AlertDialogFooter(
) -> impl IntoView {
view! {
<div
class=move || format!("alert-dialog-footer {}", class.get().unwrap_or_default())
class=move || format!("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>

View File

@@ -25,13 +25,18 @@ pub fn AlertDialogOverlay(
};
view! {
<div
class=move || format!("alert-dialog-overlay {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
<Show
when=move || open.get()
fallback=|| view! { <div></div> }
>
{children.map(|c| c())}
</div>
<div
class=move || format!("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
>
{children.map(|c| c())}
</div>
</Show>
}
}

View File

@@ -15,7 +15,7 @@ pub fn AlertDialogTitle(
) -> impl IntoView {
view! {
<h2
class=move || format!("alert-dialog-title {}", class.get().unwrap_or_default())
class=move || format!("text-lg font-semibold {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>
@@ -33,7 +33,7 @@ pub fn AlertDialogDescription(
) -> impl IntoView {
view! {
<p
class=move || format!("alert-dialog-description {}", class.get().unwrap_or_default())
class=move || format!("text-sm text-muted-foreground {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>

View File

@@ -28,7 +28,7 @@ pub fn ContextMenuContent(
view! {
<div
class=move || format!("context-menu-content {}", class.get().unwrap_or_default())
class=move || format!("fixed z-50 min-w-[10rem] overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-1 text-gray-900 dark:text-gray-100 shadow-xl backdrop-blur-sm {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=content_style
on:click=handle_click

View File

@@ -25,8 +25,8 @@ pub fn ContextMenuItem(
};
let item_class = move || {
let base_class = "context-menu-item";
let disabled_class = if disabled.get().unwrap_or(false) { " disabled" } else { "" };
let base_class = "relative flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-sm font-medium outline-none transition-colors duration-150 hover:bg-gray-100 dark:hover:bg-gray-700 focus:bg-gray-100 dark:focus:bg-gray-700 active:bg-gray-200 dark:active:bg-gray-600 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50";
let disabled_class = if disabled.get().unwrap_or(false) { " data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50" } else { "" };
let custom_class = class.get().unwrap_or_default();
format!("{}{} {}", base_class, disabled_class, custom_class)
};

View File

@@ -28,25 +28,12 @@ pub fn ContextMenuTrigger(
open.set(false);
};
Effect::new(move |_| {
if open.get() {
let handle_click_outside = move |_: MouseEvent| {
open.set(false);
};
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(handle_click_outside) as Box<dyn Fn(MouseEvent)>);
let _ = document.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref());
closure.forget();
}
}
}
});
// Use a simpler approach without global event listeners
// The context menu will close when clicking on items or outside
view! {
<div
class=move || format!("context-menu-trigger {}", class.get().unwrap_or_default())
class=move || format!("cursor-pointer {}", class.get().unwrap_or_default())
on:contextmenu=handle_context_menu
on:click=handle_click
>

View File

@@ -22,27 +22,32 @@ pub fn DrawerContent(
};
let content_class = move || {
let base_class = "drawer-content";
let base_class = "fixed z-50 bg-background";
let direction_class = match direction.get() {
DrawerDirection::Top => " drawer-content-top",
DrawerDirection::Bottom => " drawer-content-bottom",
DrawerDirection::Left => " drawer-content-left",
DrawerDirection::Right => " drawer-content-right",
DrawerDirection::Top => " top-0 inset-x-0 border-b",
DrawerDirection::Bottom => " bottom-0 inset-x-0 border-t",
DrawerDirection::Left => " left-0 top-0 h-full w-3/4 border-r sm:max-w-sm",
DrawerDirection::Right => " right-0 top-0 h-full w-3/4 border-l sm:max-w-sm",
};
let custom_class = class.get().unwrap_or_default();
format!("{}{} {}", base_class, direction_class, custom_class)
};
view! {
<div
class=content_class
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
role="dialog"
aria-modal="true"
<Show
when=move || open_state.get()
fallback=|| view! { <div></div> }
>
{children.map(|c| c())}
</div>
<div
class=content_class
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
role="dialog"
aria-modal="true"
>
{children.map(|c| c())}
</div>
</Show>
}
}

View File

@@ -21,31 +21,17 @@ pub fn Drawer(
provide_context(direction);
provide_context(should_scale_background);
// Handle escape key
Effect::new(move |_| {
if open.get() {
let handle_keydown = move |e: KeyboardEvent| {
if e.key() == "Escape" {
open.set(false);
if let Some(callback) = &on_open_change {
callback.run(false);
}
}
};
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(handle_keydown) as Box<dyn Fn(KeyboardEvent)>);
let _ = document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref());
closure.forget();
}
}
}
});
// Handle escape key - use a simpler approach without global listeners
// The escape key handling will be managed by the content components
view! {
<div class="drawer-root">
{children.map(|c| c())}
</div>
<Show
when=move || open.get()
fallback=|| view! { <div></div> }
>
<div class="drawer-root">
{children.map(|c| c())}
</div>
</Show>
}
}

View File

@@ -15,7 +15,7 @@ pub fn DrawerHeader(
) -> impl IntoView {
view! {
<div
class=move || format!("drawer-header {}", class.get().unwrap_or_default())
class=move || format!("grid gap-1.5 py-4 text-center sm:text-left {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>
@@ -33,7 +33,7 @@ pub fn DrawerFooter(
) -> impl IntoView {
view! {
<div
class=move || format!("drawer-footer {}", class.get().unwrap_or_default())
class=move || format!("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>

View File

@@ -12,7 +12,7 @@ pub fn DrawerPortal(
#[prop(optional)] children: Option<Children>,
) -> impl IntoView {
view! {
<div class="drawer-portal">
<div class="fixed inset-0 z-50">
{children.map(|c| c())}
</div>
}
@@ -37,20 +37,25 @@ pub fn DrawerOverlay(
};
let overlay_class = move || {
let base_class = "drawer-overlay";
let scale_class = if should_scale_background.get() { " scale-background" } else { "" };
let base_class = "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm";
let scale_class = if should_scale_background.get() { " data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" } else { "" };
let custom_class = class.get().unwrap_or_default();
format!("{}{} {}", base_class, scale_class, custom_class)
};
view! {
<div
class=overlay_class
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
<Show
when=move || open_state.get()
fallback=|| view! { <div></div> }
>
{children.map(|c| c())}
</div>
<div
class=overlay_class
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
on:click=handle_click
>
{children.map(|c| c())}
</div>
</Show>
}
}

View File

@@ -15,7 +15,7 @@ pub fn DrawerTitle(
) -> impl IntoView {
view! {
<h2
class=move || format!("drawer-title {}", class.get().unwrap_or_default())
class=move || format!("text-lg font-semibold leading-none tracking-tight {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>
@@ -33,7 +33,7 @@ pub fn DrawerDescription(
) -> impl IntoView {
view! {
<p
class=move || format!("drawer-description {}", class.get().unwrap_or_default())
class=move || format!("text-sm text-muted-foreground {}", class.get().unwrap_or_default())
id=move || id.get().unwrap_or_default()
style=move || style.get().unwrap_or_default()
>