Merge pull request 'ui/modal: refactor mounting and controlled state' (#4807) from render-2 into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4807
This commit is contained in:
hsjobeki
2025-08-19 10:55:43 +00:00
6 changed files with 68 additions and 57 deletions

View File

@@ -35,3 +35,11 @@
.header_divider {
@apply bg-def-3 h-[6px] border-def-2 border-t-[1px];
}
.backdrop {
@apply absolute left-0 top-0 z-50 size-full bg-white/90;
}
.contentWrapper {
@apply absolute left-0 top-0 z-50 flex size-full items-center justify-center;
}

View File

@@ -1,4 +1,4 @@
import { Component, createSignal, JSX, Show } from "solid-js";
import { Component, JSX, Show } from "solid-js";
import { Dialog as KDialog } from "@kobalte/core/dialog";
import styles from "./Modal.module.css";
import { Typography } from "../Typography/Typography";
@@ -19,53 +19,58 @@ export interface ModalProps {
class?: string;
metaHeader?: Component;
disablePadding?: boolean;
open: boolean;
}
export const Modal = (props: ModalProps) => {
const [open, setOpen] = createSignal(true);
return (
<KDialog id={props.id} open={open()} modal={true}>
<KDialog.Portal mount={props.mount}>
<KDialog.Content class={cx(styles.modal_content, props.class)}>
<div class={styles.modal_header}>
<Typography
class={styles.modal_title}
hierarchy="label"
family="mono"
size="xs"
>
{props.title}
</Typography>
<KDialog.CloseButton
onClick={() => {
setOpen(false);
props.onClose();
}}
>
<Icon icon="Close" size="0.75rem" />
</KDialog.CloseButton>
<Show when={props.open}>
<KDialog id={props.id} open={props.open} modal={true}>
<KDialog.Portal mount={props.mount}>
<div class={styles.backdrop} />
<div class={styles.contentWrapper}>
<KDialog.Content class={cx(styles.modal_content, props.class)}>
<div class={styles.modal_header}>
<Typography
class={styles.modal_title}
hierarchy="label"
family="mono"
size="xs"
>
{props.title}
</Typography>
<KDialog.CloseButton
onClick={() => {
props.onClose();
}}
>
<Icon icon="Close" size="0.75rem" />
</KDialog.CloseButton>
</div>
<Show when={props.metaHeader}>
{(metaHeader) => (
<>
<div class="flex h-9 items-center px-6 py-2 bg-def-1">
<Dynamic component={metaHeader()} />
</div>
<div class={styles.header_divider} />
</>
)}
</Show>
<div
class={styles.modal_body}
data-no-padding={props.disablePadding}
>
{props.children({
close: () => {
props.onClose();
},
})}
</div>
</KDialog.Content>
</div>
<Show when={props.metaHeader}>
{(metaHeader) => (
<>
<div class="flex h-9 items-center px-6 py-2 bg-def-1">
<Dynamic component={metaHeader()} />
</div>
<div class={styles.header_divider} />
</>
)}
</Show>
<div class={styles.modal_body} data-no-padding={props.disablePadding}>
{props.children({
close: () => {
setOpen(false);
props.onClose();
},
})}
</div>
</KDialog.Content>
</KDialog.Portal>
</KDialog>
</KDialog.Portal>
</KDialog>
</Show>
);
};

View File

@@ -61,6 +61,7 @@ const MockCreateMachine = (props: MockProps) => {
return (
<div ref={(el) => (container = el)} class="create-backdrop">
<Modal
open={true}
mount={container!}
onClose={() => {
reset(form);

View File

@@ -1,8 +1,7 @@
import { Component, createEffect, on } from "solid-js";
import { RouteSectionProps, useNavigate } from "@solidjs/router";
import { activeClanURI, setActiveClanURI } from "@/src/stores/clan";
import { activeClanURI } from "@/src/stores/clan";
import { navigateToClan } from "@/src/hooks/clan";
import { Button } from "../components/Button/Button";
export const Layout: Component<RouteSectionProps> = (props) => {
const navigate = useNavigate();

View File

@@ -69,17 +69,14 @@ export const Machine = (props: RouteSectionProps) => {
>
Install me!
</Button>
{/* Unmount the whole component to destroy the store and form values */}
<Show when={showInstall()}>
<div
class="absolute left-0 top-0 z-50 flex size-full items-center justify-center bg-white/90"
ref={(el) => (container = el)}
>
<InstallModal
machineName={useMachineName()}
mount={container!}
onClose={() => setShowModal(false)}
/>
</div>
<InstallModal
open={showInstall()}
machineName={useMachineName()}
mount={container!}
onClose={() => setShowModal(false)}
/>
</Show>
{sidebarPane(useMachineName())}
</Show>

View File

@@ -32,6 +32,7 @@ export interface InstallModalProps {
initialStep?: InstallSteps[number]["id"];
mount?: Node;
onClose?: () => void;
open: boolean;
}
const steps = [
@@ -85,12 +86,12 @@ export const InstallModal = (props: InstallModalProps) => {
<StepperProvider stepper={stepper}>
<Modal
class="h-[30rem] w-screen max-w-3xl"
mount={props.mount}
title="Install machine"
onClose={() => {
console.log("Install modal closed");
props.onClose?.();
}}
open={props.open}
// @ts-expect-error some steps might not have
metaHeader={stepper.currentStep()?.title ? <MetaHeader /> : undefined}
// @ts-expect-error some steps might not have