diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index 2419bd3..0512816 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -1,16 +1,57 @@ /** * @file app/(app)/layout.tsx - * @description Layout component providing a full-screen flex container with overflow clipping for application sub-routes. + * @description Main application layout component that manages responsive sidebars and drawer overlays. */ + +"use client"; + +import { useSidebarStore } from "@/lib/store/useSidebarStore"; +import { ServerSidebar } from "@/components/sidebar/ServerSidebar"; +import { ChannelSidebar } from "@/components/sidebar/ChannelSidebar"; +import { MemberSidebar } from "@/components/sidebar/MemberSidebar"; +import { UserProfile } from "@/components/sidebar/UserProfile"; +import { MobileDrawer } from "@/components/ui/MobileDrawer"; + /** - -* App group layout wrapper component. -* -* @param {Object} props - The component props. -* @param {React.ReactNode} props.children - The child elements to be rendered within the layout container. -* @returns {JSX.Element} The rendered layout container wrapping the child components. -*/ - + * Client component serving as the primary application layout, handling the server, channel, and member sidebars with mobile drawer support. + * + * @param {Object} props - The component props. + * @param {React.ReactNode} props.children - The child layout or page content to render in the central main view. + * @returns {JSX.Element} The application layout structure with responsive sidebars. + */ export default function AppLayout({ children }: { children: React.ReactNode }) { - return
{children}
; + const { isNavOpen, isMembersOpen, closeAll } = useSidebarStore(); + + return ( +
+ {/* Left Navigation: Server + Channels + User Profile */} + +
+
+ + +
+ +
+
+ + {/* Center: Main Area */} +
{children}
+ + {/* Right: List of Members */} + + + +
+ ); } diff --git a/app/(app)/page.tsx b/app/(app)/page.tsx index 29979dc..791343e 100644 --- a/app/(app)/page.tsx +++ b/app/(app)/page.tsx @@ -1,33 +1,28 @@ /** * @file app/(app)/page.tsx - * @description Welcome page component displayed as the initial landing screen of the Waveform application. + * @description Main dashboard page displaying the primary chat view with header, message area, and input control. */ -import Image from "next/image"; +import { ChatHeader } from "@/components/chat/ChatHeader"; +import { ChatInput } from "@/components/chat/ChatInput"; /** - * Renders the welcome page featuring the Waveform logo, main heading, and confirmation text. + * Renders the main application dashboard containing the general chat interface. * - * @returns {JSX.Element} The rendered welcome page component. + * @returns {Promise} The rendered application dashboard page. */ -export default function WelcomePage() { +export default async function ApplicationDashboardPage() { return ( -
-
- Waveform Logo +
+ + +
+
+ This is the beginning of the #general channel. +
-

- Waveform -

-

- The basic framework has been successfully set up! -

+ +
); } diff --git a/app/globals.css b/app/globals.css index 81a33a0..6799211 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,10 +1,14 @@ @import "tailwindcss"; @theme { - --color-background: hsl(200 6% 10%); - --color-foreground: hsl(210 5% 86%); + --color-background: hsl(220 13% 13%); + --color-surface: hsl(223 13% 10%); --color-accent: hsl(235 86% 65%); + --color-accent-hover: hsl(235 86% 58%); + + --color-foreground: hsl(210 11% 93%); + --color-muted: hsl(215 8% 55%); --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, diff --git a/components/chat/ChatHeader.tsx b/components/chat/ChatHeader.tsx new file mode 100644 index 0000000..4561a1f --- /dev/null +++ b/components/chat/ChatHeader.tsx @@ -0,0 +1,45 @@ +/** + * @file components/chat/ChatHeader.tsx + * @description Client component rendering the top header of the chat interface with sidebar toggles and current channel details. + */ + +"use client"; + +import { Menu, Hash, Users } from "lucide-react"; +import { useSidebarStore } from "@/lib/store/useSidebarStore"; + +/** + * Renders the top navigation header for the chat view, providing toggle triggers for mobile navigation and member list sidebars. + * + * @returns {JSX.Element} The rendered chat header component. + */ +export function ChatHeader() { + const { toggleNav, toggleMembers } = useSidebarStore(); + + return ( +
+
+ + +
+ + general +
+
+ + +
+ ); +} diff --git a/components/chat/ChatInput.tsx b/components/chat/ChatInput.tsx new file mode 100644 index 0000000..a782b4c --- /dev/null +++ b/components/chat/ChatInput.tsx @@ -0,0 +1,23 @@ +/** + * @file components/chat/ChatInput.tsx + * @description Client component providing an input field for composing and sending chat messages. + */ + +/** + * Renders the input container component for sending messages in a chat channel. + * + * @returns {JSX.Element} The rendered chat input UI element. + */ +export function ChatInput() { + return ( +
+
+ +
+
+ ); +} diff --git a/components/sidebar/ChannelSidebar.tsx b/components/sidebar/ChannelSidebar.tsx new file mode 100644 index 0000000..83c9e26 --- /dev/null +++ b/components/sidebar/ChannelSidebar.tsx @@ -0,0 +1,39 @@ +/** + * @file components/sidebar/ChannelSidebar.tsx + * @description Sidebar component for server navigation, rendering channel lists. + */ + +import { Hash } from "lucide-react"; + +/** + * Navigation sidebar displaying server details and text channels. + * + * @returns {JSX.Element} The rendered channel sidebar element. + */ +export function ChannelSidebar() { + return ( + + ); +} diff --git a/components/sidebar/MemberSidebar.tsx b/components/sidebar/MemberSidebar.tsx new file mode 100644 index 0000000..9107783 --- /dev/null +++ b/components/sidebar/MemberSidebar.tsx @@ -0,0 +1,25 @@ +/** + * @file components/sidebar/MemberSidebar.tsx + * @description Sidebar component displaying the list of active server/chat members and their online status. + */ + +/** + * Renders the member sidebar showing online users and their profile avatars. + * + * @returns {JSX.Element} The rendered member sidebar navigation container. + */ +export function MemberSidebar() { + return ( + + ); +} diff --git a/components/sidebar/ServerSidebar.tsx b/components/sidebar/ServerSidebar.tsx new file mode 100644 index 0000000..b73c93a --- /dev/null +++ b/components/sidebar/ServerSidebar.tsx @@ -0,0 +1,25 @@ +/** + * @file components/sidebar/ServerSidebar.tsx + * @description Sidebar navigation component for switching between servers and direct messages. + */ + +/** + * Navigation bar for switching between servers and direct messages. + * + * @returns {JSX.Element} The rendered server sidebar component. + */ +export function ServerSidebar() { + return ( + + ); +} diff --git a/components/sidebar/UserProfile.tsx b/components/sidebar/UserProfile.tsx new file mode 100644 index 0000000..0150ad2 --- /dev/null +++ b/components/sidebar/UserProfile.tsx @@ -0,0 +1,45 @@ +/** + * @file components/sidebar/UserProfile.tsx + * @description User profile component rendered at the bottom of the sidebar, displaying user avatar, status indicator, username, and settings action trigger. + */ + +import { Settings } from "lucide-react"; + +/** + * Renders the user profile card with user information, online status, and quick access settings. + * + * @returns {JSX.Element} The rendered user profile footer element. + */ +export function UserProfile() { + return ( +
+
+
+
+
+ U +
+ {/* Online Status Indicator */} + +
+ +
+

+ Username +

+

+ Online +

+
+
+ + +
+
+ ); +} diff --git a/components/ui/MobileDrawer.tsx b/components/ui/MobileDrawer.tsx new file mode 100644 index 0000000..d29adab --- /dev/null +++ b/components/ui/MobileDrawer.tsx @@ -0,0 +1,66 @@ +/** + * @file components/ui/MobileDrawer.tsx + * @description Slide-out drawer component for mobile views with configurable side placement, responsive breakpoint behavior, and backdrop overlay. + */ + +"use client"; + +import { clsx } from "clsx"; + +interface MobileDrawerProps { + isOpen: boolean; + onClose: () => void; + side: "left" | "right"; + breakpoint: "md" | "xl"; + children: React.ReactNode; +} + +/** + * Renders a responsive drawer container that slides in from either side on mobile viewports and transitions to a static layout at specified breakpoints. + * + * @param {MobileDrawerProps} props - The component props. + * @param {boolean} props.isOpen - Controls the visibility state of the drawer overlay on mobile viewports. + * @param {() => void} props.onClose - Callback function triggered when clicking the backdrop overlay to close the drawer. + * @param {"left" | "right"} props.side - The screen edge from which the drawer slides out. + * @param {"md" | "xl"} props.breakpoint - Tailwind responsive breakpoint at which the drawer becomes static and hides the mobile backdrop. + * @param {React.ReactNode} props.children - Content rendered within the drawer body. + * @returns {JSX.Element} The drawer element alongside its conditional backdrop overlay. + */ +export function MobileDrawer({ + isOpen, + onClose, + side, + breakpoint, + children, +}: MobileDrawerProps) { + const isLeft = side === "left"; + + const translateHidden = isLeft ? "-translate-x-full" : "translate-x-full"; + const breakpointStatic = + breakpoint === "md" + ? "md:static md:translate-x-0" + : "xl:static xl:translate-x-0"; + const breakpointHidden = breakpoint === "md" ? "md:hidden" : "xl:hidden"; + + return ( + <> +
+ {children} +
+ + {isOpen && ( +
+ )} + + ); +} diff --git a/lib/store/useSidebarStore.ts b/lib/store/useSidebarStore.ts new file mode 100644 index 0000000..4426ccb --- /dev/null +++ b/lib/store/useSidebarStore.ts @@ -0,0 +1,39 @@ +/** + * @file lib/store/useSidebarStore.ts + * @description Condition state management store for controlling the visibility and mutual exclusion of navigation and members sidebars. + */ + +import { create } from "zustand"; + +/** + * Interface representing the state and actions of the sidebar store. + * + * @interface SidebarState + * @property {boolean} isNavOpen - Indicates whether the navigation sidebar is open. + * @property {boolean} isMembersOpen - Indicates whether the members list sidebar is open. + * @property {() => void} toggleNav - Toggles the navigation sidebar while automatically closing the members sidebar. + * @property {() => void} toggleMembers - Toggles the members sidebar while automatically closing the navigation sidebar. + * @property {() => void} closeAll - Closes both the navigation and members sidebars simultaneously. + */ +interface SidebarState { + isNavOpen: boolean; + isMembersOpen: boolean; + toggleNav: () => void; + toggleMembers: () => void; + closeAll: () => void; +} + +/** + * Custom Condition hook for managing global sidebar visibility states. + * + * @returns {SidebarState} The current sidebar state and action handlers. + */ +export const useSidebarStore = create((set) => ({ + isNavOpen: false, + isMembersOpen: false, + toggleNav: () => + set((state) => ({ isNavOpen: !state.isNavOpen, isMembersOpen: false })), + toggleMembers: () => + set((state) => ({ isMembersOpen: !state.isMembersOpen, isNavOpen: false })), + closeAll: () => set({ isNavOpen: false, isMembersOpen: false }), +})); diff --git a/package-lock.json b/package-lock.json index 7ec3906..89fb7bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,12 @@ "name": "waveform", "version": "0.1.0", "dependencies": { + "clsx": "^2.1.1", + "lucide-react": "^1.35.0", "next": "16.3.3", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "zustand": "^5.0.15" }, "devDependencies": { "@tailwindcss/postcss": "^4.3.3", @@ -1722,7 +1725,7 @@ "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2853,6 +2856,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2906,7 +2918,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -5156,6 +5168,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.35.0.tgz", + "integrity": "sha512-yXCCWxGFYT6bLIPYC4SY6fPQPRs/d797rRIue+J9XP2Td6vQvD53gaQRBCnIVT1kTQRHtAtxlfOQNWAuIF8ELg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -6891,6 +6912,35 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zustand": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index fbef950..d5c94b5 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,12 @@ "lint": "eslint" }, "dependencies": { + "clsx": "^2.1.1", + "lucide-react": "^1.35.0", "next": "16.3.3", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "zustand": "^5.0.15" }, "devDependencies": { "@tailwindcss/postcss": "^4.3.3",