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