-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add labels in mail sidebar #358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # CLAUDE.md - Development Guidelines | ||
|
|
||
| ## Build & Test Commands | ||
|
|
||
| - Development: `pnpm dev` | ||
| - Build: `pnpm build` | ||
| - Lint: `pnpm lint` | ||
| - Run all tests: `pnpm test` | ||
| - Run AI tests: `pnpm test-ai` | ||
| - Run single test: `pnpm test __tests__/test-file.test.ts` | ||
| - Run specific AI test: `pnpm test-ai ai-categorize-senders` | ||
|
|
||
| ## Code Style | ||
|
|
||
| - Use TypeScript with strict null checks | ||
| - Path aliases: Use `@/` for imports from project root | ||
| - NextJS app router structure with (app) directory | ||
| - Follow tailwindcss patterns with prettier-plugin-tailwindcss | ||
| - Prefer functional components with hooks | ||
| - Use proper error handling with try/catch blocks | ||
| - Format code with Prettier | ||
| - Consult .cursor/rules for environment variable management | ||
|
|
||
| ## Component Guidelines | ||
|
|
||
| - Use shadcn/ui components when available | ||
| - Ensure responsive design with mobile-first approach | ||
| - Follow consistent naming conventions (PascalCase for components) | ||
| - Centralize types in dedicated type files when shared | ||
| - Use LoadingContent component for async data: | ||
| ```tsx | ||
| <LoadingContent loading={isLoading} error={error}> | ||
| {data && <YourComponent data={data} />} | ||
| </LoadingContent> | ||
| ``` | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| - Add to `.env.example`, `env.ts`, and `turbo.json` | ||
| - Client-side vars: Prefix with `NEXT_PUBLIC_` |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useMemo } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useMemo, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { usePathname } from "next/navigation"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -9,6 +9,8 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ArrowLeftIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BarChartBigIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BookIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ChevronDownIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ChevronRightIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CogIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CrownIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +47,8 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/components/ui/sidebar"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { SideNavMenu } from "@/components/SideNavMenu"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CommandShortcut } from "@/components/ui/command"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useLabels } from "@/hooks/useLabels"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LoadingContent } from "@/components/LoadingContent"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type NavItem = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -259,6 +263,38 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function MailNav({ path }: { path: string }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { onOpen } = useComposeModal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [showHiddenLabels, setShowHiddenLabels] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { userLabels, hiddenUserLabels, isLoading } = useLabels({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| includeHidden: showHiddenLabels, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transform user labels into NavItems | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const labelNavItems = useMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const searchParams = new URLSearchParams(path.split("?")[1] || ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentLabelId = searchParams.get("labelId"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return userLabels.map((label) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: label.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| icon: TagIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href: `?type=label&labelId=${encodeURIComponent(label.id)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add active state for the current label | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active: currentLabelId === label.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [userLabels, path]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transform hidden labels into NavItems | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hiddenLabelNavItems = useMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const searchParams = new URLSearchParams(path.split("?")[1] || ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentLabelId = searchParams.get("labelId"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return hiddenUserLabels.map((label) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: label.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| icon: TagIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href: `?type=label&labelId=${encodeURIComponent(label.id)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add active state for the current label | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active: currentLabelId === label.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [hiddenUserLabels, path]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -284,6 +320,41 @@ function MailNav({ path }: { path: string }) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SidebarGroupLabel>Categories</SidebarGroupLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SideNavMenu items={bottomMailLinks} activeHref={path} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </SidebarGroup> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SidebarGroup> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SidebarGroupLabel>Labels</SidebarGroupLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <LoadingContent loading={isLoading}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {userLabels.length > 0 ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SideNavMenu items={labelNavItems} activeHref={path} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="px-3 py-2 text-xs text-muted-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| No labels | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Hidden labels toggle */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {hiddenUserLabels.length > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => setShowHiddenLabels(!showHiddenLabels)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {showHiddenLabels ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ChevronDownIcon className="mr-1 size-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ChevronRightIcon className="mr-1 size-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span>More</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+336
to
+347
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add accessibility attributes to the toggle button. The toggle button for hidden labels should have proper accessibility attributes for better screen reader support. <button
type="button"
onClick={() => setShowHiddenLabels(!showHiddenLabels)}
+ aria-expanded={showHiddenLabels}
+ aria-controls="hidden-labels-menu"
className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {showHiddenLabels && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SideNavMenu items={hiddenLabelNavItems} activeHref={path} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </LoadingContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </SidebarGroup> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for the loading state.
The LoadingContent component should handle error states from the useLabels hook.
📝 Committable suggestion