From dccedbd6738b7b2fa4bc399608a30123ccca88e9 Mon Sep 17 00:00:00 2001 From: Nizzy Date: Sun, 16 Feb 2025 16:58:30 -0500 Subject: [PATCH] badges by color --- components/icons/animated/moon.tsx | 92 ++++++++++++++++++++++ components/icons/animated/sun.tsx | 101 +++++++++++++++++++++++++ components/mail/mail-list.tsx | 33 +++++--- components/ui/badge.tsx | 10 ++- components/ui/sidebar-theme-switch.tsx | 43 ++++------- 5 files changed, 240 insertions(+), 39 deletions(-) create mode 100644 components/icons/animated/moon.tsx create mode 100644 components/icons/animated/sun.tsx diff --git a/components/icons/animated/moon.tsx b/components/icons/animated/moon.tsx new file mode 100644 index 000000000..860cd2251 --- /dev/null +++ b/components/icons/animated/moon.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { forwardRef, useCallback, useImperativeHandle, useRef } from "react"; +import type { Transition, Variants } from "motion/react"; +import { motion, useAnimation } from "motion/react"; +import type { HTMLAttributes } from "react"; + +export interface MoonIconHandle { + startAnimation: () => void; + stopAnimation: () => void; +} + +const svgVariants: Variants = { + normal: { + rotate: 0, + }, + animate: { + rotate: [0, -10, 10, -5, 5, 0], + }, +}; + +const svgTransition: Transition = { + duration: 1.2, + ease: "easeInOut", +}; + +const MoonIcon = forwardRef>( + ({ onMouseEnter, onMouseLeave, ...props }, ref) => { + const controls = useAnimation(); + const isControlledRef = useRef(false); + + useImperativeHandle(ref, () => { + isControlledRef.current = true; + + return { + startAnimation: () => controls.start("animate"), + stopAnimation: () => controls.start("normal"), + }; + }); + + const handleMouseEnter = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start("animate"); + } else { + onMouseEnter?.(e); + } + }, + [controls, onMouseEnter], + ); + + const handleMouseLeave = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start("normal"); + } else { + onMouseLeave?.(e); + } + }, + [controls, onMouseLeave], + ); + return ( +
+ + + +
+ ); + }, +); + +MoonIcon.displayName = "MoonIcon"; + +export { MoonIcon }; diff --git a/components/icons/animated/sun.tsx b/components/icons/animated/sun.tsx new file mode 100644 index 000000000..0c827c8d1 --- /dev/null +++ b/components/icons/animated/sun.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { forwardRef, useCallback, useImperativeHandle, useRef } from "react"; +import { motion, useAnimation } from "motion/react"; +import type { Variants } from "motion/react"; +import type { HTMLAttributes } from "react"; + +export interface SunIconHandle { + startAnimation: () => void; + stopAnimation: () => void; +} + +const pathVariants: Variants = { + normal: { opacity: 1 }, + animate: (i: number) => ({ + opacity: [0, 1], + transition: { delay: i * 0.1, duration: 0.3 }, + }), +}; + +const SunIcon = forwardRef>( + ({ onMouseEnter, onMouseLeave, ...props }, ref) => { + const controls = useAnimation(); + const isControlledRef = useRef(false); + + useImperativeHandle(ref, () => { + isControlledRef.current = true; + + return { + startAnimation: () => controls.start("animate"), + stopAnimation: () => controls.start("normal"), + }; + }); + + const handleMouseEnter = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start("animate"); + } else { + onMouseEnter?.(e); + } + }, + [controls, onMouseEnter], + ); + + const handleMouseLeave = useCallback( + (e: React.MouseEvent) => { + if (!isControlledRef.current) { + controls.start("normal"); + } else { + onMouseLeave?.(e); + } + }, + [controls, onMouseLeave], + ); + return ( +
+ + + {[ + "M12 2v2", + "m19.07 4.93-1.41 1.41", + "M20 12h2", + "m17.66 17.66 1.41 1.41", + "M12 20v2", + "m6.34 17.66-1.41 1.41", + "M2 12h2", + "m4.93 4.93 1.41 1.41", + ].map((d, index) => ( + + ))} + +
+ ); + }, +); + +SunIcon.displayName = "SunIcon"; + +export { SunIcon }; diff --git a/components/mail/mail-list.tsx b/components/mail/mail-list.tsx index 25cd8abe5..a3f00ea71 100644 --- a/components/mail/mail-list.tsx +++ b/components/mail/mail-list.tsx @@ -153,7 +153,7 @@ const Thread = ({ message: initialMessage, selectMode, onSelect, isCompact }: Th {messagesCount !== 1 ? ( {messagesCount} ) : null} - {message.unread ? : null} + {message.unread ? : null}

!["unread", "inbox"].includes(label.toLowerCase()), + ); + + if (!visibleLabels.length) return null; + return (

- {labels.map((label) => ( - -

{label.replace(/_/g, " ")}

+ {visibleLabels.map((label) => ( + +

+ {label.replace(/^category_/i, "").replace(/_/g, " ")} +

))}
@@ -275,14 +283,19 @@ function MailLabels({ labels }: { labels: string[] }) { } function getDefaultBadgeStyle(label: string): ComponentProps["variant"] { - return "outline"; + const normalizedLabel = label.toLowerCase().replace(/^category_/i, ""); - // TODO: styling for each tag type - switch (true) { - case label.toLowerCase() === "work": + switch (normalizedLabel) { + case "important": + return "important"; + case "promotions": + return "promotions"; + case "personal": + return "personal"; + case "updates": + return "updates"; + case "work": return "default"; - case label.toLowerCase().startsWith("category_"): - return "outline"; default: return "secondary"; } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index 80c2ca55a..702fbf6b0 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { @@ -14,6 +14,14 @@ const badgeVariants = cva( destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", + important: + "border-0 bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/20 dark:text-amber-500 dark:hover:bg-amber-900/30", + promotions: + "border-0 bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/20 dark:text-red-500 dark:hover:bg-red-900/30", + personal: + "border-0 bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/20 dark:text-green-500 dark:hover:bg-green-900/30", + updates: + "border-0 bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/20 dark:text-purple-500 dark:hover:bg-purple-900/30", }, }, defaultVariants: { diff --git a/components/ui/sidebar-theme-switch.tsx b/components/ui/sidebar-theme-switch.tsx index b93b5594a..b589b60f6 100644 --- a/components/ui/sidebar-theme-switch.tsx +++ b/components/ui/sidebar-theme-switch.tsx @@ -1,27 +1,24 @@ "use client"; -import { LaptopMinimalIcon, MoonIcon, SunIcon } from "lucide-react"; -import * as SelectPrimitive from "@radix-ui/react-select"; +import { LaptopMinimalIcon } from "lucide-react"; + import { useTheme } from "next-themes"; -import { Select, SelectContent, SelectItem } from "@/components/ui/select"; +import { MoonIcon } from "../icons/animated/moon"; import { Button } from "@/components/ui/button"; +import { SunIcon } from "../icons/animated/sun"; export function SidebarThemeSwitch() { const { theme, systemTheme, resolvedTheme, setTheme } = useTheme(); - async function handleThemeChange(newTheme: string) { - let nextResolvedTheme = newTheme; - - if (newTheme === "system" && systemTheme) { - nextResolvedTheme = systemTheme; - } + async function handleThemeToggle() { + const newTheme = theme === "dark" ? "light" : "dark"; function update() { setTheme(newTheme); } - if (document.startViewTransition && nextResolvedTheme !== resolvedTheme) { + if (document.startViewTransition && newTheme !== resolvedTheme) { document.documentElement.style.viewTransitionName = "theme-transition"; await document.startViewTransition(update).finished; document.documentElement.style.viewTransitionName = ""; @@ -31,23 +28,13 @@ export function SidebarThemeSwitch() { } return ( - + ); }