@dalexto/lexsys-registry 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +211 -3
- package/dist/items/breadcrumb.d.ts +7 -0
- package/dist/items/data-table.d.ts +7 -0
- package/dist/items/date-picker.d.ts +7 -0
- package/dist/items/filter-toolbar.d.ts +7 -0
- package/dist/items/index.d.ts +8 -0
- package/dist/items/page-header.d.ts +7 -0
- package/dist/items/pagination.d.ts +7 -0
- package/dist/items/settings-page-layout.d.ts +7 -0
- package/dist/items/stats-card.d.ts +7 -0
- package/package.json +1 -1
- package/templates/blocks/CommandPalette/CommandPalette.tsx +12 -8
- package/templates/blocks/CommandPalette/CommandPalette.types.ts +2 -2
- package/templates/blocks/DataTable/DataTable.tsx +236 -0
- package/templates/blocks/DataTable/DataTable.types.ts +74 -0
- package/templates/blocks/DataTable/DataTable.variants.ts +23 -0
- package/templates/blocks/FilterToolbar/FilterToolbar.tsx +126 -0
- package/templates/blocks/FilterToolbar/FilterToolbar.types.ts +31 -0
- package/templates/blocks/FilterToolbar/FilterToolbar.variants.ts +24 -0
- package/templates/blocks/PageHeader/PageHeader.tsx +215 -0
- package/templates/blocks/PageHeader/PageHeader.types.ts +66 -0
- package/templates/blocks/PageHeader/PageHeader.variants.ts +48 -0
- package/templates/blocks/StatsCard/StatsCard.tsx +132 -0
- package/templates/blocks/StatsCard/StatsCard.types.ts +43 -0
- package/templates/blocks/StatsCard/StatsCard.variants.ts +28 -0
- package/templates/primitives/Breadcrumb/Breadcrumb.tsx +142 -0
- package/templates/primitives/Breadcrumb/Breadcrumb.types.ts +62 -0
- package/templates/primitives/Breadcrumb/Breadcrumb.variants.ts +37 -0
- package/templates/primitives/DatePicker/DatePicker.tsx +263 -0
- package/templates/primitives/DatePicker/DatePicker.types.ts +52 -0
- package/templates/primitives/DatePicker/DatePicker.variants.ts +76 -0
- package/templates/primitives/Pagination/Pagination.tsx +160 -0
- package/templates/primitives/Pagination/Pagination.types.ts +54 -0
- package/templates/primitives/Pagination/Pagination.variants.ts +47 -0
- package/templates/primitives/Toolbar/Toolbar.variants.ts +15 -15
- package/templates/styles/theme.css +1 -1
- package/templates/styles/tokens.css +334 -1
- package/templates/templates/SettingsPageLayout/SettingsPageLayout.tsx +198 -0
- package/templates/templates/SettingsPageLayout/SettingsPageLayout.types.ts +55 -0
- package/templates/templates/SettingsPageLayout/SettingsPageLayout.variants.ts +42 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsCard.tsx
|
|
3
|
+
*
|
|
4
|
+
* Reference StatsCard block — compound Card metric summary.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardFooter,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "@/components/primitives/Card"
|
|
15
|
+
import type {
|
|
16
|
+
StatsCardContentProps,
|
|
17
|
+
StatsCardDescriptionProps,
|
|
18
|
+
StatsCardFooterProps,
|
|
19
|
+
StatsCardHeaderProps,
|
|
20
|
+
StatsCardProps,
|
|
21
|
+
StatsCardTitleProps,
|
|
22
|
+
StatsCardTrendProps,
|
|
23
|
+
StatsCardValueProps,
|
|
24
|
+
} from "./StatsCard.types"
|
|
25
|
+
import {
|
|
26
|
+
statsCardClasses,
|
|
27
|
+
statsCardTrendClasses,
|
|
28
|
+
statsCardValueClasses,
|
|
29
|
+
} from "./StatsCard.variants"
|
|
30
|
+
import { cn } from "@/lib/utils"
|
|
31
|
+
|
|
32
|
+
const StatsCard = ({
|
|
33
|
+
ref,
|
|
34
|
+
variant,
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
...cardProps
|
|
38
|
+
}: StatsCardProps) => {
|
|
39
|
+
return (
|
|
40
|
+
<Card
|
|
41
|
+
ref={ref}
|
|
42
|
+
variant={variant}
|
|
43
|
+
className={cn(statsCardClasses(), className)}
|
|
44
|
+
{...cardProps}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</Card>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
StatsCard.displayName = "StatsCard"
|
|
52
|
+
|
|
53
|
+
const StatsCardHeader = ({
|
|
54
|
+
ref,
|
|
55
|
+
className,
|
|
56
|
+
...props
|
|
57
|
+
}: StatsCardHeaderProps) => {
|
|
58
|
+
return <CardHeader ref={ref} className={className} {...props} />
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
StatsCardHeader.displayName = "StatsCardHeader"
|
|
62
|
+
|
|
63
|
+
const StatsCardTitle = ({ ref, className, ...props }: StatsCardTitleProps) => {
|
|
64
|
+
return <CardTitle ref={ref} className={className} {...props} />
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
StatsCardTitle.displayName = "StatsCardTitle"
|
|
68
|
+
|
|
69
|
+
const StatsCardDescription = ({
|
|
70
|
+
ref,
|
|
71
|
+
className,
|
|
72
|
+
...props
|
|
73
|
+
}: StatsCardDescriptionProps) => {
|
|
74
|
+
return <CardDescription ref={ref} className={className} {...props} />
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
StatsCardDescription.displayName = "StatsCardDescription"
|
|
78
|
+
|
|
79
|
+
const StatsCardContent = ({
|
|
80
|
+
ref,
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: StatsCardContentProps) => {
|
|
84
|
+
return <CardContent ref={ref} className={className} {...props} />
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
StatsCardContent.displayName = "StatsCardContent"
|
|
88
|
+
|
|
89
|
+
const StatsCardValue = ({ ref, className, ...props }: StatsCardValueProps) => {
|
|
90
|
+
return (
|
|
91
|
+
<p
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={cn(statsCardValueClasses(), className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
StatsCardValue.displayName = "StatsCardValue"
|
|
100
|
+
|
|
101
|
+
const StatsCardTrend = ({ ref, className, ...props }: StatsCardTrendProps) => {
|
|
102
|
+
return (
|
|
103
|
+
<span
|
|
104
|
+
ref={ref}
|
|
105
|
+
className={cn(statsCardTrendClasses(), className)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
StatsCardTrend.displayName = "StatsCardTrend"
|
|
112
|
+
|
|
113
|
+
const StatsCardFooter = ({
|
|
114
|
+
ref,
|
|
115
|
+
className,
|
|
116
|
+
...props
|
|
117
|
+
}: StatsCardFooterProps) => {
|
|
118
|
+
return <CardFooter ref={ref} className={className} {...props} />
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
StatsCardFooter.displayName = "StatsCardFooter"
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
StatsCard,
|
|
125
|
+
StatsCardHeader,
|
|
126
|
+
StatsCardTitle,
|
|
127
|
+
StatsCardDescription,
|
|
128
|
+
StatsCardContent,
|
|
129
|
+
StatsCardValue,
|
|
130
|
+
StatsCardTrend,
|
|
131
|
+
StatsCardFooter,
|
|
132
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsCard.types.ts
|
|
3
|
+
*
|
|
4
|
+
* Public types for the StatsCard block.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HTMLAttributes, ReactNode, Ref } from "react"
|
|
8
|
+
import type {
|
|
9
|
+
CardContentProps,
|
|
10
|
+
CardDescriptionProps,
|
|
11
|
+
CardFooterProps,
|
|
12
|
+
CardHeaderProps,
|
|
13
|
+
CardProps,
|
|
14
|
+
CardTitleProps,
|
|
15
|
+
} from "@/components/primitives/Card"
|
|
16
|
+
|
|
17
|
+
export interface StatsCardProps extends Omit<CardProps, "children"> {
|
|
18
|
+
ref?: Ref<HTMLDivElement>
|
|
19
|
+
className?: CardProps["className"]
|
|
20
|
+
children?: ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type StatsCardHeaderProps = CardHeaderProps
|
|
24
|
+
|
|
25
|
+
export type StatsCardTitleProps = CardTitleProps
|
|
26
|
+
|
|
27
|
+
export type StatsCardDescriptionProps = CardDescriptionProps
|
|
28
|
+
|
|
29
|
+
export type StatsCardContentProps = CardContentProps
|
|
30
|
+
|
|
31
|
+
export interface StatsCardValueProps extends HTMLAttributes<HTMLParagraphElement> {
|
|
32
|
+
ref?: Ref<HTMLParagraphElement>
|
|
33
|
+
className?: string
|
|
34
|
+
children?: ReactNode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface StatsCardTrendProps extends HTMLAttributes<HTMLSpanElement> {
|
|
38
|
+
ref?: Ref<HTMLSpanElement>
|
|
39
|
+
className?: string
|
|
40
|
+
children?: ReactNode
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type StatsCardFooterProps = CardFooterProps
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsCard.variants.ts
|
|
3
|
+
*
|
|
4
|
+
* Variant classes for the StatsCard block.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const statsCardClasses = (): string => {
|
|
8
|
+
return "lex-stats-card"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const statsCardValueClasses = (): string => {
|
|
12
|
+
return [
|
|
13
|
+
"lex-stats-card__value",
|
|
14
|
+
"text-(length:--lex-typography-heading-md-font-size)",
|
|
15
|
+
"font-(--lex-typography-heading-md-font-weight)",
|
|
16
|
+
"leading-(--lex-typography-heading-md-font-line-height)",
|
|
17
|
+
"text-(--lex-color-text-primary)",
|
|
18
|
+
"m-0",
|
|
19
|
+
].join(" ")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const statsCardTrendClasses = (): string => {
|
|
23
|
+
return [
|
|
24
|
+
"lex-stats-card__trend",
|
|
25
|
+
"text-(length:--lex-typography-body-xs-font-size)",
|
|
26
|
+
"text-(--lex-color-text-secondary)",
|
|
27
|
+
].join(" ")
|
|
28
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breadcrumb.tsx
|
|
3
|
+
*
|
|
4
|
+
* Reference Breadcrumb component implementation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
|
8
|
+
import type {
|
|
9
|
+
BreadcrumbEllipsisProps,
|
|
10
|
+
BreadcrumbItemProps,
|
|
11
|
+
BreadcrumbLinkProps,
|
|
12
|
+
BreadcrumbListProps,
|
|
13
|
+
BreadcrumbPageProps,
|
|
14
|
+
BreadcrumbProps,
|
|
15
|
+
BreadcrumbSeparatorProps,
|
|
16
|
+
} from "./Breadcrumb.types"
|
|
17
|
+
import {
|
|
18
|
+
breadcrumbEllipsisVariants,
|
|
19
|
+
breadcrumbItemVariants,
|
|
20
|
+
breadcrumbLinkVariants,
|
|
21
|
+
breadcrumbListVariants,
|
|
22
|
+
breadcrumbPageVariants,
|
|
23
|
+
breadcrumbRootVariants,
|
|
24
|
+
breadcrumbSeparatorVariants,
|
|
25
|
+
} from "./Breadcrumb.variants"
|
|
26
|
+
import { cn } from "@/lib/utils"
|
|
27
|
+
|
|
28
|
+
const Breadcrumb = ({ ref, className, ...props }: BreadcrumbProps) => {
|
|
29
|
+
return (
|
|
30
|
+
<nav
|
|
31
|
+
ref={ref}
|
|
32
|
+
aria-label="breadcrumb"
|
|
33
|
+
className={cn(breadcrumbRootVariants(), className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Breadcrumb.displayName = "Breadcrumb"
|
|
40
|
+
|
|
41
|
+
const BreadcrumbList = ({ ref, className, ...props }: BreadcrumbListProps) => {
|
|
42
|
+
return (
|
|
43
|
+
<ol
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn(breadcrumbListVariants(), className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
BreadcrumbList.displayName = "BreadcrumbList"
|
|
52
|
+
|
|
53
|
+
const BreadcrumbItem = ({ ref, className, ...props }: BreadcrumbItemProps) => {
|
|
54
|
+
return (
|
|
55
|
+
<li
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={cn(breadcrumbItemVariants(), className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
BreadcrumbItem.displayName = "BreadcrumbItem"
|
|
64
|
+
|
|
65
|
+
const BreadcrumbLink = ({ ref, className, ...props }: BreadcrumbLinkProps) => {
|
|
66
|
+
return (
|
|
67
|
+
<a
|
|
68
|
+
ref={ref}
|
|
69
|
+
className={cn(breadcrumbLinkVariants(), className)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
BreadcrumbLink.displayName = "BreadcrumbLink"
|
|
76
|
+
|
|
77
|
+
const BreadcrumbPage = ({ ref, className, ...props }: BreadcrumbPageProps) => {
|
|
78
|
+
return (
|
|
79
|
+
<span
|
|
80
|
+
ref={ref}
|
|
81
|
+
role="link"
|
|
82
|
+
aria-disabled="true"
|
|
83
|
+
aria-current="page"
|
|
84
|
+
className={cn(breadcrumbPageVariants(), className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
BreadcrumbPage.displayName = "BreadcrumbPage"
|
|
91
|
+
|
|
92
|
+
const BreadcrumbSeparator = ({
|
|
93
|
+
ref,
|
|
94
|
+
className,
|
|
95
|
+
children,
|
|
96
|
+
...props
|
|
97
|
+
}: BreadcrumbSeparatorProps) => {
|
|
98
|
+
return (
|
|
99
|
+
<span
|
|
100
|
+
ref={ref}
|
|
101
|
+
role="presentation"
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
className={cn(breadcrumbSeparatorVariants(), className)}
|
|
104
|
+
{...props}
|
|
105
|
+
>
|
|
106
|
+
{children ?? <ChevronRight size={14} />}
|
|
107
|
+
</span>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
|
112
|
+
|
|
113
|
+
const BreadcrumbEllipsis = ({
|
|
114
|
+
ref,
|
|
115
|
+
className,
|
|
116
|
+
...props
|
|
117
|
+
}: BreadcrumbEllipsisProps) => {
|
|
118
|
+
return (
|
|
119
|
+
<span
|
|
120
|
+
ref={ref}
|
|
121
|
+
role="presentation"
|
|
122
|
+
aria-hidden="true"
|
|
123
|
+
className={cn(breadcrumbEllipsisVariants(), className)}
|
|
124
|
+
{...props}
|
|
125
|
+
>
|
|
126
|
+
<MoreHorizontal size={14} />
|
|
127
|
+
<span className="sr-only">More</span>
|
|
128
|
+
</span>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis"
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
Breadcrumb,
|
|
136
|
+
BreadcrumbList,
|
|
137
|
+
BreadcrumbItem,
|
|
138
|
+
BreadcrumbLink,
|
|
139
|
+
BreadcrumbPage,
|
|
140
|
+
BreadcrumbSeparator,
|
|
141
|
+
BreadcrumbEllipsis,
|
|
142
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, HTMLAttributes, Ref } from "react"
|
|
2
|
+
/**
|
|
3
|
+
* Breadcrumb.types.ts
|
|
4
|
+
*
|
|
5
|
+
* Public and internal types for Breadcrumb component.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface BreadcrumbProps extends Omit<
|
|
9
|
+
HTMLAttributes<HTMLElement>,
|
|
10
|
+
"className"
|
|
11
|
+
> {
|
|
12
|
+
ref?: Ref<HTMLElement>
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BreadcrumbListProps extends Omit<
|
|
17
|
+
HTMLAttributes<HTMLOListElement>,
|
|
18
|
+
"className"
|
|
19
|
+
> {
|
|
20
|
+
ref?: Ref<HTMLOListElement>
|
|
21
|
+
className?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BreadcrumbItemProps extends Omit<
|
|
25
|
+
HTMLAttributes<HTMLLIElement>,
|
|
26
|
+
"className"
|
|
27
|
+
> {
|
|
28
|
+
ref?: Ref<HTMLLIElement>
|
|
29
|
+
className?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BreadcrumbLinkProps extends Omit<
|
|
33
|
+
AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
34
|
+
"className"
|
|
35
|
+
> {
|
|
36
|
+
ref?: Ref<HTMLAnchorElement>
|
|
37
|
+
className?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BreadcrumbPageProps extends Omit<
|
|
41
|
+
HTMLAttributes<HTMLSpanElement>,
|
|
42
|
+
"className"
|
|
43
|
+
> {
|
|
44
|
+
ref?: Ref<HTMLSpanElement>
|
|
45
|
+
className?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BreadcrumbSeparatorProps extends Omit<
|
|
49
|
+
HTMLAttributes<HTMLSpanElement>,
|
|
50
|
+
"className"
|
|
51
|
+
> {
|
|
52
|
+
ref?: Ref<HTMLSpanElement>
|
|
53
|
+
className?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BreadcrumbEllipsisProps extends Omit<
|
|
57
|
+
HTMLAttributes<HTMLSpanElement>,
|
|
58
|
+
"className"
|
|
59
|
+
> {
|
|
60
|
+
ref?: Ref<HTMLSpanElement>
|
|
61
|
+
className?: string
|
|
62
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breadcrumb.variants.ts
|
|
3
|
+
*
|
|
4
|
+
* Defines visual variants using class composition.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cva } from "class-variance-authority"
|
|
8
|
+
|
|
9
|
+
export const breadcrumbRootVariants = cva("w-full")
|
|
10
|
+
|
|
11
|
+
export const breadcrumbListVariants = cva(
|
|
12
|
+
"flex flex-wrap items-center gap-(--lex-breadcrumb-list-gap) break-words text-(length:--lex-breadcrumb-link-font-size) leading-(--lex-breadcrumb-link-font-line-height)",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export const breadcrumbItemVariants = cva(
|
|
16
|
+
"inline-flex items-center gap-(--lex-breadcrumb-item-gap)",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
export const breadcrumbLinkVariants = cva(
|
|
20
|
+
[
|
|
21
|
+
"text-(--lex-breadcrumb-link-foreground) font-(--lex-breadcrumb-link-font-weight) transition-colors duration-(--lex-breadcrumb-transition-duration) ease-(--lex-breadcrumb-transition-easing)",
|
|
22
|
+
"hover:text-(--lex-breadcrumb-link-hover-foreground)",
|
|
23
|
+
"outline-none focus-visible:underline",
|
|
24
|
+
].join(" "),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export const breadcrumbPageVariants = cva(
|
|
28
|
+
"font-(--lex-breadcrumb-page-font-weight) text-(--lex-breadcrumb-page-foreground)",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const breadcrumbSeparatorVariants = cva(
|
|
32
|
+
"inline-flex items-center text-(--lex-breadcrumb-separator-foreground)",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const breadcrumbEllipsisVariants = cva(
|
|
36
|
+
"inline-flex h-(--lex-breadcrumb-ellipsis-size) w-(--lex-breadcrumb-ellipsis-size) items-center justify-center text-(--lex-breadcrumb-ellipsis-foreground)",
|
|
37
|
+
)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatePicker.tsx
|
|
3
|
+
*
|
|
4
|
+
* Reference DatePicker component implementation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState } from "react"
|
|
8
|
+
import { ChevronLeft, ChevronRight } from "lucide-react"
|
|
9
|
+
import { Input } from "../Input/Input"
|
|
10
|
+
import {
|
|
11
|
+
Popover,
|
|
12
|
+
PopoverPortal,
|
|
13
|
+
PopoverPositioner,
|
|
14
|
+
PopoverPopup,
|
|
15
|
+
PopoverTrigger,
|
|
16
|
+
} from "../Popover/Popover"
|
|
17
|
+
import type {
|
|
18
|
+
DatePickerCalendarProps,
|
|
19
|
+
DatePickerContentProps,
|
|
20
|
+
DatePickerDayProps,
|
|
21
|
+
DatePickerInputProps,
|
|
22
|
+
DatePickerProps,
|
|
23
|
+
DatePickerTriggerProps,
|
|
24
|
+
} from "./DatePicker.types"
|
|
25
|
+
import {
|
|
26
|
+
datePickerCalendarVariants,
|
|
27
|
+
datePickerContentVariants,
|
|
28
|
+
datePickerDayVariants,
|
|
29
|
+
datePickerGridVariants,
|
|
30
|
+
datePickerHeaderVariants,
|
|
31
|
+
datePickerMonthLabelVariants,
|
|
32
|
+
datePickerNavButtonVariants,
|
|
33
|
+
datePickerWeekdayVariants,
|
|
34
|
+
datePickerWeekdaysVariants,
|
|
35
|
+
} from "./DatePicker.variants"
|
|
36
|
+
import { cn } from "@/lib/utils"
|
|
37
|
+
|
|
38
|
+
const WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] as const
|
|
39
|
+
|
|
40
|
+
const isSameDay = (left: Date, right: Date): boolean => {
|
|
41
|
+
return (
|
|
42
|
+
left.getFullYear() === right.getFullYear() &&
|
|
43
|
+
left.getMonth() === right.getMonth() &&
|
|
44
|
+
left.getDate() === right.getDate()
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const buildCalendarDays = (
|
|
49
|
+
month: Date,
|
|
50
|
+
): Array<{ date: Date; inMonth: boolean }> => {
|
|
51
|
+
const year = month.getFullYear()
|
|
52
|
+
const monthIndex = month.getMonth()
|
|
53
|
+
const firstDay = new Date(year, monthIndex, 1)
|
|
54
|
+
const lastDay = new Date(year, monthIndex + 1, 0)
|
|
55
|
+
const startOffset = firstDay.getDay()
|
|
56
|
+
const days: Array<{ date: Date; inMonth: boolean }> = []
|
|
57
|
+
|
|
58
|
+
for (let offset = startOffset - 1; offset >= 0; offset -= 1) {
|
|
59
|
+
days.push({
|
|
60
|
+
date: new Date(year, monthIndex, -offset),
|
|
61
|
+
inMonth: false,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let day = 1; day <= lastDay.getDate(); day += 1) {
|
|
66
|
+
days.push({
|
|
67
|
+
date: new Date(year, monthIndex, day),
|
|
68
|
+
inMonth: true,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
while (days.length % 7 !== 0) {
|
|
73
|
+
const trailingDay = days.length - startOffset - lastDay.getDate() + 1
|
|
74
|
+
days.push({
|
|
75
|
+
date: new Date(year, monthIndex + 1, trailingDay),
|
|
76
|
+
inMonth: false,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return days
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const formatMonthLabel = (month: Date): string => {
|
|
84
|
+
return month.toLocaleString("default", { month: "long", year: "numeric" })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const DatePicker = <Payload = unknown,>(props: DatePickerProps<Payload>) => {
|
|
88
|
+
return <Popover {...props} />
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
DatePicker.displayName = "DatePicker"
|
|
92
|
+
|
|
93
|
+
const DatePickerTrigger = <Payload = unknown,>({
|
|
94
|
+
ref,
|
|
95
|
+
...props
|
|
96
|
+
}: DatePickerTriggerProps<Payload>) => {
|
|
97
|
+
return <PopoverTrigger ref={ref} {...props} />
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
DatePickerTrigger.displayName = "DatePickerTrigger"
|
|
101
|
+
|
|
102
|
+
const DatePickerInput = ({
|
|
103
|
+
ref,
|
|
104
|
+
className,
|
|
105
|
+
...props
|
|
106
|
+
}: DatePickerInputProps) => {
|
|
107
|
+
return <Input ref={ref} className={className} {...props} />
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
DatePickerInput.displayName = "DatePickerInput"
|
|
111
|
+
|
|
112
|
+
const DatePickerContent = ({
|
|
113
|
+
ref,
|
|
114
|
+
className,
|
|
115
|
+
children,
|
|
116
|
+
...props
|
|
117
|
+
}: DatePickerContentProps) => {
|
|
118
|
+
return (
|
|
119
|
+
<PopoverPortal>
|
|
120
|
+
<PopoverPositioner>
|
|
121
|
+
<PopoverPopup
|
|
122
|
+
ref={ref}
|
|
123
|
+
className={cn(datePickerContentVariants(), className)}
|
|
124
|
+
{...props}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</PopoverPopup>
|
|
128
|
+
</PopoverPositioner>
|
|
129
|
+
</PopoverPortal>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
DatePickerContent.displayName = "DatePickerContent"
|
|
134
|
+
|
|
135
|
+
const DatePickerDay = ({
|
|
136
|
+
ref,
|
|
137
|
+
className,
|
|
138
|
+
date,
|
|
139
|
+
isSelected,
|
|
140
|
+
isOutside,
|
|
141
|
+
isToday,
|
|
142
|
+
type = "button",
|
|
143
|
+
...props
|
|
144
|
+
}: DatePickerDayProps) => {
|
|
145
|
+
return (
|
|
146
|
+
<button
|
|
147
|
+
ref={ref}
|
|
148
|
+
type={type}
|
|
149
|
+
className={cn(
|
|
150
|
+
datePickerDayVariants({ isSelected, isOutside, isToday }),
|
|
151
|
+
className,
|
|
152
|
+
)}
|
|
153
|
+
{...props}
|
|
154
|
+
>
|
|
155
|
+
{date.getDate()}
|
|
156
|
+
</button>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
DatePickerDay.displayName = "DatePickerDay"
|
|
161
|
+
|
|
162
|
+
const DatePickerCalendar = ({
|
|
163
|
+
ref,
|
|
164
|
+
className,
|
|
165
|
+
value,
|
|
166
|
+
defaultMonth,
|
|
167
|
+
month,
|
|
168
|
+
onMonthChange,
|
|
169
|
+
onSelect,
|
|
170
|
+
...props
|
|
171
|
+
}: DatePickerCalendarProps) => {
|
|
172
|
+
const [internalMonth, setInternalMonth] = useState(
|
|
173
|
+
() => defaultMonth ?? value ?? new Date(),
|
|
174
|
+
)
|
|
175
|
+
const viewedMonth = month ?? internalMonth
|
|
176
|
+
const today = new Date()
|
|
177
|
+
const days = buildCalendarDays(viewedMonth)
|
|
178
|
+
|
|
179
|
+
const setMonth = (nextMonth: Date) => {
|
|
180
|
+
if (month === undefined) {
|
|
181
|
+
setInternalMonth(nextMonth)
|
|
182
|
+
}
|
|
183
|
+
onMonthChange?.(nextMonth)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div
|
|
188
|
+
ref={ref}
|
|
189
|
+
className={cn(datePickerCalendarVariants(), className)}
|
|
190
|
+
{...props}
|
|
191
|
+
>
|
|
192
|
+
<div className={datePickerHeaderVariants()}>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
aria-label="Previous month"
|
|
196
|
+
className={datePickerNavButtonVariants()}
|
|
197
|
+
onClick={() =>
|
|
198
|
+
setMonth(
|
|
199
|
+
new Date(
|
|
200
|
+
viewedMonth.getFullYear(),
|
|
201
|
+
viewedMonth.getMonth() - 1,
|
|
202
|
+
1,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
>
|
|
207
|
+
<ChevronLeft aria-hidden="true" size={16} />
|
|
208
|
+
</button>
|
|
209
|
+
<div className={datePickerMonthLabelVariants()}>
|
|
210
|
+
{formatMonthLabel(viewedMonth)}
|
|
211
|
+
</div>
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
aria-label="Next month"
|
|
215
|
+
className={datePickerNavButtonVariants()}
|
|
216
|
+
onClick={() =>
|
|
217
|
+
setMonth(
|
|
218
|
+
new Date(
|
|
219
|
+
viewedMonth.getFullYear(),
|
|
220
|
+
viewedMonth.getMonth() + 1,
|
|
221
|
+
1,
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
>
|
|
226
|
+
<ChevronRight aria-hidden="true" size={16} />
|
|
227
|
+
</button>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div className={datePickerWeekdaysVariants()}>
|
|
231
|
+
{WEEKDAY_LABELS.map((label) => (
|
|
232
|
+
<span key={label} className={datePickerWeekdayVariants()}>
|
|
233
|
+
{label}
|
|
234
|
+
</span>
|
|
235
|
+
))}
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div className={datePickerGridVariants()}>
|
|
239
|
+
{days.map(({ date, inMonth }) => (
|
|
240
|
+
<DatePickerDay
|
|
241
|
+
key={date.toISOString()}
|
|
242
|
+
date={date}
|
|
243
|
+
isOutside={!inMonth}
|
|
244
|
+
isSelected={value ? isSameDay(date, value) : false}
|
|
245
|
+
isToday={isSameDay(date, today)}
|
|
246
|
+
onClick={() => onSelect?.(date)}
|
|
247
|
+
/>
|
|
248
|
+
))}
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
DatePickerCalendar.displayName = "DatePickerCalendar"
|
|
255
|
+
|
|
256
|
+
export {
|
|
257
|
+
DatePicker,
|
|
258
|
+
DatePickerTrigger,
|
|
259
|
+
DatePickerInput,
|
|
260
|
+
DatePickerContent,
|
|
261
|
+
DatePickerCalendar,
|
|
262
|
+
DatePickerDay,
|
|
263
|
+
}
|