Skip to content

Commit 2bd3895

Browse files
committed
fix: issue with selecting role for inviting members on firefox
1 parent 05e1576 commit 2bd3895

File tree

3 files changed

+254
-204
lines changed

3 files changed

+254
-204
lines changed
Lines changed: 31 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
11
"use client";
22

3-
import * as React from "react";
4-
import { Check, ChevronsUpDown, Lock, X } from "lucide-react";
53
import type { Role } from "@prisma/client";
4+
import * as React from "react";
65

7-
import { cn } from "@comp/ui/cn";
86
import { useI18n } from "@/locales/client";
9-
import { Badge } from "@comp/ui/badge";
10-
import { Button } from "@comp/ui/button";
11-
import {
12-
Command,
13-
CommandEmpty,
14-
CommandGroup,
15-
CommandInput,
16-
CommandItem,
17-
CommandList,
18-
} from "@comp/ui/command";
19-
import { Popover, PopoverContent, PopoverTrigger } from "@comp/ui/popover";
20-
import {
21-
Tooltip,
22-
TooltipContent,
23-
TooltipProvider,
24-
TooltipTrigger,
25-
} from "@comp/ui/tooltip";
7+
import { Dialog, DialogContent } from "@comp/ui/dialog";
8+
import { MultiRoleComboboxContent } from "./MultiRoleComboboxContent";
9+
import { MultiRoleComboboxTrigger } from "./MultiRoleComboboxTrigger";
2610

2711
// Define the selectable roles explicitly (exclude owner)
2812
const selectableRoles: {
@@ -148,190 +132,33 @@ export function MultiRoleCombobox({
148132
return label.toLowerCase().includes(searchTerm.toLowerCase());
149133
});
150134

151-
return (
152-
<Popover open={open} onOpenChange={setOpen} modal={false}>
153-
<PopoverTrigger asChild>
154-
<Button
155-
variant="outline"
156-
role="combobox"
157-
aria-expanded={open}
158-
className="w-full justify-between min-h-[40px] h-auto shadow-none"
159-
disabled={disabled}
160-
>
161-
<div className="flex flex-wrap gap-1 items-center">
162-
{selectedRoles.length === 0 && (
163-
<span className="text-muted-foreground text-sm">
164-
{triggerText}
165-
</span>
166-
)}
167-
{selectedRoles.map((role) => (
168-
<Badge
169-
key={role}
170-
variant="secondary"
171-
className={cn(
172-
"text-xs",
173-
lockedRoles.includes(role) &&
174-
"border border-primary",
175-
)}
176-
onClick={(e) => {
177-
e.stopPropagation(); // Prevent popover
178-
handleSelect(role);
179-
}}
180-
>
181-
{getRoleLabel(role)}
182-
{!lockedRoles.includes(role) ? (
183-
<X className="ml-1 h-3 w-3 cursor-pointer" />
184-
) : (
185-
<TooltipProvider>
186-
<Tooltip>
187-
<TooltipTrigger asChild>
188-
<Lock className="ml-1 h-3 w-3 text-primary" />
189-
</TooltipTrigger>
190-
<TooltipContent>
191-
<p>
192-
{t(
193-
"people.member_actions.role_dialog.owner_note",
194-
)}
195-
</p>
196-
</TooltipContent>
197-
</Tooltip>
198-
</TooltipProvider>
199-
)}
200-
</Badge>
201-
))}
202-
</div>
203-
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
204-
</Button>
205-
</PopoverTrigger>
206-
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
207-
<Command>
208-
<CommandInput
209-
placeholder="Search..."
210-
value={searchTerm}
211-
onValueChange={setSearchTerm}
135+
return (
136+
<>
137+
<MultiRoleComboboxTrigger
138+
selectedRoles={selectedRoles}
139+
lockedRoles={lockedRoles}
140+
triggerText={triggerText}
141+
disabled={disabled}
142+
handleSelect={handleSelect} // For badge clicks
143+
getRoleLabel={getRoleLabel}
144+
t={t}
145+
onClick={() => setOpen(true)}
146+
ariaExpanded={open}
147+
/>
148+
<Dialog open={open} onOpenChange={setOpen}>
149+
<DialogContent className="p-0">
150+
<MultiRoleComboboxContent
151+
searchTerm={searchTerm}
152+
setSearchTerm={setSearchTerm}
153+
t={t}
154+
filteredRoles={filteredRoles}
155+
handleSelect={handleSelect} // For item selection
156+
lockedRoles={lockedRoles}
157+
selectedRoles={selectedRoles}
158+
onCloseDialog={() => setOpen(false)}
212159
/>
213-
<CommandList>
214-
<CommandEmpty>
215-
{t("people.empty.no_results.title")}
216-
</CommandEmpty>
217-
<CommandGroup>
218-
{filteredRoles.map((role) => (
219-
<CommandItem
220-
key={role.value}
221-
value={(() => {
222-
switch (role.value) {
223-
case "owner":
224-
return t("people.roles.owner");
225-
case "admin":
226-
return t("people.roles.admin");
227-
case "auditor":
228-
return t(
229-
"people.roles.auditor",
230-
);
231-
case "employee":
232-
return t(
233-
"people.roles.employee",
234-
);
235-
default:
236-
return role.value;
237-
}
238-
})()} // Use label for search
239-
onSelect={() => {
240-
handleSelect(role.value);
241-
}}
242-
disabled={
243-
role.value === "owner" || // Always disable the owner role
244-
(lockedRoles.includes(role.value) &&
245-
selectedRoles.includes(role.value)) // Disable any locked roles
246-
}
247-
className={cn(
248-
"flex flex-col items-start py-2 cursor-pointer", // Adjust padding and alignment
249-
lockedRoles.includes(role.value) &&
250-
selectedRoles.includes(
251-
role.value,
252-
) &&
253-
"bg-muted/50 text-muted-foreground",
254-
)}
255-
>
256-
<div className="flex w-full items-center">
257-
{" "}
258-
{/* Wrap label and check */}
259-
<Check
260-
className={cn(
261-
"mr-2 h-4 w-4 flex-shrink-0", // Ensure check doesn't shrink
262-
selectedRoles.includes(
263-
role.value,
264-
)
265-
? "opacity-100"
266-
: "opacity-0",
267-
)}
268-
/>
269-
<span className="flex-grow">
270-
{" "}
271-
{/* Allow label to take space */}
272-
{(() => {
273-
switch (role.value) {
274-
case "owner":
275-
return t(
276-
"people.roles.owner",
277-
);
278-
case "admin":
279-
return t(
280-
"people.roles.admin",
281-
);
282-
case "auditor":
283-
return t(
284-
"people.roles.auditor",
285-
);
286-
case "employee":
287-
return t(
288-
"people.roles.employee",
289-
);
290-
default:
291-
return role.value;
292-
}
293-
})()}
294-
</span>
295-
{lockedRoles.includes(role.value) &&
296-
selectedRoles.includes(
297-
role.value,
298-
) && (
299-
<span className="ml-auto text-xs text-muted-foreground pl-2 flex-shrink-0">
300-
(Locked)
301-
</span>
302-
)}
303-
</div>
304-
{/* Add description below */}
305-
<div className="ml-6 text-xs text-muted-foreground mt-1">
306-
{(() => {
307-
switch (role.value) {
308-
case "owner":
309-
return t(
310-
"people.roles.owner_description",
311-
);
312-
case "admin":
313-
return t(
314-
"people.roles.admin_description",
315-
);
316-
case "auditor":
317-
return t(
318-
"people.roles.auditor_description",
319-
);
320-
case "employee":
321-
return t(
322-
"people.roles.employee_description",
323-
);
324-
default:
325-
return "";
326-
}
327-
})()}
328-
</div>
329-
</CommandItem>
330-
))}
331-
</CommandGroup>
332-
</CommandList>
333-
</Command>
334-
</PopoverContent>
335-
</Popover>
160+
</DialogContent>
161+
</Dialog>
162+
</>
336163
);
337164
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import { Check } from "lucide-react";
5+
import type { Role } from "@prisma/client"; // Assuming Role is from prisma
6+
import {
7+
Command,
8+
CommandEmpty,
9+
CommandGroup,
10+
CommandInput,
11+
CommandItem,
12+
CommandList,
13+
} from "@comp/ui/command";
14+
15+
import { cn } from "@comp/ui/cn";
16+
17+
interface MultiRoleComboboxContentProps {
18+
searchTerm: string;
19+
setSearchTerm: (value: string) => void;
20+
t: (key: string, options?: any) => string;
21+
filteredRoles: Array<{ value: Role }>; // Role objects, labels derived via t()
22+
handleSelect: (roleValue: Role) => void;
23+
lockedRoles: Role[];
24+
selectedRoles: Role[];
25+
onCloseDialog: () => void;
26+
}
27+
28+
export function MultiRoleComboboxContent({
29+
searchTerm,
30+
setSearchTerm,
31+
t,
32+
filteredRoles,
33+
handleSelect,
34+
lockedRoles,
35+
selectedRoles,
36+
onCloseDialog,
37+
}: MultiRoleComboboxContentProps) {
38+
const getRoleDisplayLabel = (roleValue: Role) => {
39+
switch (roleValue) {
40+
case "owner":
41+
return t("people.roles.owner");
42+
case "admin":
43+
return t("people.roles.admin");
44+
case "auditor":
45+
return t("people.roles.auditor");
46+
case "employee":
47+
return t("people.roles.employee");
48+
default:
49+
return roleValue;
50+
}
51+
};
52+
53+
const getRoleDescription = (roleValue: Role) => {
54+
switch (roleValue) {
55+
case "owner":
56+
return t("people.roles.owner_description");
57+
case "admin":
58+
return t("people.roles.admin_description");
59+
case "auditor":
60+
return t("people.roles.auditor_description");
61+
case "employee":
62+
return t("people.roles.employee_description");
63+
default:
64+
return "";
65+
}
66+
};
67+
68+
return (
69+
<Command>
70+
<CommandInput
71+
placeholder="Search..."
72+
value={searchTerm}
73+
onValueChange={setSearchTerm}
74+
/>
75+
<CommandList>
76+
<CommandEmpty>
77+
{t("people.empty.no_results.title")}
78+
</CommandEmpty>
79+
<CommandGroup>
80+
{filteredRoles.map((role) => (
81+
<CommandItem
82+
key={role.value}
83+
value={getRoleDisplayLabel(role.value)} // Use translated label for search/value
84+
onPointerDown={(e) => e.preventDefault()}
85+
onClick={(e) => e.stopPropagation()}
86+
onSelect={() => {
87+
handleSelect(role.value);
88+
onCloseDialog();
89+
}}
90+
disabled={
91+
role.value === "owner" || // Always disable the owner role
92+
(lockedRoles.includes(role.value) &&
93+
selectedRoles.includes(role.value)) // Disable any locked roles
94+
}
95+
className={cn(
96+
"flex flex-col items-start py-2 cursor-pointer",
97+
lockedRoles.includes(role.value) &&
98+
selectedRoles.includes(role.value) &&
99+
"bg-muted/50 text-muted-foreground",
100+
)}
101+
>
102+
<div className="flex w-full items-center">
103+
<Check
104+
className={cn(
105+
"mr-2 h-4 w-4 flex-shrink-0",
106+
selectedRoles.includes(role.value)
107+
? "opacity-100"
108+
: "opacity-0",
109+
)}
110+
/>
111+
<span className="flex-grow">
112+
{getRoleDisplayLabel(role.value)}
113+
</span>
114+
{lockedRoles.includes(role.value) &&
115+
selectedRoles.includes(role.value) && (
116+
<span className="ml-auto text-xs text-muted-foreground pl-2 flex-shrink-0">
117+
(Locked)
118+
</span>
119+
)}
120+
</div>
121+
<div className="ml-6 text-xs text-muted-foreground mt-1">
122+
{getRoleDescription(role.value)}
123+
</div>
124+
</CommandItem>
125+
))}
126+
</CommandGroup>
127+
</CommandList>
128+
</Command>
129+
);
130+
}

0 commit comments

Comments
 (0)