Skip to content
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.

Commit

Permalink
feat: setup the base of channel feature
Browse files Browse the repository at this point in the history
  • Loading branch information
sor4chi committed Dec 29, 2023
1 parent e2df767 commit caccca9
Show file tree
Hide file tree
Showing 26 changed files with 470 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { withThemeByClassName } from "@storybook/addon-themes";
import type { Preview } from "@storybook/react";
import { MemoryRouter } from "react-router-dom";
import React from "react";

import "../src/styles/global.css";

const preview: Preview = {
Expand Down Expand Up @@ -46,6 +49,11 @@ const preview: Preview = {
},
defaultTheme: "light",
}),
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
],
};

Expand Down
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"valibot": "^0.20.1"
},
"devDependencies": {
Expand Down
15 changes: 12 additions & 3 deletions client/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 49 additions & 13 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
import { BrowserRouter } from "react-router-dom";
import { LoadingOverlay } from "./components/loading-overlay/loading-overlay";
import { AppRoutes } from "./routes";
import { Suspense } from "react";
import { LoadingOverlayProvider } from "./providers/loading-overlay.tsx";
import { AuthProvider } from "./providers/auth.tsx";
import { Suspense, useCallback, useContext, useEffect } from "react";
import {
LoadingOverlayContext,
LoadingOverlayProvider,
} from "./providers/loading-overlay.tsx";
import { AuthContext, AuthProvider } from "./providers/auth.tsx";
import { ChannelContext, ChannelProvider } from "./providers/channel.tsx";

function App() {
interface AppRootProps {
children: React.ReactNode;
}

const AppRoot = ({ children }: AppRootProps) => {
const { setIsLoading } = useContext(LoadingOverlayContext);
const { fetchUser } = useContext(AuthContext);
const { fetchChannels } = useContext(ChannelContext);

const setupApplication = useCallback(async () => {
setIsLoading(true);
try {
const user = await fetchUser();
if (user) {
await fetchChannels();
}
} finally {
setIsLoading(false);
}
}, [fetchUser, fetchChannels, setIsLoading]);

useEffect(() => {
void setupApplication();
}, []);

return <div>{children}</div>;
};

const App = () => {
return (
<AuthProvider>
<LoadingOverlayProvider>
<LoadingOverlay />
<BrowserRouter>
<Suspense>
<AppRoutes />
</Suspense>
</BrowserRouter>
</LoadingOverlayProvider>
<ChannelProvider>
<LoadingOverlayProvider>
<LoadingOverlay />
<AppRoot>
<BrowserRouter>
<Suspense>
<AppRoutes />
</Suspense>
</BrowserRouter>
</AppRoot>
</LoadingOverlayProvider>
</ChannelProvider>
</AuthProvider>
);
}
};

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Meta } from "@storybook/react";
import { ChannelLayout } from "..";
import { DESKTOP_STORY_CONFIG } from "@/__stories__/config";

const meta = {
title: "Features/Channel/Components/Layout",
} satisfies Meta;

export default meta;

export const Overview = () => (
<ChannelLayout
sidePanel={<span>Channel List</span>}
main={<span>Channel Chat</span>}
/>
);
Overview.story = DESKTOP_STORY_CONFIG;
1 change: 1 addition & 0 deletions client/src/features/channel/components/layout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./layout"
30 changes: 30 additions & 0 deletions client/src/features/channel/components/layout/layout.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { constants, vars } from "@/styles";
import { style } from "@vanilla-extract/css";

export const styles = {
channelLayoutWrapper: style({
display: "flex",
width: "100%",
height: "100dvh",
}),
channelLayoutSidePanel: style({
width: constants.sizes.channelLayoutSidePanelWidth,
flexShrink: 0,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: vars.spacing[4],
padding: vars.spacing[4],
boxSizing: "border-box",
}),
channelLayoutSidePanelLogo: style({
color: vars.color.gray[11],
fontSize: vars.font.size["xl"],
padding: vars.spacing[4],
}),
channelLayoutMain: style({
flexGrow: 1,
background: vars.color.gray[2],
borderLeft: `1px solid ${vars.color.gray[6]}`,
}),
};
21 changes: 21 additions & 0 deletions client/src/features/channel/components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MaximumIcon } from "@/components/icons/maximum";
import { styles } from "./layout.css";

interface ChannelLayoutProps {
sidePanel: React.ReactNode;
main: React.ReactNode;
}

export const ChannelLayout = ({ sidePanel, main }: ChannelLayoutProps) => {
return (
<div className={styles.channelLayoutWrapper}>
<div className={styles.channelLayoutSidePanel}>
<div className={styles.channelLayoutSidePanelLogo}>
<MaximumIcon />
</div>
{sidePanel}
</div>
<div className={styles.channelLayoutMain}>{main}</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Meta } from "@storybook/react";
import { ChannelTopPageTemplate } from "../template";

const meta = {
title: "Features/Channel/Top",
} satisfies Meta;

export default meta;

const mockUser = {
name: "test",
imageURL: "https://example.com",
};

const mockChannels = [
{
id: 1,
name: "general",
},
{
id: 2,
name: "random",
},
{
id: 3,
name: "random2",
},
];

export const Overview = () => (
<ChannelTopPageTemplate user={mockUser} channels={mockChannels} />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Meta } from "@storybook/react";
import { ChannelList } from "..";

const meta = {
title: "Features/Channel/Top/Components/ChannelList",
} satisfies Meta;

export default meta;

const mockChannels = [
{
id: 1,
name: "general",
},
{
id: 2,
name: "random",
},
{
id: 3,
name: "random2",
},
];

export const Overview = () => <ChannelList channels={mockChannels} />;

export const WithActiveChannel = () => (
<ChannelList
channels={[
mockChannels[0],
{ ...mockChannels[1], active: true },
mockChannels[2],
]}
/>
);

export const WithNotification = () => (
<ChannelList
channels={[
mockChannels[0],
{ ...mockChannels[1], hasNotification: true },
mockChannels[2],
]}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { style } from "@vanilla-extract/css";
import { vars } from "@/styles";

export const styles = {
channelList: style({
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
gap: vars.spacing[2],
padding: vars.spacing[4],
boxSizing: "border-box",
overflowY: "auto",
}),
channelListItem: style({
display: "flex",
gap: vars.spacing[2],
alignItems: "center",
cursor: "pointer",
color: vars.color.gray[11],
textDecoration: "none",
transition: vars.transition.normal("background"),
background: "transparent",
padding: `${vars.spacing[1]} ${vars.spacing[2]}`,
boxSizing: "border-box",
borderRadius: vars.spacing[2],

selectors: {
"&:hover": {
background: vars.color.gray[3],
},
},
}),
channelListItemActive: style({
color: vars.color.gray[12],
background: vars.color.gray[4],
fontWeight: 600,
}),
channelListItemNotification: style({
color: vars.color.gray[12],
}),
channelListItemIcon: style({
strokeWidth: 3,
width: vars.spacing[4],
flexShrink: 0,
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Link } from "react-router-dom";
import { styles } from "./channel-list.css";
import { Hash } from "react-feather";
import { clsx } from "@/libs/clsx";

interface Channel {
id: number;
name: string;
active?: boolean;
hasNotification?: boolean;
}

interface Props {
channels: Channel[];
}

export const ChannelList = ({ channels }: Props) => {
return (
<div className={styles.channelList}>
{channels.map((channel) => (
<Link
key={channel.id}
className={clsx(
styles.channelListItem,
channel.hasNotification && styles.channelListItemNotification,
channel.active && styles.channelListItemActive
)}
to={`/channel/${channel.id}`}
>
<Hash className={styles.channelListItemIcon} />
{channel.name}
</Link>
))}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./channel-list";
1 change: 1 addition & 0 deletions client/src/features/channel/pages/top/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChannelTopPage as default } from "./page";
Loading

0 comments on commit caccca9

Please sign in to comment.