Skip to content
Closed
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
6 changes: 6 additions & 0 deletions apps/web/core/hooks/use-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export function useSearch({
const { store } = useSyncEngine();
const cache = useQueryClient();
const [query, setQuery] = React.useState<string>(initialQuery ?? '');

React.useEffect(() => {
const next = initialQuery ?? '';
setQuery(prev => (prev === next ? prev : next));
}, [initialQuery]);

const debouncedQuery = useDebouncedValue(query);

const maybeEntityId = debouncedQuery.trim();
Expand Down
2 changes: 1 addition & 1 deletion apps/web/design-system/reorderable-relation-chips-dnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export default function ReorderableRelationChipsDnd({
onDragEnd={handleDragEnd}
>
<SortableContext items={sortedRelations.map(r => r.id)} strategy={horizontalListSortingStrategy}>
<div className="flex min-w-0 w-full max-w-full flex-wrap gap-1">
<div className="flex min-w-0 flex-wrap gap-1">
{sortedRelations.map(relation => (
<SortableRelationChip key={relation?.id} relation={relation} spaceId={spaceId} />
))}
Expand Down
43 changes: 41 additions & 2 deletions apps/web/design-system/select-entity-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ type SelectEntityAsPopoverProps = {
showIDs?: boolean;
initialQuery?: string;
selectedEntityId?: string;
clearQueryAfterPick?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
onSearchQueryChange?: (query: string) => void;
Comment on lines +33 to +36

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open is treated as a controlled prop when provided, but onOpenChange is optional. In the controlled case, omitting onOpenChange will make the popover effectively read-only (can’t open/close). Consider a discriminated union for props (controlled vs uncontrolled) so onOpenChange is required when open is set.

Copilot uses AI. Check for mistakes.
};

export function SelectEntityAsPopover({
Expand All @@ -43,8 +47,25 @@ export function SelectEntityAsPopover({
showIDs = true,
initialQuery,
selectedEntityId,
clearQueryAfterPick = true,
open: openProp,
onOpenChange: onOpenChangeProp,
onSearchQueryChange,
}: SelectEntityAsPopoverProps) {
const [open, setOpen] = useState<boolean>(false);
const [uncontrolledOpen, setUncontrolledOpen] = useState<boolean>(false);
const isControlled = openProp !== undefined;
const open = isControlled ? openProp : uncontrolledOpen;

const setOpen = React.useCallback(
(next: boolean) => {
if (isControlled) {
onOpenChangeProp?.(next);
} else {
setUncontrolledOpen(next);
}
},
[isControlled, onOpenChangeProp]
);

useKey('Escape', () => {
if (!open) return;
Expand All @@ -57,7 +78,23 @@ export function SelectEntityAsPopover({
<Popover.Trigger asChild>{trigger}</Popover.Trigger>

<Popover.Portal>
<Popover.Content sideOffset={4} align="start" className="z-1001" collisionPadding={10} avoidCollisions={true}>
<Popover.Content
sideOffset={4}
align="start"
className="z-1001"
collisionPadding={10}
avoidCollisions={true}
onPointerDownOutside={event => {
if ((event.target as Element | null)?.closest?.('[data-select-entity-dropdown]')) {
event.preventDefault();
}
}}
onInteractOutside={event => {
if ((event.target as Element | null)?.closest?.('[data-select-entity-dropdown]')) {
event.preventDefault();
}
}}
>
<SelectEntity
key={JSON.stringify(relationValueTypes)}
withSearchIcon={true}
Expand All @@ -71,6 +108,8 @@ export function SelectEntityAsPopover({
showIDs={showIDs}
initialQuery={initialQuery}
selectedEntityId={selectedEntityId}
clearQueryAfterPick={clearQueryAfterPick}
onSearchQueryChange={onSearchQueryChange}
/>
</Popover.Content>
</Popover.Portal>
Expand Down
32 changes: 26 additions & 6 deletions apps/web/design-system/select-entity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type SelectEntityProps = {
initialQuery?: string;
/** When set, the result with this ID gets a "Currently selected" indicator */
selectedEntityId?: string;
/** The results popover stays open for back-to-back picks.*/
clearQueryAfterPick?: boolean;
onSearchQueryChange?: (query: string) => void;
};

type SpaceFilter = { spaceId: string; spaceName: string | null };
Expand All @@ -99,6 +102,8 @@ export const SelectEntity = ({
showIDs = true,
initialQuery,
selectedEntityId,
clearQueryAfterPick = true,
onSearchQueryChange,
}: SelectEntityProps) => {
const [isShowingIds, setIsShowingIds] = useAtom(showingIdsAtom);
const { storage } = useMutate();
Expand Down Expand Up @@ -130,7 +135,7 @@ export const SelectEntity = ({
? allowedTypes.map(r => r.id)
: undefined;

const { query, onQueryChange, isLoading, isEmpty, results } = useSearch({
const { query, onQueryChange: setSearchQuery, isLoading, isEmpty, results } = useSearch({
filterByTypes,
filterBySpace,
initialQuery,
Expand All @@ -150,6 +155,14 @@ export const SelectEntity = ({
}
}, [autoFocus]);

const onQueryChange = React.useCallback(
(value: string) => {
setSearchQuery(value);
onSearchQueryChange?.(value);
},
[setSearchQuery, onSearchQueryChange]
);

if (query === '' && result !== null) {
startTransition(() => {
setResult(null);
Expand Down Expand Up @@ -191,8 +204,10 @@ export const SelectEntity = ({
// Create new entity with name and types using internal id
storage.entities.name.set(newEntityId, spaceId, query);
onDone?.({ id: newEntityId, name: query, space: spaceId }, true);
onQueryChange('');
setSelectedIndex(0);
if (clearQueryAfterPick) {
onQueryChange('');
setSelectedIndex(0);
}
setToast(<EntityCreatedToast entityId={newEntityId} spaceId={spaceId} />);
};

Expand All @@ -212,7 +227,9 @@ export const SelectEntity = ({
name: result.name,
primarySpace: result.spaces?.[0]?.spaceId ? result.spaces[0].spaceId : undefined,
});
onQueryChange('');
if (clearQueryAfterPick) {
onQueryChange('');
}
}
});

Expand Down Expand Up @@ -314,6 +331,7 @@ export const SelectEntity = ({
<Popover.Portal>
<Popover.Content
ref={popoverRef}
data-select-entity-dropdown
onOpenAutoFocus={event => {
event.preventDefault();
event.stopPropagation();
Expand Down Expand Up @@ -495,8 +513,10 @@ export const SelectEntity = ({
? result.spaces[0].spaceId
: undefined,
});
onQueryChange('');
setSelectedIndex(0);
if (clearQueryAfterPick) {
onQueryChange('');
setSelectedIndex(0);
}
}}
id={`select-entity-result-${index}`}
className={cx(
Expand Down
Loading
Loading