Skip to content

Commit 6d2bda4

Browse files
committed
feat: enhance Combobox and Popover components with portalContainer support for improved rendering
1 parent 983031e commit 6d2bda4

3 files changed

Lines changed: 37 additions & 47 deletions

File tree

src/components/features/memory/entry-form.tsx

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useForm, useStore } from "@tanstack/react-form";
22
import { useMutation } from "@tanstack/react-query";
33
import { Loader2Icon, SparklesIcon, TagsIcon } from "lucide-react";
4-
import { useEffect, useState } from "react";
4+
import { useEffect, useRef, useState } from "react";
55
import { toast } from "sonner";
66
import { z } from "zod";
77
import { Badge } from "@/components/ui/badge";
@@ -15,13 +15,6 @@ import {
1515
FieldLabel,
1616
} from "@/components/ui/field";
1717
import { InputBadge } from "@/components/ui/input-badge";
18-
import {
19-
Select,
20-
SelectContent,
21-
SelectItem,
22-
SelectTrigger,
23-
SelectValue,
24-
} from "@/components/ui/select";
2518
import { Textarea } from "@/components/ui/textarea";
2619
import { useMemoryMutations, useTopUsedTags } from "@/hooks/use-memories";
2720
import { getCategorizationService } from "@/lib/ai/categorization-service";
@@ -63,6 +56,10 @@ export function EntryForm({
6356
onCancel,
6457
}: EntryFormProps) {
6558
const isPreviewMode = layout === "preview";
59+
const formRef = useRef<HTMLFormElement>(null);
60+
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(
61+
null,
62+
);
6663
const [selectedProvider, setSelectedProvider] = useState<
6764
AIProvider | undefined
6865
>();
@@ -254,6 +251,17 @@ export function EntryForm({
254251
};
255252
}, []);
256253

254+
useEffect(() => {
255+
const formElement = formRef.current;
256+
257+
if (isPreviewMode && formElement) {
258+
setPortalContainer(formElement);
259+
return;
260+
}
261+
262+
setPortalContainer(null);
263+
}, [isPreviewMode]);
264+
257265
useEffect(() => {
258266
const handleKeyDown = (e: KeyboardEvent) => {
259267
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
@@ -313,6 +321,7 @@ export function EntryForm({
313321

314322
return (
315323
<form
324+
ref={formRef}
316325
onSubmit={(e) => {
317326
e.preventDefault();
318327
form.handleSubmit();
@@ -446,43 +455,18 @@ export function EntryForm({
446455
<SparklesIcon className="size-3 animate-pulse" />
447456
)}
448457
</FieldLabel>
449-
{!isPreviewMode ? (
450-
<Combobox
451-
id={field.name}
452-
name={field.name}
453-
value={field.state.value}
454-
onValueChange={field.handleChange}
455-
options={categoryOptions}
456-
placeholder="Select a category"
457-
searchPlaceholder="Search categories..."
458-
emptyText="No category found."
459-
aria-invalid={isInvalid}
460-
/>
461-
) : (
462-
<Select
463-
value={field.state.value}
464-
onValueChange={field.handleChange}
465-
>
466-
<SelectTrigger
467-
id={field.name}
468-
onBlur={field.handleBlur}
469-
aria-invalid={isInvalid}
470-
aria-describedby={
471-
isInvalid ? `${field.name}-error` : undefined
472-
}
473-
className="w-full"
474-
>
475-
<SelectValue placeholder="Select a category" />
476-
</SelectTrigger>
477-
<SelectContent>
478-
{categoryOptions.map((option) => (
479-
<SelectItem key={option.value} value={option.value}>
480-
{option.label}
481-
</SelectItem>
482-
))}
483-
</SelectContent>
484-
</Select>
485-
)}
458+
<Combobox
459+
id={field.name}
460+
name={field.name}
461+
value={field.state.value}
462+
onValueChange={field.handleChange}
463+
options={categoryOptions}
464+
placeholder="Select a category"
465+
searchPlaceholder="Search categories..."
466+
emptyText="No category found."
467+
portalContainer={portalContainer}
468+
aria-invalid={isInvalid}
469+
/>
486470
{isInvalid && (
487471
<FieldError
488472
id={`${field.name}-error`}

src/components/ui/combobox.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface ComboboxProps {
3434
className?: string;
3535
id?: string;
3636
name?: string;
37+
portalContainer?: React.ComponentProps<typeof PopoverContent>["portalContainer"];
3738
"aria-invalid"?: boolean;
3839
}
3940

@@ -50,6 +51,7 @@ const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
5051
className,
5152
id,
5253
name,
54+
portalContainer,
5355
"aria-invalid": ariaInvalid,
5456
},
5557
ref,
@@ -87,6 +89,7 @@ const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
8789
<PopoverContent
8890
className="w-(--radix-popover-trigger-width) p-0"
8991
align="start"
92+
portalContainer={portalContainer}
9093
>
9194
<Command>
9295
<CommandInput placeholder={searchPlaceholder} />

src/components/ui/popover.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ function PopoverContent({
1919
className,
2020
align = "center",
2121
sideOffset = 4,
22+
portalContainer,
2223
...props
23-
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
24+
}: React.ComponentProps<typeof PopoverPrimitive.Content> & {
25+
portalContainer?: React.ComponentProps<typeof PopoverPrimitive.Portal>["container"];
26+
}) {
2427
return (
25-
<PopoverPrimitive.Portal>
28+
<PopoverPrimitive.Portal container={portalContainer}>
2629
<PopoverPrimitive.Content
2730
data-slot="popover-content"
2831
align={align}

0 commit comments

Comments
 (0)