shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
69.19k stars 4.11k forks source link

The calendar looks completely wrong. #1574

Closed JuanMorell closed 6 months ago

JuanMorell commented 11 months ago

The calendar component is just not formatted or styled correctly. This project uses other shadcn components and they all work fine, but the calendar just looks and behaves incorrectly. The days aren't positions in rows and columns but clump to the left, and the hover and selected effects don't appear at all. I've been trying to solve this for a while and I just can't find the reason. I've added a picture of the appearance of the calendar and some code from the project: calendar

The Calendar Component

"use client"

import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/shadcn/components/ui/button"

export type CalendarProps = React.ComponentProps<typeof DayPicker>

function Calendar({
  className,
  classNames,
  showOutsideDays = true,
  ...props
}: CalendarProps) {
  return (
    <DayPicker
      showOutsideDays={showOutsideDays}
      className={cn("p-3", className)}
      classNames={{
        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
        month: "space-y-4",
        caption: "flex justify-center pt-1 relative items-center",
        caption_label: "text-sm font-medium",
        nav: "space-x-1 flex items-center",
        nav_button: cn(
          buttonVariants({ variant: "outline" }),
          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
        ),
        nav_button_previous: "absolute left-1",
        nav_button_next: "absolute right-1",
        table: "w-full border-collapse space-y-1",
        head_row: "flex",
        head_cell:
          "text-slate-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-slate-400",
        row: "flex w-full mt-2",
        cell: "text-center text-sm p-0 relative [&:has([aria-selected])]:bg-slate-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected])]:bg-slate-800",
        day: cn(
          buttonVariants({ variant: "ghost" }),
          "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
        ),
        day_selected:
          "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
        day_today: "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50",
        day_outside: "text-slate-500 opacity-50 dark:text-slate-400",
        day_disabled: "text-slate-500 opacity-50 dark:text-slate-400",
        day_range_middle:
          "aria-selected:bg-slate-100 aria-selected:text-slate-900 dark:aria-selected:bg-slate-800 dark:aria-selected:text-slate-50",
        day_hidden: "invisible",
        ...classNames,
      }}
      components={{
        IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
        IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
      }}
      {...props}
    />
  )
}
Calendar.displayName = "Calendar"

export { Calendar }

My DatePicker component

"use client"

import * as React from "react"
import { format } from "date-fns"
import { Calendar as CalendarIcon } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/shadcn/components/ui/button"
import { Popover, PopoverTrigger, PopoverContent } from "@/shadcn/components/ui/popover"
import { Calendar } from "@/shadcn/components/ui/calendar"

export function DatePicker() {
  const [date, setDate] = React.useState<Date>()

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          variant={"outline"}
          className={cn(
            "w-[280px] justify-start text-left font-normal",
            !date && "text-muted-foreground"
          )}
        >
          <CalendarIcon className="mr-2 h-4 w-4" />
          {date ? format(date, "PPP") : <span>Pick a date</span>}
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-auto p-0">
        <Calendar
          mode="single"
          selected={date}
          onSelect={setDate}
          initialFocus
        />
      </PopoverContent>
    </Popover>
  )
}
devrolle commented 11 months ago

It might be due to your DayPicker import in your Calendar component. You're importing DayPicker from "react-day-picker" but did you mean to select DatePicker from "@/shadcn/components/ui/calendar"?

spenserschwartz commented 11 months ago

@JuanMorell Did you ever get a solution to this? I'm seeing the same issue.

shadcn commented 6 months ago

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

nicu-chiciuc commented 6 months ago

Sorry for not yet providing a complete reproduction, but it seems there's actually some kind of issue with the rendering of the calendar.

We're thinking about migrating from MUI to tw/shadcn, so there are some hacky things done with the styling presently, which might cause this issue in our case.

Nevertheless, I think this issue is valid.

Screenshot 2024-02-29 at 10 37 35

bergiusj commented 6 months ago
image

I can confirm that this is still a valid issue. I've followed the installation guide and just used the provided example code and this is how it looks

Inspecting the elements within the date picker it seems like none of the provided classnames adds actual styling. Some CSS scoping issues maybe?

chandan-shrivastava commented 6 months ago

Any workaround for this issue?

kwabe007 commented 5 months ago

Could it be as simple as adding import 'react-day-picker/dist/style.css'; in the Calendar component generated by shadcn?

I also got a wrong looking calendar but then realized that the DayPicker CSS import was missing. After manually adding it, it worked. According to the React Day Picker docs:

When importing, include the DayPicker CSS in your component:

import { DayPicker } from 'react-day-picker';
import 'react-day-picker/dist/style.css';

function Component() {
  return <DayPicker />;
}
tonkaew131 commented 1 month ago

Reverse react-day-picker back to the old version ^8.9.1 seems to fix the issues

  1. Uninstall new version
bun remove react-day-picker
  1. Install old version
bun add react-day-picker@^8.9.1

image

Brainisthekey commented 1 month ago

@tonkaew131 indeed, thanks man, you literally saved me a day...

anshulLuhsna commented 1 month ago

@tonkaew131 Works! Thanks yo

joao-pedrozo commented 1 month ago

@tonkaew131 ty bro

gsi-chao commented 1 month ago

Reverse react-day-picker back to the old version ^8.9.1 seems to fix the issues

  1. Uninstall new version
bun remove react-day-picker
  1. Install old version
bun add react-day-picker@^8.9.1

image

That's work, but @shadcn needs to review to add compatibility with this new major version of react-day-picker

MahmoudElkayal commented 4 weeks ago

The component needs to be upgraded, https://daypicker.dev/upgrading

"use client"

import * as React from "react"
import { ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronDownIcon } from "@radix-ui/react-icons"
import { DayPicker } from "react-day-picker"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"

export type CalendarProps = React.ComponentProps<typeof DayPicker>

function Calendar({
    className,
    classNames,
    showOutsideDays = true,
    ...props
}: CalendarProps) {
    return (
        <DayPicker
            showOutsideDays={showOutsideDays}
            className={cn("p-3", className)}
            classNames={{
                months: "flex flex-col relative sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
                month: "space-y-4 ",
                month_caption: "flex justify-center items-center",
                caption_label: "text-sm font-medium",
                nav: "space-x-1 flex items-center",
                button_previous: cn(
                    buttonVariants({ variant: "outline" }),
                    "absolute left-9 top-3 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
                ),
                button_next: cn(
                    buttonVariants({ variant: "outline" }),
                    "absolute right-9 top-3 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
                ),
                month_grid: "w-full border-collapse space-y-1",
                weekdays: "flex",
                weekday:
                    "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
                week: "flex w-full mt-2",
                day: "h-9 w-9 text-center rounded-md text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
                day_button: cn(
                    buttonVariants({ variant: "ghost" }),
                    "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
                ),
                range_end: "day-range-end",
                selected:
                    "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
                today: "bg-accent text-accent-foreground",
                outside:
                    "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
                disabled: "text-muted-foreground opacity-50",
                range_middle:
                    "aria-selected:bg-accent aria-selected:text-accent-foreground",
                hidden: "invisible",
                ...classNames,
            }}
            components={{
                Chevron: ({ ...props }) => <Chevron {...props} />,
            }}
            {...props}
        />
    )
}
Calendar.displayName = "Calendar"

export { Calendar }

function Chevron({ orientation = "left", ...props }) {
    switch (orientation) {
        case "left":
            return <ChevronLeftIcon className="h-4 w-4" />;
        case "right":
            return <ChevronRightIcon className="h-4 w-4" />;
        case "up":
            return <ChevronUpIcon className="h-4 w-4" />;
        case "down":
            return <ChevronDownIcon className="h-4 w-4" />;
        default:
            return null; 
    }
}
YoonHan commented 2 weeks ago

Based on what @MahmoudElkayal shared right above, here is my working code of <Calendar> component.

'use client'

import {
  ChevronDownIcon,
  ChevronLeftIcon,
  ChevronRightIcon,
  ChevronUpIcon,
} from 'lucide-react'
import * as React from 'react'
import { DayFlag, DayPicker, SelectionState, UI } from 'react-day-picker'

import { cn } from '@/lib/utils'

import { buttonVariants } from '@/components/button/variant'

export type CalendarProps = React.ComponentProps<typeof DayPicker>

export const Calendar = ({
  className,
  classNames,
  showOutsideDays = true,
  ...props
}: CalendarProps) => {
  return (
    <DayPicker
      showOutsideDays={showOutsideDays}
      className={cn('p-3', className)}
      classNames={{
        [UI.Months]: 'relative',
        [UI.Month]: 'space-y-4 ml-0',
        [UI.MonthCaption]: 'flex justify-center items-center h-7',
        [UI.CaptionLabel]: 'text-sm font-medium',
        [UI.ButtonPrevious]: cn(
          buttonVariants({ variant: 'outline' }),
          'absolute left-1 top-0 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
        ),
        [UI.ButtonNext]: cn(
          buttonVariants({ variant: 'outline' }),
          'absolute right-1 top-0 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
        ),
        [UI.MonthGrid]: 'w-full border-collapse space-y-1',
        [UI.Weekdays]: 'flex',
        [UI.Weekday]:
          'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
        [UI.Week]: 'flex w-full mt-2',
        [UI.Day]:
          'h-9 w-9 text-center rounded-md text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
        [UI.DayButton]: cn(
          buttonVariants({ variant: 'ghost' }),
          'h-9 w-9 p-0 font-normal aria-selected:opacity-100 hover:bg-primary hover:text-primary-foreground'
        ),
        [SelectionState.range_end]: 'day-range-end',
        [SelectionState.selected]:
          'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
        [SelectionState.range_middle]:
          'aria-selected:bg-accent aria-selected:text-accent-foreground',
        [DayFlag.today]: 'bg-accent text-accent-foreground',
        [DayFlag.outside]:
          'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
        [DayFlag.disabled]: 'text-muted-foreground opacity-50',
        [DayFlag.hidden]: 'invisible',
        ...classNames,
      }}
      components={{
        Chevron: ({ ...props }) => <Chevron {...props} />,
      }}
      {...props}
    />
  )
}

const Chevron = ({ orientation = 'left' }) => {
  switch (orientation) {
    case 'left':
      return <ChevronLeftIcon className="h-4 w-4" />
    case 'right':
      return <ChevronRightIcon className="h-4 w-4" />
    case 'up':
      return <ChevronUpIcon className="h-4 w-4" />
    case 'down':
      return <ChevronDownIcon className="h-4 w-4" />
    default:
      return null
  }
}
apeTim commented 1 week ago

@YoonHan legend! thanks

MHasanKN commented 2 days ago

I was unable to fix the same issue so used a couple lines of css to make it a lot better:

tbody.rdp-tbody {
    display: flex;
    flex-direction: column;
    gap: 10px; /* Adjust gap between rows */
}

tbody.rdp-head {
    display: flex;
    flex-direction: column;
    gap: 10px; /* Adjust gap between rows */
}

.rdp-head tr {
    display: flex;
    justify-content: space-between;
    width: 100%;
}

.rdp-tbody tr {
    display: flex;
    justify-content: space-between;
    width: 100%;
}

.rdp-tbody td {
    flex: 1; /* Ensures equal width for all cells */
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 5px;
}
.rdp-tbody button {
    max-height: 10px;
}

.rdp-button {
    width: 100%; /* Ensures the button takes full width of the cell */
    height: 50px; /* Adjust as necessary */
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 5px;
    border-radius: 5px;
    transition: background-color 0.3s ease;
}

.rdp-button:hover {
    background-color: #f0f0f0; /* Hover effect */
}

.rdp-button:disabled {
    opacity: 0.5;
}