11"use client" ;
22
3- import { useState } from "react" ;
3+ import { memo , useState } from "react" ;
44import { Button } from "@call/ui/components/button" ;
55import { Monitor , UserPlus } from "lucide-react" ;
66import {
@@ -17,6 +17,76 @@ import {
1717} from "@/lib/constants" ;
1818import 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+
2090export 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