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 { .header_divider {
@apply bg-def-3 h-[6px] border-def-2 border-t-[1px]; @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 { Dialog as KDialog } from "@kobalte/core/dialog";
import styles from "./Modal.module.css"; import styles from "./Modal.module.css";
import { Typography } from "../Typography/Typography"; import { Typography } from "../Typography/Typography";
@@ -19,53 +19,58 @@ export interface ModalProps {
class?: string; class?: string;
metaHeader?: Component; metaHeader?: Component;
disablePadding?: boolean; disablePadding?: boolean;
open: boolean;
} }
export const Modal = (props: ModalProps) => { export const Modal = (props: ModalProps) => {
const [open, setOpen] = createSignal(true);
return ( return (
<KDialog id={props.id} open={open()} modal={true}> <Show when={props.open}>
<KDialog.Portal mount={props.mount}> <KDialog id={props.id} open={props.open} modal={true}>
<KDialog.Content class={cx(styles.modal_content, props.class)}> <KDialog.Portal mount={props.mount}>
<div class={styles.modal_header}> <div class={styles.backdrop} />
<Typography <div class={styles.contentWrapper}>
class={styles.modal_title} <KDialog.Content class={cx(styles.modal_content, props.class)}>
hierarchy="label" <div class={styles.modal_header}>
family="mono" <Typography
size="xs" class={styles.modal_title}
> hierarchy="label"
{props.title} family="mono"
</Typography> size="xs"
<KDialog.CloseButton >
onClick={() => { {props.title}
setOpen(false); </Typography>
props.onClose(); <KDialog.CloseButton
}} onClick={() => {
> props.onClose();
<Icon icon="Close" size="0.75rem" /> }}
</KDialog.CloseButton> >
<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> </div>
<Show when={props.metaHeader}> </KDialog.Portal>
{(metaHeader) => ( </KDialog>
<> </Show>
<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>
); );
}; };

View File

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

View File

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

View File

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

View File

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