-
+
+
+
+
+
+ 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 (
+
+
+
+ );
+}
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",