Skip to content

Commit 2132085

Browse files
committed
feat(desktop): add new request modal picker
1 parent 4fd1679 commit 2132085

File tree

8 files changed

+443
-121
lines changed

8 files changed

+443
-121
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { setupDialogFocusTrap } from '@t-req/ui';
2+
import { createEffect, createMemo, For, onCleanup, Show } from 'solid-js';
3+
import { Portal } from 'solid-js/web';
4+
import {
5+
CREATE_WORKSPACE_ITEM_OPTIONS,
6+
type CreateWorkspaceItemKind,
7+
isCreateRequestKind
8+
} from '../create-request';
9+
10+
type CreateRequestDialogProps = {
11+
open: boolean;
12+
isBusy: boolean;
13+
name: string;
14+
kind: CreateWorkspaceItemKind;
15+
targetLabel: string;
16+
error: string | undefined;
17+
onClose: () => void;
18+
onNameChange: (value: string) => void;
19+
onKindChange: (kind: CreateWorkspaceItemKind) => void;
20+
onSubmit: () => void;
21+
};
22+
23+
export function CreateRequestDialog(props: CreateRequestDialogProps) {
24+
let dialogRef: HTMLDivElement | undefined;
25+
26+
const selectedDescription = createMemo(() => {
27+
const option =
28+
CREATE_WORKSPACE_ITEM_OPTIONS.find((item) => item.kind === props.kind) ??
29+
CREATE_WORKSPACE_ITEM_OPTIONS[0];
30+
if (!option) {
31+
return '';
32+
}
33+
return option.disabled ? `${option.description} (coming soon)` : option.description;
34+
});
35+
36+
const isCreateDisabled = createMemo(() => {
37+
if (!isCreateRequestKind(props.kind)) {
38+
return true;
39+
}
40+
if (props.isBusy) {
41+
return true;
42+
}
43+
return props.name.trim().length === 0;
44+
});
45+
46+
createEffect(() => {
47+
if (!props.open || !dialogRef) {
48+
return;
49+
}
50+
51+
const cleanupFocusTrap = setupDialogFocusTrap(dialogRef, {
52+
onRequestClose: props.onClose
53+
});
54+
55+
onCleanup(() => {
56+
cleanupFocusTrap();
57+
});
58+
});
59+
60+
const handleSubmit = (event: Event) => {
61+
event.preventDefault();
62+
props.onSubmit();
63+
};
64+
65+
return (
66+
<Show when={props.open}>
67+
<Portal>
68+
<div
69+
class="modal modal-open"
70+
role="dialog"
71+
aria-modal="true"
72+
aria-labelledby="new-item-title"
73+
>
74+
<div
75+
ref={dialogRef}
76+
class="modal-box border border-base-300 bg-base-100/95 shadow-2xl"
77+
tabIndex={-1}
78+
>
79+
<h3 id="new-item-title" class="text-base font-semibold text-base-content">
80+
New Request
81+
</h3>
82+
<p class="mt-1 text-xs text-base-content/65">Choose a type, then provide a filename.</p>
83+
84+
<form class="mt-4 space-y-4" onSubmit={handleSubmit}>
85+
<fieldset class="space-y-2">
86+
<legend class="font-mono text-[11px] text-base-content/70">Type</legend>
87+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
88+
<For each={CREATE_WORKSPACE_ITEM_OPTIONS}>
89+
{(option) => (
90+
<label
91+
class="label cursor-pointer items-start justify-start gap-2 rounded-box border border-base-300 bg-base-200/40 p-3"
92+
classList={{
93+
'border-primary bg-primary/10': props.kind === option.kind,
94+
'cursor-not-allowed opacity-60': option.disabled,
95+
'hover:border-primary/40': !option.disabled
96+
}}
97+
>
98+
<input
99+
type="radio"
100+
class="radio radio-sm mt-0.5"
101+
name="request-type"
102+
value={option.kind}
103+
checked={props.kind === option.kind}
104+
disabled={option.disabled || props.isBusy}
105+
onChange={(event) =>
106+
props.onKindChange(event.currentTarget.value as CreateWorkspaceItemKind)
107+
}
108+
/>
109+
<span class="label-text flex min-w-0 items-start justify-between gap-2 text-left">
110+
<span class="min-w-0">
111+
<span class="block text-sm font-medium text-base-content">
112+
{option.label}
113+
</span>
114+
<span class="block text-xs text-base-content/65">
115+
{option.description}
116+
</span>
117+
</span>
118+
<Show when={option.disabled}>
119+
<span class="badge badge-outline badge-xs shrink-0">Soon</span>
120+
</Show>
121+
</span>
122+
</label>
123+
)}
124+
</For>
125+
</div>
126+
</fieldset>
127+
128+
<label class="form-control gap-1">
129+
<span class="label-text font-mono text-[11px] text-base-content/70">Filename</span>
130+
<input
131+
type="text"
132+
class="input input-sm w-full border-base-300 bg-base-100/70 font-mono text-xs"
133+
value={props.name}
134+
onInput={(event) => props.onNameChange(event.currentTarget.value)}
135+
placeholder="new-request"
136+
aria-label="New request file name"
137+
disabled={props.isBusy}
138+
/>
139+
</label>
140+
141+
<div class="rounded-box border border-base-300 bg-base-200/40 px-3 py-2">
142+
<p class="font-mono text-[11px] text-base-content/70">
143+
Create in: {props.targetLabel}
144+
</p>
145+
<p class="mt-1 text-xs text-base-content/65">{selectedDescription()}</p>
146+
</div>
147+
148+
<Show when={props.error}>
149+
{(message) => <p class="text-xs text-error">{message()}</p>}
150+
</Show>
151+
152+
<div class="modal-action mt-0">
153+
<button
154+
type="button"
155+
class="btn btn-ghost btn-sm font-mono text-[11px] normal-case"
156+
onClick={props.onClose}
157+
disabled={props.isBusy}
158+
>
159+
Cancel
160+
</button>
161+
<button
162+
type="submit"
163+
class="btn btn-primary btn-sm font-mono text-[11px] normal-case"
164+
disabled={isCreateDisabled()}
165+
>
166+
Create
167+
</button>
168+
</div>
169+
</form>
170+
</div>
171+
<button
172+
type="button"
173+
class="modal-backdrop"
174+
onClick={props.onClose}
175+
aria-label="Close new request dialog"
176+
>
177+
close
178+
</button>
179+
</div>
180+
</Portal>
181+
</Show>
182+
);
183+
}

0 commit comments

Comments
 (0)