Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions web/apps/dashboard/components/ui/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function Combobox({
);

return (
<Popover open={open} onOpenChange={setOpen}>
<Popover open={open} onOpenChange={setOpen} modal={true}>
<div className={cn(comboboxWrapperVariants({ variant }), wrapperClassName)}>
{leftIcon && (
<div className="absolute left-3 flex items-center pointer-events-none">{leftIcon}</div>
Expand Down Expand Up @@ -149,21 +149,35 @@ export function Combobox({
</PopoverTrigger>
</div>
<PopoverContent
align="start"
className="p-0 w-[var(--radix-popover-trigger-width)] rounded-lg border border-grayA-4 bg-white dark:bg-black shadow-md z-50"
className="p-0 w-full min-w-[var(--radix-popover-trigger-width)] rounded-lg border border-grayA-4 bg-white dark:bg-black shadow-md z-[200] overflow-visible"
onOpenAutoFocus={(e) => {
// Prevent auto-focus to allow proper keyboard navigation
e.preventDefault();
}}
>
<Command>
<Command
onKeyDown={(e) => {
// Allow keyboard navigation within the combobox
if (
e.key === "ArrowDown" ||
e.key === "ArrowUp" ||
e.key === "Enter" ||
e.key === "Escape"
) {
e.stopPropagation();
}
}}
>
<CommandInput
onInput={onChange}
onKeyDown={(e) => {
if (e.key !== "Enter" && e.key !== " ") {
e.stopPropagation();
}
// Prevent propagation to Dialog but allow command list navigation
e.stopPropagation();
}}
placeholder={searchPlaceholder}
className="text-xs placeholder:text-xs placeholder:text-accent-8"
/>
<CommandList className="max-h-[300px] overflow-y-auto overflow-x-hidden">
<CommandList className="max-h-[300px] overflow-y-auto overflow-x-hidden scrollbar-thin">
<CommandEmpty>{emptyMessage}</CommandEmpty>
<CommandGroup className="max-h-[260px] overflow-y-auto">
{options.map((option) => (
Expand Down
3 changes: 2 additions & 1 deletion web/apps/dashboard/components/ui/form-combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ export const FormCombobox = React.forwardRef<HTMLDivElement, FormComboboxProps>(
},
ref,
) => {
const generatedId = React.useId();
const inputVariant = error ? "error" : variant;
const inputId = propId || React.useId();
const inputId = propId || generatedId;
const descriptionId = `${inputId}-helper`;
const errorId = `${inputId}-error`;

Expand Down
12 changes: 12 additions & 0 deletions web/internal/ui/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ const DialogContent = React.forwardRef<
className,
)}
onKeyDown={(e) => {
// Allow keyboard navigation for nested interactive elements
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter") {
// Let these events propagate to nested components like Combobox
return;
}
// Prevent Tab key from closing the dialog
if (e.key === "Tab") {
e.stopPropagation();
Expand Down Expand Up @@ -106,6 +111,13 @@ const DialogContent = React.forwardRef<
// Also prevent interact outside events when preventOutsideClose is true
if (preventOutsideClose) {
e.preventDefault();
return;
}

// Allow interactions with nested popovers/portals (e.g., Combobox dropdowns)
const target = e.target as HTMLElement;
if (target.closest('[role="listbox"]') || target.closest("[cmdk-root]")) {
e.preventDefault();
}
}}
{...props}
Expand Down
21 changes: 18 additions & 3 deletions web/internal/ui/src/components/dialog/navigable-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,18 @@ const NavigableDialogRoot = <TStepName extends string>({
<Dialog open={isOpen} onOpenChange={onOpenChange} modal={true}>
<DialogPortal>
<DialogContent
onKeyDown={(e) => e.stopPropagation()}
onKeyDown={(e) => {
// Allow keyboard events to propagate to nested components like Combobox
if (
e.key === "ArrowDown" ||
e.key === "ArrowUp" ||
e.key === "Enter" ||
e.key === "Escape"
) {
return;
}
e.stopPropagation();
}}
className={cn(
"drop-shadow-2xl transform-gpu border-grayA-4 overflow-hidden !rounded-2xl p-0 gap-0 flex flex-col max-h-[90vh]",
dialogClassName,
Expand Down Expand Up @@ -225,7 +236,7 @@ const NavigableDialogContent = <TStepName extends string>({
return (
<div className="flex-1 min-w-0 overflow-y-auto">
<DefaultDialogContentArea className={cn("min-h-[70vh] xl:min-h-[50vh] h-full", className)}>
<div className="h-full relative">
<div className="h-full relative overflow-visible">
{items.map((item) => {
const isActive = item.id === activeId;
return (
Expand Down Expand Up @@ -258,7 +269,11 @@ const NavigableDialogBody = ({
children: ReactNode;
className?: string;
}) => {
return <div className={cn("flex flex-grow overflow-hidden", className)}>{children}</div>;
return (
<div className={cn("flex flex-grow overflow-x-hidden overflow-y-hidden", className)}>
{children}
</div>
);
};

NavigableDialogBody.displayName = "NavigableDialogBody";
Expand Down
2 changes: 1 addition & 1 deletion web/internal/ui/src/components/dialog/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
"z-[150] w-72 overflow-hidden rounded-lg border border-grayA-4 bg-gray-2 p-4 text-gray-12 shadow-md outline-none",
"z-[200] w-72 rounded-lg border border-grayA-4 bg-gray-2 p-4 text-gray-12 shadow-md outline-none",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
Expand Down