mirror of https://github.com/usememos/memos.git
style: enhance ActivityCalendar components with improved styling and layout adjustments
This commit is contained in:
parent
fcb9e377c1
commit
f7a81296fb
|
|
@ -26,7 +26,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => {
|
|||
const smallExtraClasses = size === "small" ? `${SMALL_CELL_SIZE.dimensions} min-h-0` : "";
|
||||
|
||||
const baseClasses = cn(
|
||||
"aspect-square w-full flex items-center justify-center text-center transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-2 select-none",
|
||||
"aspect-square w-full flex items-center justify-center text-center transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 select-none border border-border/10 bg-muted/20",
|
||||
sizeConfig.font,
|
||||
sizeConfig.borderRadius,
|
||||
smallExtraClasses,
|
||||
|
|
@ -35,7 +35,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => {
|
|||
const ariaLabel = day.isSelected ? `${tooltipText} (selected)` : tooltipText;
|
||||
|
||||
if (!day.isCurrentMonth) {
|
||||
return <div className={cn(baseClasses, "text-muted-foreground/30 bg-transparent cursor-default")}>{day.label}</div>;
|
||||
return <div className={cn(baseClasses, "text-muted-foreground/30 bg-transparent border-transparent cursor-default")}>{day.label}</div>;
|
||||
}
|
||||
|
||||
const intensityClass = getCellIntensityClass(day, maxCount);
|
||||
|
|
@ -45,7 +45,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => {
|
|||
intensityClass,
|
||||
day.isToday && "ring-2 ring-primary/30 ring-offset-1 font-semibold z-10",
|
||||
day.isSelected && "ring-2 ring-primary ring-offset-1 font-bold z-10",
|
||||
isInteractive ? "cursor-pointer hover:scale-110 hover:shadow-md hover:z-20" : "cursor-default",
|
||||
isInteractive ? "cursor-pointer hover:bg-muted/40 hover:border-border/30" : "cursor-default",
|
||||
);
|
||||
|
||||
const button = (
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { useInstance } from "@/contexts/InstanceContext";
|
|||
import { cn } from "@/lib/utils";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { CalendarCell } from "./CalendarCell";
|
||||
import { DEFAULT_CELL_SIZE, SMALL_CELL_SIZE } from "./constants";
|
||||
import { useTodayDate, useWeekdayLabels } from "./hooks";
|
||||
import type { MonthCalendarProps } from "./types";
|
||||
import { useCalendarMatrix } from "./useCalendar";
|
||||
|
|
@ -28,19 +27,19 @@ export const MonthCalendar = memo((props: MonthCalendarProps) => {
|
|||
selectedDate: "",
|
||||
});
|
||||
|
||||
const sizeConfig = size === "small" ? SMALL_CELL_SIZE : DEFAULT_CELL_SIZE;
|
||||
const gridGap = size === "small" ? "gap-x-3 gap-y-3" : "gap-x-3.5 gap-y-3.5";
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)}>
|
||||
<div className={cn("grid grid-cols-7", sizeConfig.gap, "text-muted-foreground mb-1", size === "small" ? "text-[10px]" : "text-xs")}>
|
||||
<div className={cn("grid grid-cols-7", gridGap, "text-muted-foreground mb-1", size === "small" ? "text-[10px]" : "text-xs")}>
|
||||
{rotatedWeekDays.map((label, index) => (
|
||||
<div key={index} className="flex h-4 items-center justify-center text-muted-foreground/60 font-medium">
|
||||
<div key={index} className="flex h-4 items-center justify-center text-muted-foreground/70 font-medium uppercase tracking-wide">
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={cn("grid grid-cols-7", sizeConfig.gap)}>
|
||||
<div className={cn("grid grid-cols-7 px-2", gridGap)}>
|
||||
{weeks.map((week, weekIndex) =>
|
||||
week.days.map((day, dayIndex) => {
|
||||
const tooltipText = getTooltipText(day.count, day.date, t);
|
||||
|
|
|
|||
|
|
@ -30,18 +30,20 @@ export const YearCalendar = ({ selectedYear, data, onYearChange, onDateClick, cl
|
|||
const handleToday = () => onYearChange(currentYear);
|
||||
|
||||
return (
|
||||
<div className={cn("w-full flex flex-col gap-6 p-2 md:p-0 select-none", className)}>
|
||||
<div className="flex items-center justify-between pb-4 px-2 pt-2">
|
||||
<h2 className="text-3xl font-bold text-foreground tracking-tight leading-none">{selectedYear}</h2>
|
||||
<div className={cn("w-full flex flex-col gap-6 px-4 sm:px-0 py-4 select-none", className)}>
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<div className="flex items-baseline gap-3">
|
||||
<h2 className="text-2xl md:text-3xl font-semibold text-foreground tracking-tight leading-none">{selectedYear}</h2>
|
||||
</div>
|
||||
|
||||
<div className="inline-flex items-center gap-1 shrink-0">
|
||||
<div className="inline-flex items-center gap-1 shrink-0 rounded-lg border border-border/20 bg-muted/20 p-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handlePrevYear}
|
||||
disabled={!canGoPrev}
|
||||
aria-label="Previous year"
|
||||
className="h-9 w-9 p-0 rounded-md hover:bg-secondary/80 text-muted-foreground hover:text-foreground"
|
||||
className="h-8 w-8 p-0 rounded-md hover:bg-muted/30 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
|
|
@ -53,8 +55,8 @@ export const YearCalendar = ({ selectedYear, data, onYearChange, onDateClick, cl
|
|||
disabled={isCurrentYear}
|
||||
aria-label={t("common.today")}
|
||||
className={cn(
|
||||
"h-9 px-4 rounded-md text-sm font-medium transition-colors",
|
||||
isCurrentYear ? "bg-secondary/50 text-muted-foreground cursor-default" : "hover:bg-secondary/80 text-foreground",
|
||||
"h-8 px-3 rounded-md text-[11px] font-semibold uppercase tracking-[0.18em] transition-colors",
|
||||
isCurrentYear ? "bg-muted/30 text-muted-foreground cursor-default" : "hover:bg-muted/30 text-foreground",
|
||||
)}
|
||||
>
|
||||
{t("common.today")}
|
||||
|
|
@ -66,7 +68,7 @@ export const YearCalendar = ({ selectedYear, data, onYearChange, onDateClick, cl
|
|||
onClick={handleNextYear}
|
||||
disabled={!canGoNext}
|
||||
aria-label="Next year"
|
||||
className="h-9 w-9 p-0 rounded-md hover:bg-secondary/80 text-muted-foreground hover:text-foreground"
|
||||
className="h-8 w-8 p-0 rounded-md hover:bg-muted/30 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ChevronRightIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
|
|
@ -75,13 +77,15 @@ export const YearCalendar = ({ selectedYear, data, onYearChange, onDateClick, cl
|
|||
|
||||
<TooltipProvider>
|
||||
<div className="w-full animate-fade-in">
|
||||
<div className="grid gap-6 grid-cols-1 sm:grid-cols-2 md:grid-cols-3">
|
||||
<div className="grid gap-6 md:gap-7 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
{months.map((month) => (
|
||||
<div
|
||||
key={month}
|
||||
className="flex flex-col gap-3 rounded-lg p-3 hover:bg-secondary/40 transition-colors cursor-default border border-transparent hover:border-border/50"
|
||||
className="flex flex-col gap-3 rounded-2xl border border-border/20 bg-muted/10 p-4 shadow-sm hover:shadow-md transition-shadow cursor-default"
|
||||
>
|
||||
<div className="text-xs font-bold text-foreground/80 uppercase tracking-widest pl-1">{getMonthLabel(month)}</div>
|
||||
<div className="text-[11px] font-semibold text-muted-foreground uppercase tracking-[0.22em] pl-1">
|
||||
{getMonthLabel(month)}
|
||||
</div>
|
||||
<MonthCalendar month={month} data={yearData} maxCount={yearMaxCount} size="small" onClick={onDateClick} />
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -14,22 +14,22 @@ export const INTENSITY_THRESHOLDS = {
|
|||
} as const;
|
||||
|
||||
export const CELL_STYLES = {
|
||||
HIGH: "bg-primary text-primary-foreground shadow-sm",
|
||||
MEDIUM: "bg-primary/80 text-primary-foreground shadow-sm",
|
||||
LOW: "bg-primary/60 text-primary-foreground shadow-sm",
|
||||
MINIMAL: "bg-primary/40 text-foreground",
|
||||
EMPTY: "bg-secondary/30 text-muted-foreground hover:bg-secondary/50",
|
||||
HIGH: "bg-primary text-primary-foreground shadow-sm border-transparent",
|
||||
MEDIUM: "bg-primary/85 text-primary-foreground shadow-sm border-transparent",
|
||||
LOW: "bg-primary/70 text-primary-foreground border-transparent",
|
||||
MINIMAL: "bg-primary/50 text-foreground border-transparent",
|
||||
EMPTY: "bg-muted/20 text-muted-foreground hover:bg-muted/30 border-border/10",
|
||||
} as const;
|
||||
|
||||
export const SMALL_CELL_SIZE = {
|
||||
font: "text-xs",
|
||||
dimensions: "w-8 h-8 mx-auto",
|
||||
borderRadius: "rounded-md",
|
||||
gap: "gap-1",
|
||||
font: "text-[11px]",
|
||||
dimensions: "w-full h-full",
|
||||
borderRadius: "rounded-lg",
|
||||
gap: "gap-1.5",
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_CELL_SIZE = {
|
||||
font: "text-xs",
|
||||
borderRadius: "rounded-md",
|
||||
gap: "gap-1.5",
|
||||
borderRadius: "rounded-lg",
|
||||
gap: "gap-2",
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { LatLng } from "leaflet";
|
||||
import { uniqBy } from "lodash-es";
|
||||
import { FileIcon, LinkIcon, LoaderIcon, MapPinIcon, Maximize2Icon, MoreHorizontalIcon, PlusIcon, type LucideIcon } from "lucide-react";
|
||||
import { FileIcon, LinkIcon, LoaderIcon, type LucideIcon, MapPinIcon, Maximize2Icon, MoreHorizontalIcon, PlusIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useDebounce } from "react-use";
|
||||
import { useReverseGeocoding } from "@/components/map";
|
||||
|
|
@ -106,9 +106,12 @@ const InsertMenu = (props: InsertMenuProps) => {
|
|||
setLocationDialogOpen(false);
|
||||
}, [location]);
|
||||
|
||||
const handlePositionChange = useCallback((position: LatLng) => {
|
||||
location.handlePositionChange(position);
|
||||
}, [location]);
|
||||
const handlePositionChange = useCallback(
|
||||
(position: LatLng) => {
|
||||
location.handlePositionChange(position);
|
||||
},
|
||||
[location],
|
||||
);
|
||||
|
||||
const handleToggleFocusMode = useCallback(() => {
|
||||
onToggleFocusMode?.();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import dayjs from "dayjs";
|
||||
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { YearCalendar } from "@/components/ActivityCalendar";
|
||||
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
|
|
@ -31,33 +31,36 @@ export const MonthNavigator = ({ visibleMonth, onMonthChange, activityStats }: M
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="w-full mb-2 flex flex-row justify-between items-center gap-1">
|
||||
<div className="w-full mb-2 flex flex-row justify-between items-center gap-2">
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<button className="px-2 py-1 -ml-2 rounded-md hover:bg-secondary/50 text-sm text-foreground font-semibold transition-colors flex items-center gap-1 select-none group">
|
||||
<button className="py-1 text-sm text-foreground font-medium transition-colors flex items-center select-none">
|
||||
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
||||
<ChevronDownIcon className="w-3.5 h-3.5 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="p-0 border-none bg-background md:max-w-4xl" size="2xl" showCloseButton={false}>
|
||||
<DialogContent
|
||||
className="p-0 border border-border/20 bg-background md:max-w-6xl w-[min(100vw-24px,1200px)] max-h-[85vh] overflow-auto rounded-2xl shadow-2xl"
|
||||
size="2xl"
|
||||
showCloseButton={false}
|
||||
>
|
||||
<DialogTitle className="sr-only">Select Month</DialogTitle>
|
||||
<YearCalendar selectedYear={currentYear} data={activityStats} onYearChange={handleYearChange} onDateClick={handleDateClick} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<div className="flex justify-end items-center shrink-0 gap-0.5">
|
||||
<div className="flex justify-end items-center shrink-0">
|
||||
<button
|
||||
className="p-1 rounded-md hover:bg-secondary/50 text-muted-foreground hover:text-foreground transition-all"
|
||||
className="h-8 w-8 rounded-lg hover:border-border/40 hover:bg-muted/30 text-muted-foreground hover:text-foreground transition-all"
|
||||
onClick={handlePrevMonth}
|
||||
aria-label="Previous month"
|
||||
>
|
||||
<ChevronLeftIcon className="w-4 h-4" />
|
||||
<ChevronLeftIcon className="w-4 h-4 mx-auto" />
|
||||
</button>
|
||||
<button
|
||||
className="p-1 rounded-md hover:bg-secondary/50 text-muted-foreground hover:text-foreground transition-all"
|
||||
className="h-8 w-8 rounded-lg hover:border-border/40 hover:bg-muted/30 text-muted-foreground hover:text-foreground transition-all"
|
||||
onClick={handleNextMonth}
|
||||
aria-label="Next month"
|
||||
>
|
||||
<ChevronRightIcon className="w-4 h-4" />
|
||||
<ChevronRightIcon className="w-4 h-4 mx-auto" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue