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
7 changes: 7 additions & 0 deletions src/frontend/apps/web/app/(main)/huddle/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import HuddleContainer from '@/src/features/video/ui/huddle-container';

//허들 테스트를 위한 임시페이지(삭제예정)
const HuddleTest = () => {
return <HuddleContainer />;
};
export default HuddleTest;
Empty file.
1 change: 1 addition & 0 deletions src/frontend/apps/web/src/features/video/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as HuddleContainer } from './ui/huddle-container';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum HuddleControl {
Mic = 'mic',
Video = 'video',
Screen = 'screen',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type HuddleControl } from './huddle-control.type';

export type HuddleControlsState = {
[HuddleControl.Mic]: boolean;
[HuddleControl.Video]: boolean;
[HuddleControl.Screen]: boolean;
};

export type HuddleControlAction = {
type: HuddleControl;
};

export const huddleControlReducer = (
state: HuddleControlsState,
action: HuddleControlAction,
) => ({ ...state, [action.type]: !state[action.type] });
6 changes: 6 additions & 0 deletions src/frontend/apps/web/src/features/video/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { HuddleControl } from './huddle-control.type';
export {
type HuddleControlsState,
type HuddleControlAction,
huddleControlReducer,
} from './huddle-controls-reducer';
20 changes: 20 additions & 0 deletions src/frontend/apps/web/src/features/video/ui/huddle-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Headphones } from 'lucide-react';
import HuddleContent from './huddle-section';
import ControlsContainer from './huddle-footer';

const HuddleContainer = () => {
return (
<div className="bg-primary w-full h-full flex flex-col gap-1 px-2 py-1">
<h3 className="flex flex-row text-white gap-2">
<Headphones /> slack-전체에서의 허들 1명
</h3>
<div className="min-h-0 h-[90%]">
<HuddleContent />
</div>
<div className="min-h-0 h-[10%]">
<ControlsContainer />
</div>
</div>
);
};
export default HuddleContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ToggleGroupItem } from '@workspace/ui/components';
import { HuddleControl, HuddleControlAction } from '../model';
import { Dispatch } from 'react';

type HuddleControlProps = {
value: HuddleControl;
children: React.ReactNode;
dispatch: Dispatch<HuddleControlAction>;
};

const HuddleControlItem = ({
value,
children,
dispatch,
}: HuddleControlProps) => {
return (
<ToggleGroupItem
value={value}
onClick={() => dispatch({ type: value })}
>
{children}
</ToggleGroupItem>
);
};
export default HuddleControlItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';
import { Button, ToggleGroup } from '@workspace/ui/components';
import {
Mic,
MicOff,
ScreenShare,
ScreenShareOff,
Video,
VideoOff,
} from 'lucide-react';
import { useReducer } from 'react';
import HuddleControlItem from './huddle-control-item';
import { HuddleControl, huddleControlReducer } from '../model';

export const controlIconList = {
[HuddleControl.Mic]: { on: <Mic />, off: <MicOff /> },
[HuddleControl.Video]: { on: <Video />, off: <VideoOff /> },
[HuddleControl.Screen]: { on: <ScreenShare />, off: <ScreenShareOff /> },
} as const;

const HuddleControlsGroup = () => {
const [controlGroupState, controlGroupDispatch] = useReducer(
huddleControlReducer,
{
[HuddleControl.Mic]: false,
[HuddleControl.Video]: false,
[HuddleControl.Screen]: false,
},
);

return (
<div className="flex justify-center items-center">
<ToggleGroup
variant="default"
type="multiple"
size="lg"
className="gap-3"
>
{Object.entries(controlIconList).map(([key, component]) => {
const controlKey = key as HuddleControl;
return (
<HuddleControlItem
key={key}
value={controlKey}
dispatch={controlGroupDispatch}
>
{component[controlGroupState[controlKey] ? 'on' : 'off']}
</HuddleControlItem>
);
})}

<Button
variant="destructive"
size="lg"
className="h-12 font-bold"
>
나가기
</Button>
</ToggleGroup>
</div>
);
};
export default HuddleControlsGroup;
19 changes: 19 additions & 0 deletions src/frontend/apps/web/src/features/video/ui/huddle-footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Toggle } from '@workspace/ui/components';
import HuddleControlsGroup from './huddle-controls-group';
import { MessageSquareText } from 'lucide-react';

const HuddleFooter = () => {
return (
<div className="relative w-full h-full min-w-0 min-h-0 flex flex-row justify-center items-center">
<HuddleControlsGroup />
<Toggle
variant="default"
size="lg"
className="absolute right-0"
>
<MessageSquareText />
</Toggle>
</div>
);
};
export default HuddleFooter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Avatar, AvatarFallback, AvatarImage } from '@workspace/ui/components';

const HuddleParticipantAvatar = () => {
return (
<Avatar
variant="square"
className="w-full h-full"
>
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback className="bg-gray-300">user profile</AvatarFallback>
</Avatar>
);
};
export default HuddleParticipantAvatar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import HuddleParticipantAvatar from './huddle-participant-avatar';

const HuddleParticipantCard = () => {
// 참여자 카메라 화면 또는 아바타가 보여질 예정
return (
<div className="h-[45%] aspect-square rounded-md overflow-hidden">
<HuddleParticipantAvatar />
</div>
);
};
export default HuddleParticipantCard;
13 changes: 13 additions & 0 deletions src/frontend/apps/web/src/features/video/ui/huddle-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import HuddleParticipant from './huddle-participant-card';

const HuddleSection = () => {
return (
<div className="w-full h-full min-h-0 overflow-hidden p-3 bg-muted rounded-lg flex flex-row flex-wrap justify-center items-center gap-5 overflow-y-auto">
<HuddleParticipant />
<HuddleParticipant />
{/* <HuddleParticipant />
<HuddleParticipant /> */}
</div>
);
};
export default HuddleSection;
30 changes: 16 additions & 14 deletions src/frontend/packages/ui/src/components/Toggle/toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@workspace/ui/lib/utils';

const toggleVariants = cva(
'inline-flex items-center justify-center rounded-full text-sm font-medium text-muted-foreground ring-offset-background transition-colors hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2',
'inline-flex items-center justify-center rounded-full text-sm font-medium text-muted ring-offset-background transition-colors hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-primary [&_svg]:pointer-events-none [&_svg]:w-full [&_svg]:h-full [&_svg]:shrink-0 gap-2',
{
variants: {
variant: {
default: 'bg-transparent',
outline: 'border border-input bg-transparent hover:text-accent-foreground',
default: 'bg-toggle',
outline: 'border border-input hover:text-accent-foreground',
},
size: {
default: 'w-10 h-10 px-3',
lg: 'w-12 h-12 px-5',
default: 'w-10 h-10 p-3',
lg: 'w-14 h-14 p-4',
},
},
defaultVariants: {
Expand All @@ -25,15 +25,17 @@ const toggleVariants = cva(
},
);

const Toggle = React.forwardRef<React.ElementRef<typeof TogglePrimitive.Root>, React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>>(
({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
),
);
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
));

Toggle.displayName = TogglePrimitive.Root.displayName;

Expand Down
7 changes: 6 additions & 1 deletion src/frontend/packages/ui/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { fontFamily } from 'tailwindcss/defaultTheme';

const config = {
darkMode: ['class'],
content: ['app/**/*.{ts,tsx}', 'src/**/*.{ts,tsx}', '../../packages/ui/src/components/**/*.{ts,tsx}'],
content: [
'app/**/*.{ts,tsx}',
'src/**/*.{ts,tsx}',
'../../packages/ui/src/components/**/*.{ts,tsx}',
],
theme: {
extend: {
fontFamily: {
Expand All @@ -16,6 +20,7 @@ const config = {
live: '#fff4e1',
thread: '#1264a3',
chatboxHover: '#F1F5F9',
toggle: '#E59A43',
black: {
100: 'rgba(0, 0, 0, 0.5)',
},
Expand Down