Skip to content

Commit 4071bf2

Browse files
authored
Merge pull request joincalldotco#263 from code-env/main
feat: added the grid and modified the experiment
2 parents ce725c0 + 3f42314 commit 4071bf2

File tree

5 files changed

+415
-244
lines changed

5 files changed

+415
-244
lines changed

apps/web/app/(app)/app/call/[id]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { useParams, useRouter, useSearchParams } from "next/navigation";
1616
import { useEffect, useState } from "react";
1717
import { toast } from "sonner";
1818

19-
function CallPageContent() {
19+
const CallPageContent = () => {
2020
const params = useParams();
2121
const router = useRouter();
2222
const searchParams = useSearchParams();
@@ -266,7 +266,7 @@ function CallPageContent() {
266266
)}
267267
</div>
268268
);
269-
}
269+
};
270270

271271
export default function CallPage() {
272272
return <CallPageContent />;

apps/web/app/exp/page.tsx

Lines changed: 147 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { memo, useState } from "react";
44
import { Button } from "@call/ui/components/button";
55
import { Monitor, UserPlus } from "lucide-react";
66
import {
@@ -17,6 +17,76 @@ import {
1717
} from "@/lib/constants";
1818
import NumberFlow from "@number-flow/react";
1919

20+
const getGridLayout = (count: number) => {
21+
if (count <= 1) return "grid-cols-4";
22+
if (count <= 4) return "grid-cols-4";
23+
if (count === 5 || count === 8) return "grid-cols-6";
24+
return "grid-cols-3";
25+
};
26+
27+
const getParticipantColSpan = (count: number, index: number) => {
28+
if (count <= 4) {
29+
if (count === 3) {
30+
if (index === 0 || index === 1) return "col-span-2";
31+
if (index === 2) return "col-span-2 col-start-2";
32+
}
33+
if (count === 4) {
34+
return "col-span-2";
35+
}
36+
if (count === 1) {
37+
return "col-span-2 col-start-2";
38+
}
39+
if (count === 2) {
40+
return "col-span-2";
41+
}
42+
return "col-span-2";
43+
}
44+
45+
if (count === 5) {
46+
if (index < 3) {
47+
return "col-span-2";
48+
}
49+
if (index === 3) {
50+
return "col-span-2 col-start-2";
51+
}
52+
if (index === 4) {
53+
return "col-span-2";
54+
}
55+
}
56+
57+
if (count === 8) {
58+
if (index < 6) {
59+
return "col-span-2";
60+
}
61+
if (index === 6) {
62+
return "col-span-2 col-start-2";
63+
}
64+
if (index === 7) {
65+
return "col-span-2";
66+
}
67+
}
68+
69+
if (count <= 9) {
70+
const remainder = count % 3;
71+
if (remainder > 0) {
72+
const lastRowStartIndex = count - remainder;
73+
if (index >= lastRowStartIndex) {
74+
const positionInLastRow = index - lastRowStartIndex;
75+
if (remainder === 1) {
76+
return "col-span-1 col-start-2";
77+
}
78+
if (remainder === 2) {
79+
if (positionInLastRow === 0) return "col-span-1 col-start-2";
80+
if (positionInLastRow === 1) return "col-span-1 col-start-3";
81+
}
82+
}
83+
}
84+
return "col-span-1";
85+
}
86+
87+
return "col-span-2";
88+
};
89+
2090
export default function GoogleMeetLayout() {
2191
const [participants, setParticipants] = useState([{ id: 1, name: "You" }]);
2292
const [isScreenSharing, setIsScreenSharing] = useState(false);
@@ -45,85 +115,9 @@ export default function GoogleMeetLayout() {
45115
setIsScreenSharing(!isScreenSharing);
46116
};
47117

48-
const getGridLayout = (count: number) => {
49-
if (count <= 1) return "grid-cols-4";
50-
if (count <= 4) return "grid-cols-4";
51-
if (count === 5 || count === 8) return "grid-cols-6";
52-
return "grid-cols-3";
53-
};
54-
55-
const getParticipantColSpan = (count: number, index: number) => {
56-
console.log(count, index);
57-
if (count <= 4) {
58-
if (count === 3) {
59-
if (index === 0 || index === 1) return "col-span-2";
60-
if (index === 2) return "col-span-2 col-start-2";
61-
}
62-
if (count === 4) {
63-
return "col-span-2";
64-
}
65-
if (count === 1) {
66-
return "col-span-2 col-start-2";
67-
}
68-
if (count === 2) {
69-
return "col-span-2";
70-
}
71-
return "col-span-2";
72-
}
73-
74-
// Special cases for 5 and 8 participants (6-column grid)
75-
if (count === 5) {
76-
// First 3 participants get col-span-2
77-
if (index < 3) {
78-
return "col-span-2";
79-
}
80-
// Last 2 participants are centered
81-
if (index === 3) {
82-
return "col-span-2 col-start-2";
83-
}
84-
if (index === 4) {
85-
return "col-span-2";
86-
}
87-
}
88-
89-
if (count === 8) {
90-
if (index < 6) {
91-
return "col-span-2";
92-
}
93-
if (index === 6) {
94-
return "col-span-2 col-start-2";
95-
}
96-
if (index === 7) {
97-
return "col-span-2";
98-
}
99-
}
100-
101-
if (count <= 9) {
102-
const remainder = count % 3;
103-
104-
if (remainder > 0) {
105-
const lastRowStartIndex = count - remainder;
106-
if (index >= lastRowStartIndex) {
107-
const positionInLastRow = index - lastRowStartIndex;
108-
109-
if (remainder === 1) {
110-
return "col-span-1 col-start-2";
111-
}
112-
if (remainder === 2) {
113-
if (positionInLastRow === 0) return "col-span-1 col-start-2";
114-
if (positionInLastRow === 1) return "col-span-1 col-start-3";
115-
}
116-
}
117-
}
118-
119-
return "col-span-1";
120-
}
121-
122-
return "col-span-2";
123-
};
124-
125118
return (
126119
<div className="bg-background flex min-h-screen flex-col">
120+
{/* Controls */}
127121
<div className="mb-6 flex h-16 justify-center gap-4 border-b">
128122
<div className="flex items-center gap-2">
129123
<Button
@@ -163,13 +157,13 @@ export default function GoogleMeetLayout() {
163157
<div className="flex flex-1 gap-4">
164158
<div
165159
className={cn(
166-
"container mx-auto flex w-full flex-1 items-center justify-center p-8"
160+
"container mx-auto flex w-full flex-1 flex-col items-center justify-center p-8"
167161
)}
168162
>
169-
<AnimatePresence mode="wait">
163+
<AnimatePresence mode="sync">
170164
{isScreenSharing && (
171165
<motion.div
172-
className="mb-6"
166+
className="mb-6 w-full"
173167
variants={screenShareVariants as Variants}
174168
initial="hidden"
175169
animate="visible"
@@ -232,10 +226,10 @@ export default function GoogleMeetLayout() {
232226
<LayoutGroup>
233227
<motion.div
234228
className={cn(
235-
"grid w-full justify-center gap-4",
236-
getGridLayout(participants.length)
237-
// getGridRows(participants.length)
238-
// "auto-rows-fr"
229+
"w-full justify-center gap-4",
230+
isScreenSharing
231+
? "flex flex-wrap items-center"
232+
: `grid ${getGridLayout(participants.length)}`
239233
)}
240234
variants={containerVariants}
241235
initial="hidden"
@@ -251,72 +245,56 @@ export default function GoogleMeetLayout() {
251245
}}
252246
>
253247
<AnimatePresence mode="popLayout">
254-
{visibleParticipants
255-
.map((participant, index) => (
256-
<motion.div
257-
key={participant.id}
258-
layoutId={`participant-${participant.id}`}
259-
variants={participantVariants as Variants}
260-
initial="hidden"
261-
animate="visible"
262-
exit="exit"
263-
layout
264-
className={cn(
265-
"bg-inset-accent border-inset-accent-foreground relative flex min-h-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-lg border-4",
266-
getParticipantColSpan(
267-
visibleParticipants.length,
268-
index
269-
),
270-
{
271-
"w-auto": visibleParticipants.length > 9,
272-
"aspect-video": visibleParticipants.length <= 9,
273-
}
274-
)}
275-
whileTap={{ scale: 0.98 }}
276-
onClick={() => removeParticipant(participant.id)}
277-
>
248+
{isScreenSharing
249+
? visibleParticipants.slice(0, 4).map((participant) => (
278250
<motion.div
279-
className="sls pointer-events-none absolute bottom-4 left-4 rounded bg-black/70 px-3 py-1 text-sm font-medium text-white"
280-
layoutId={`participant-name-${participant.id}`}
251+
key={participant.id}
252+
layoutId={`participant-${participant.id}`}
253+
variants={participantVariants as Variants}
254+
initial="hidden"
255+
animate="visible"
256+
exit="exit"
257+
layout
258+
className="relative flex h-[100px] w-[140px] cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 border-gray-500 bg-gray-700"
259+
whileTap={{ scale: 0.98 }}
260+
onClick={() => removeParticipant(participant.id)}
281261
>
282-
{participant.name}
262+
<span className="text-sm text-white">
263+
{participant.name}
264+
</span>
283265
</motion.div>
266+
))
267+
: visibleParticipants.map((participant, index) => (
268+
<Participant
269+
key={participant.id}
270+
participant={participant}
271+
index={index}
272+
visibleParticipants={visibleParticipants}
273+
removeParticipant={removeParticipant}
274+
/>
275+
))}
284276

285-
{participants.length > 1 && (
286-
<motion.div
287-
className="absolute inset-0 flex items-center justify-center bg-red-600/20 opacity-0 transition-opacity duration-200 hover:opacity-100"
288-
initial={{ opacity: 0 }}
289-
whileHover={{ opacity: 1 }}
290-
>
291-
<span className="font-medium text-white">
292-
Click to remove
293-
</span>
294-
</motion.div>
295-
)}
296-
</motion.div>
297-
))
298-
.slice(0, 8)}
299-
{(remainingParticipants.length || participants.length >= 9) && (
277+
{isScreenSharing && remainingParticipants.length > 0 && (
300278
<motion.div
301279
layoutId={`participant-${participants.length + 1}`}
302280
variants={participantVariants as Variants}
303281
initial="hidden"
304282
animate="visible"
305283
exit="exit"
306284
layout
307-
className="bg-inset-accent border-inset-accent-foreground relative flex min-h-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-lg border-4"
285+
className="flex h-[100px] w-[140px] items-center justify-center rounded-lg border-2 border-gray-500 bg-gray-700"
308286
>
309-
<span className="text-2xl font-bold text-white">
310-
<span className="text-sm"> + </span>
311-
<NumberFlow value={remainingParticipants.length + 1} />
312-
<span className="text-sm"> more</span>
287+
<span className="font-semibold text-white">
288+
+{remainingParticipants.length} more
313289
</span>
314290
</motion.div>
315291
)}
316292
</AnimatePresence>
317293
</motion.div>
318294
</LayoutGroup>
319295
</div>
296+
297+
{/* Sidebar */}
320298
<AnimatePresence>
321299
{isSidebarOpen && (
322300
<motion.div
@@ -374,3 +352,40 @@ export default function GoogleMeetLayout() {
374352
</div>
375353
);
376354
}
355+
356+
const Participant = memo(
357+
({
358+
participant,
359+
index,
360+
visibleParticipants,
361+
removeParticipant,
362+
}: {
363+
participant: { id: number; name: string };
364+
index: number;
365+
visibleParticipants: { id: number; name: string }[];
366+
removeParticipant: (id: number) => void;
367+
}) => {
368+
return (
369+
<motion.div
370+
layoutId={`participant-${participant.id}`}
371+
variants={participantVariants as Variants}
372+
initial="hidden"
373+
animate="visible"
374+
exit="exit"
375+
// layout
376+
className={cn(
377+
"bg-inset-accent border-inset-accent-foreground relative flex min-h-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-lg border-4",
378+
getParticipantColSpan(visibleParticipants.length, index),
379+
{
380+
"w-auto": visibleParticipants.length > 9,
381+
"aspect-video": visibleParticipants.length <= 9,
382+
}
383+
)}
384+
whileTap={{ scale: 0.98 }}
385+
onClick={() => removeParticipant(participant.id)}
386+
>
387+
<span className="text-white">{participant.name}</span>
388+
</motion.div>
389+
);
390+
}
391+
);

0 commit comments

Comments
 (0)