Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/ui/public/r/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"files": [
{
"path": "registry/default/ui/calendar.tsx",
"content": "\"use client\";\n\nimport { type DateLib, DayPicker } from \"@daypicker/react\";\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n ChevronsUpDownIcon,\n} from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nconst buttonClassNames =\n \"relative flex size-(--cell-size) text-base sm:text-sm items-center justify-center rounded-lg text-foreground not-in-data-selected:hover:bg-accent disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\";\n\nexport function Calendar({\n className,\n classNames,\n showOutsideDays = true,\n components: userComponents,\n mode = \"single\",\n ...props\n}: React.ComponentProps<typeof DayPicker>): React.ReactElement {\n const defaultClassNames = {\n button_next: buttonClassNames,\n button_previous: buttonClassNames,\n caption_label:\n \"text-base sm:text-sm font-medium flex items-center gap-2 h-full\",\n day: \"size-(--cell-size) text-sm py-px\",\n day_button: cn(\n buttonClassNames,\n \"in-data-disabled:pointer-events-none in-[.range-middle]:rounded-none in-[.range-end:not(.range-start)]:rounded-s-none in-[.range-start:not(.range-end)]:rounded-e-none in-[.range-middle]:in-data-selected:bg-accent in-data-selected:bg-primary in-[.range-middle]:in-data-selected:text-foreground in-data-disabled:text-muted-foreground/72 in-data-outside:text-muted-foreground/72 in-data-selected:in-data-outside:text-primary-foreground in-data-selected:text-primary-foreground in-data-disabled:line-through outline-none in-[[data-selected]:not(.range-middle)]:transition-[border-radius,box-shadow] focus-visible:z-1 focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n ),\n dropdown: \"absolute bg-popover inset-0 opacity-0\",\n dropdown_root:\n \"relative has-focus:border-ring has-focus:ring-ring/50 has-focus:ring-[3px] border border-input shadow-xs/5 rounded-lg px-[calc(--spacing(3)-1px)] h-9 sm:h-8 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-me-1\",\n dropdowns:\n \"w-full flex items-center text-base sm:text-sm justify-center h-(--cell-size) gap-1.5 *:[span]:font-medium\",\n hidden: \"invisible\",\n month: \"w-full\",\n month_caption:\n \"relative mx-(--cell-size) px-1 mb-1 flex h-(--cell-size) items-center justify-center z-2\",\n months: \"relative flex flex-col sm:flex-row gap-2\",\n nav: \"absolute top-0 flex w-full justify-between z-1\",\n outside:\n \"text-muted-foreground data-selected:bg-accent/50 data-selected:text-muted-foreground\",\n range_end: \"range-end\",\n range_middle: \"range-middle\",\n range_start: \"range-start\",\n today:\n \"*:after:pointer-events-none *:after:absolute *:after:bottom-1 *:after:start-1/2 *:after:z-1 *:after:size-[3px] *:after:-translate-x-1/2 *:after:rounded-full *:after:bg-primary [&[data-selected]:not(.range-middle)>*]:after:bg-background [&[data-disabled]>*]:after:bg-foreground/30\",\n week_number:\n \"size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72\",\n weekday:\n \"size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72\",\n };\n const mergedClassNames: typeof defaultClassNames = Object.keys(\n defaultClassNames,\n ).reduce(\n (acc, key) => {\n const userClass = classNames?.[key as keyof typeof classNames];\n const baseClass =\n defaultClassNames[key as keyof typeof defaultClassNames];\n\n acc[key as keyof typeof defaultClassNames] = userClass\n ? cn(baseClass, userClass)\n : baseClass;\n\n return acc;\n },\n { ...defaultClassNames } as typeof defaultClassNames,\n );\n\n const defaultComponents = {\n Chevron: ({\n className,\n orientation,\n ...props\n }: {\n className?: string;\n orientation?: \"left\" | \"right\" | \"up\" | \"down\";\n }): React.ReactElement => {\n if (orientation === \"left\") {\n return (\n <ChevronLeftIcon\n className={cn(className, \"rtl:rotate-180\")}\n {...props}\n aria-hidden=\"true\"\n />\n );\n }\n\n if (orientation === \"right\") {\n return (\n <ChevronRightIcon\n className={cn(className, \"rtl:rotate-180\")}\n {...props}\n aria-hidden=\"true\"\n />\n );\n }\n\n return (\n <ChevronsUpDownIcon\n className={className}\n {...props}\n aria-hidden=\"true\"\n />\n );\n },\n };\n\n const mergedComponents = {\n ...defaultComponents,\n ...userComponents,\n };\n\n const dayPickerProps = {\n className: cn(\n \"w-fit [--cell-size:--spacing(10)] sm:[--cell-size:--spacing(9)]\",\n className,\n ),\n classNames: mergedClassNames,\n components: mergedComponents,\n \"data-slot\": \"calendar\",\n formatters: {\n formatMonthDropdown: (month: Date, dateLib: DateLib) =>\n dateLib.format(month, \"LLL\"),\n } as React.ComponentProps<typeof DayPicker>[\"formatters\"],\n mode,\n showOutsideDays,\n ...props,\n };\n\n return (\n <DayPicker\n {...(dayPickerProps as React.ComponentProps<typeof DayPicker>)}\n />\n );\n}\n",
"content": "\"use client\";\n\nimport { type DateLib, DayPicker } from \"@daypicker/react\";\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n ChevronsUpDownIcon,\n} from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nconst buttonClassNames =\n \"relative flex size-(--cell-size) text-base sm:text-sm items-center justify-center rounded-lg text-foreground not-in-data-selected:hover:bg-accent disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\";\n\ntype CalendarClassNames = NonNullable<\n React.ComponentProps<typeof DayPicker>[\"classNames\"]\n>;\ntype CalendarFormatters = NonNullable<\n React.ComponentProps<typeof DayPicker>[\"formatters\"]\n>;\n\n// A key set to `undefined` counts as not provided, so it never clears a default.\nfunction withoutUndefined<T extends object>(value: T | undefined): Partial<T> {\n return Object.fromEntries(\n Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined),\n ) as Partial<T>;\n}\n\nexport function Calendar({\n className,\n classNames,\n showOutsideDays = true,\n components: userComponents,\n formatters: userFormatters,\n mode = \"single\",\n ...props\n}: React.ComponentProps<typeof DayPicker>): React.ReactElement {\n const defaultClassNames = {\n button_next: buttonClassNames,\n button_previous: buttonClassNames,\n caption_label:\n \"text-base sm:text-sm font-medium flex items-center gap-2 h-full\",\n day: \"size-(--cell-size) text-sm py-px\",\n day_button: cn(\n buttonClassNames,\n \"in-data-disabled:pointer-events-none in-[.range-middle]:rounded-none in-[.range-end:not(.range-start)]:rounded-s-none in-[.range-start:not(.range-end)]:rounded-e-none in-[.range-middle]:in-data-selected:bg-accent in-data-selected:bg-primary in-[.range-middle]:in-data-selected:text-foreground in-data-disabled:text-muted-foreground/72 in-data-outside:text-muted-foreground/72 in-data-selected:in-data-outside:text-primary-foreground in-data-selected:text-primary-foreground in-data-disabled:line-through outline-none in-[[data-selected]:not(.range-middle)]:transition-[border-radius,box-shadow] focus-visible:z-1 focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n ),\n dropdown: \"absolute bg-popover inset-0 opacity-0\",\n dropdown_root:\n \"relative has-focus:border-ring has-focus:ring-ring/50 has-focus:ring-[3px] border border-input shadow-xs/5 rounded-lg px-[calc(--spacing(3)-1px)] h-9 sm:h-8 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-me-1\",\n dropdowns:\n \"w-full flex items-center text-base sm:text-sm justify-center h-(--cell-size) gap-1.5 *:[span]:font-medium\",\n hidden: \"invisible\",\n month: \"w-full\",\n month_caption:\n \"relative mx-(--cell-size) px-1 mb-1 flex h-(--cell-size) items-center justify-center z-2\",\n months: \"relative flex flex-col sm:flex-row gap-2\",\n nav: \"absolute top-0 flex w-full justify-between z-1\",\n outside:\n \"text-muted-foreground data-selected:bg-accent/50 data-selected:text-muted-foreground\",\n range_end: \"range-end\",\n range_middle: \"range-middle\",\n range_start: \"range-start\",\n today:\n \"*:after:pointer-events-none *:after:absolute *:after:bottom-1 *:after:start-1/2 *:after:z-1 *:after:size-[3px] *:after:-translate-x-1/2 *:after:rounded-full *:after:bg-primary [&[data-selected]:not(.range-middle)>*]:after:bg-background [&[data-disabled]>*]:after:bg-foreground/30\",\n week_number:\n \"size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72\",\n weekday:\n \"size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72\",\n };\n const mergedClassNames: CalendarClassNames = Object.keys(\n defaultClassNames,\n ).reduce(\n (acc, key) => {\n const userClass = classNames?.[key as keyof typeof classNames];\n const baseClass =\n defaultClassNames[key as keyof typeof defaultClassNames];\n\n acc[key as keyof typeof defaultClassNames] = userClass\n ? cn(baseClass, userClass)\n : baseClass;\n\n return acc;\n },\n // Parts without a coss default keep the consumer's classes as passed.\n withoutUndefined<CalendarClassNames>(classNames),\n );\n\n const defaultComponents = {\n Chevron: ({\n className,\n orientation,\n ...props\n }: {\n className?: string;\n orientation?: \"left\" | \"right\" | \"up\" | \"down\";\n }): React.ReactElement => {\n if (orientation === \"left\") {\n return (\n <ChevronLeftIcon\n className={cn(className, \"rtl:rotate-180\")}\n {...props}\n aria-hidden=\"true\"\n />\n );\n }\n\n if (orientation === \"right\") {\n return (\n <ChevronRightIcon\n className={cn(className, \"rtl:rotate-180\")}\n {...props}\n aria-hidden=\"true\"\n />\n );\n }\n\n return (\n <ChevronsUpDownIcon\n className={className}\n {...props}\n aria-hidden=\"true\"\n />\n );\n },\n };\n\n const mergedComponents = {\n ...defaultComponents,\n ...userComponents,\n };\n\n const dayPickerProps = {\n className: cn(\n \"w-fit [--cell-size:--spacing(10)] sm:[--cell-size:--spacing(9)]\",\n className,\n ),\n classNames: mergedClassNames,\n components: mergedComponents,\n \"data-slot\": \"calendar\",\n formatters: {\n formatMonthDropdown: (month: Date, dateLib: DateLib) =>\n dateLib.format(month, \"LLL\"),\n ...withoutUndefined<CalendarFormatters>(userFormatters),\n } as CalendarFormatters,\n mode,\n showOutsideDays,\n ...props,\n };\n\n return (\n <DayPicker\n {...(dayPickerProps as React.ComponentProps<typeof DayPicker>)}\n />\n );\n}\n",
"type": "registry:ui"
}
],
Expand Down
23 changes: 20 additions & 3 deletions apps/ui/registry/default/ui/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,26 @@ import { cn } from "@/registry/default/lib/utils";
const buttonClassNames =
"relative flex size-(--cell-size) text-base sm:text-sm items-center justify-center rounded-lg text-foreground not-in-data-selected:hover:bg-accent disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0";

type CalendarClassNames = NonNullable<
React.ComponentProps<typeof DayPicker>["classNames"]
>;
type CalendarFormatters = NonNullable<
React.ComponentProps<typeof DayPicker>["formatters"]
>;

// A key set to `undefined` counts as not provided, so it never clears a default.
function withoutUndefined<T extends object>(value: T | undefined): Partial<T> {
return Object.fromEntries(
Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined),
) as Partial<T>;
}

export function Calendar({
className,
classNames,
showOutsideDays = true,
components: userComponents,
formatters: userFormatters,
mode = "single",
...props
}: React.ComponentProps<typeof DayPicker>): React.ReactElement {
Expand Down Expand Up @@ -53,7 +68,7 @@ export function Calendar({
weekday:
"size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72",
};
const mergedClassNames: typeof defaultClassNames = Object.keys(
const mergedClassNames: CalendarClassNames = Object.keys(
defaultClassNames,
).reduce(
(acc, key) => {
Expand All @@ -67,7 +82,8 @@ export function Calendar({

return acc;
},
{ ...defaultClassNames } as typeof defaultClassNames,
// Parts without a coss default keep the consumer's classes as passed.
withoutUndefined<CalendarClassNames>(classNames),
);

const defaultComponents = {
Expand Down Expand Up @@ -125,7 +141,8 @@ export function Calendar({
formatters: {
formatMonthDropdown: (month: Date, dateLib: DateLib) =>
dateLib.format(month, "LLL"),
} as React.ComponentProps<typeof DayPicker>["formatters"],
...withoutUndefined<CalendarFormatters>(userFormatters),
} as CalendarFormatters,
mode,
showOutsideDays,
...props,
Expand Down
23 changes: 20 additions & 3 deletions packages/ui/src/components/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,26 @@ import type * as React from "react";
const buttonClassNames =
"relative flex size-(--cell-size) text-base sm:text-sm items-center justify-center rounded-lg text-foreground not-in-data-selected:hover:bg-accent disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0";

type CalendarClassNames = NonNullable<
React.ComponentProps<typeof DayPicker>["classNames"]
>;
type CalendarFormatters = NonNullable<
React.ComponentProps<typeof DayPicker>["formatters"]
>;

// A key set to `undefined` counts as not provided, so it never clears a default.
function withoutUndefined<T extends object>(value: T | undefined): Partial<T> {
return Object.fromEntries(
Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined),
) as Partial<T>;
}

export function Calendar({
className,
classNames,
showOutsideDays = true,
components: userComponents,
formatters: userFormatters,
mode = "single",
...props
}: React.ComponentProps<typeof DayPicker>): React.ReactElement {
Expand Down Expand Up @@ -53,7 +68,7 @@ export function Calendar({
weekday:
"size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72",
};
const mergedClassNames: typeof defaultClassNames = Object.keys(
const mergedClassNames: CalendarClassNames = Object.keys(
defaultClassNames,
).reduce(
(acc, key) => {
Expand All @@ -67,7 +82,8 @@ export function Calendar({

return acc;
},
{ ...defaultClassNames } as typeof defaultClassNames,
// Parts without a coss default keep the consumer's classes as passed.
withoutUndefined<CalendarClassNames>(classNames),
);

const defaultComponents = {
Expand Down Expand Up @@ -125,7 +141,8 @@ export function Calendar({
formatters: {
formatMonthDropdown: (month: Date, dateLib: DateLib) =>
dateLib.format(month, "LLL"),
} as React.ComponentProps<typeof DayPicker>["formatters"],
...withoutUndefined<CalendarFormatters>(userFormatters),
} as CalendarFormatters,
mode,
showOutsideDays,
...props,
Expand Down
Loading
Loading