feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s

- Implemented Button component with various props for customization.
- Created Modal component with header, content, and footer subcomponents.
- Added Spinner component for loading indicators.
- Developed Toast component for displaying notifications.
- Introduced Tooltip component for contextual hints with keyboard shortcuts.
- Added corresponding CSS modules for styling each component.
- Updated index file to export new components.
- Configured TypeScript settings for the UI package.
This commit is contained in:
Bryan1029384756
2026-04-14 09:02:14 -05:00
parent 9ef839938e
commit b7a4cf4ce8
376 changed files with 52619 additions and 167641 deletions

View File

@@ -0,0 +1,85 @@
import { useQuery } from 'convex/react';
import type { ReactNode } from 'react';
import { useLocation } from 'react-router-dom';
import { api } from '../../../../../convex/_generated/api';
import { FileUploadDropZone } from '../channel/FileUploadDropZone';
import { DMLayout } from '../dm/DMLayout';
import { GuildList } from './GuildList';
import { GuildNavbar } from './GuildNavbar';
import { MobileBottomNav } from './MobileBottomNav';
import { UserArea } from './UserArea';
import styles from './GuildsLayout.module.css';
interface GuildsLayoutProps {
children: ReactNode;
}
/**
* Parse the current pathname to decide which mobile view to show. At
* ≤768px exactly one of the three columns is visible at a time.
* /channels/@me → nav (DM list)
* /channels/@me/:channelId → chat
* /channels/:serverId → nav (channel list)
* /channels/:serverId/:channelId → chat
* /channels/you → you (mobile profile tab)
*/
function detectMobileMode(pathname: string): 'nav' | 'chat' | 'you' {
const segments = pathname.split('/').filter(Boolean);
if (segments[0] !== 'channels') return 'nav';
if (segments[1] === 'you') return 'you';
return segments.length >= 3 && !!segments[2] ? 'chat' : 'nav';
}
export function GuildsLayout({ children }: GuildsLayoutProps) {
const location = useLocation();
const segments = location.pathname.split('/').filter(Boolean);
// /channels/:serverId/... — serverId is segments[1], '@me' for DMs
const serverId = segments[0] === 'channels' ? segments[1] : undefined;
const channelId = segments[0] === 'channels' ? segments[2] : undefined;
const hasServer = !!serverId && serverId !== '@me' && serverId !== 'you';
const mobileMode = detectMobileMode(location.pathname);
// Look up the current channel's name + type so the drop overlay
// reads "Upload to #channel-name". Drops are disabled when there's
// no channel in the URL (DM home, `/you` profile) and on voice
// channels — they have no composer to attach to.
const channel = useQuery(
api.channels.get,
channelId ? { id: channelId as any } : 'skip',
);
const channelName = (channel && (channel as any).name) || 'channel';
const channelType = (channel && (channel as any).type) || undefined;
const dropDisabled = !channelId || channelType === 'voice' || channelType === 'category';
return (
<FileUploadDropZone
channelName={channelName}
disabled={dropDisabled}
onDropFiles={(files) => {
if (!channelId) return;
window.dispatchEvent(
new CustomEvent('brycord:drop-files', {
detail: { channelId, files },
}),
);
}}
>
<div className={styles.wrapper} data-mobile-mode={mobileMode}>
<div className={styles.container}>
<div className={styles.guildList}>
<GuildList />
</div>
<div className={styles.sidebar}>
{hasServer ? <GuildNavbar /> : <DMLayout />}
</div>
<div className={styles.sidebarDivider} />
<div className={styles.content}>{children}</div>
<div className={styles.userAreaWrapper}>
<UserArea />
</div>
</div>
<MobileBottomNav />
</div>
</FileUploadDropZone>
);
}