@a2v2ai/uikit 0.0.36 → 0.0.38
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/Alert/Alert.stories.tsx +121 -0
- package/Alert/Alert.tsx +71 -0
- package/AlertDialog/AlertDialog.stories.tsx +665 -0
- package/AlertDialog/AlertDialog.tsx +241 -0
- package/Avatar/Avatar.stories.tsx +128 -0
- package/Avatar/Avatar.tsx +71 -0
- package/Badge/Badge.stories.tsx +76 -0
- package/Badge/Badge.tsx +39 -0
- package/Breadcrumb/Breadcrumb.stories.tsx +231 -0
- package/Breadcrumb/Breadcrumb.tsx +114 -0
- package/Button/Button.stories.tsx +684 -0
- package/Button/Button.tsx +107 -0
- package/Calendar/Calendar.stories.tsx +291 -0
- package/Calendar/Calendar.tsx +246 -0
- package/Card/Card.stories.tsx +136 -0
- package/Card/Card.tsx +96 -0
- package/Carousel/Carousel.stories.tsx +256 -0
- package/Carousel/Carousel.tsx +301 -0
- package/ChatBubble/ChatBubble.stories.tsx +339 -0
- package/ChatBubble/ChatBubble.tsx +179 -0
- package/Checkbox/Checkbox.stories.tsx +137 -0
- package/Checkbox/Checkbox.tsx +53 -0
- package/DataTable/DataTable.stories.tsx +400 -0
- package/DataTable/DataTable.tsx +207 -0
- package/Drawer/Drawer.stories.tsx +721 -0
- package/Drawer/Drawer.tsx +201 -0
- package/DropdownMenu/DropdownMenu.stories.tsx +251 -0
- package/DropdownMenu/DropdownMenu.tsx +199 -0
- package/ErrorMessage/ErrorMessage.stories.tsx +159 -0
- package/ErrorMessage/ErrorMessage.tsx +55 -0
- package/Flex/Flex.stories.tsx +390 -0
- package/Flex/Flex.tsx +102 -0
- package/IconButton/IconButton.stories.tsx +566 -0
- package/IconButton/IconButton.tsx +95 -0
- package/Input/Input.stories.tsx +566 -0
- package/Input/Input.tsx +168 -0
- package/InputOTP/InputOTP.stories.tsx +246 -0
- package/InputOTP/InputOTP.tsx +127 -0
- package/Label/Label.stories.tsx +110 -0
- package/Label/Label.tsx +44 -0
- package/Loader/Loader.stories.tsx +170 -0
- package/Loader/Loader.tsx +62 -0
- package/Menubar/Menubar.stories.tsx +382 -0
- package/Menubar/Menubar.tsx +274 -0
- package/Menubar/index.ts +18 -0
- package/Pagination/Pagination.stories.tsx +196 -0
- package/Pagination/Pagination.tsx +122 -0
- package/Popover/Popover.stories.tsx +133 -0
- package/Popover/Popover.tsx +31 -0
- package/Progress/Progress.stories.tsx +146 -0
- package/Progress/Progress.tsx +67 -0
- package/RadioGroup/RadioGroup.stories.tsx +159 -0
- package/RadioGroup/RadioGroup.tsx +68 -0
- package/ScrollArea/ScrollArea.stories.tsx +136 -0
- package/ScrollArea/ScrollArea.tsx +46 -0
- package/Select/Select.stories.tsx +378 -0
- package/Select/Select.tsx +230 -0
- package/Separator/Separator.stories.tsx +110 -0
- package/Separator/Separator.tsx +29 -0
- package/Sidebar/Sidebar.stories.tsx +340 -0
- package/Sidebar/Sidebar.tsx +414 -0
- package/Sidebar/index.ts +28 -0
- package/Skeleton/Skeleton.stories.tsx +117 -0
- package/Skeleton/Skeleton.tsx +16 -0
- package/Slider/Slider.stories.tsx +216 -0
- package/Slider/Slider.tsx +29 -0
- package/Spinner/Spinner.stories.tsx +210 -0
- package/Spinner/Spinner.tsx +78 -0
- package/Switch/Switch.stories.tsx +146 -0
- package/Switch/Switch.tsx +59 -0
- package/Table/Table.stories.tsx +510 -0
- package/Table/Table.tsx +114 -0
- package/Tabs/Tabs.stories.tsx +197 -0
- package/Tabs/Tabs.tsx +74 -0
- package/Textarea/Textarea.stories.tsx +187 -0
- package/Textarea/Textarea.tsx +73 -0
- package/Toast/Toast.stories.tsx +285 -0
- package/Toast/Toast.tsx +59 -0
- package/Tooltip/Tooltip.stories.tsx +463 -0
- package/Tooltip/Tooltip.tsx +96 -0
- package/Typography/Typography.stories.tsx +425 -0
- package/Typography/Typography.tsx +106 -0
- package/helpers.ts +5 -0
- package/{icons.js → icons.ts} +1 -1
- package/index.ts +217 -0
- package/lib/typography-types.ts +223 -0
- package/lib/utils.ts +15 -0
- package/package.json +36 -33
- package/tsconfig.json +22 -0
- package/Alert/Alert.d.ts +0 -13
- package/Alert/Alert.js +0 -25
- package/AlertDialog/AlertDialog.d.ts +0 -43
- package/AlertDialog/AlertDialog.js +0 -71
- package/Avatar/Avatar.d.ts +0 -14
- package/Avatar/Avatar.js +0 -25
- package/Badge/Badge.d.ts +0 -11
- package/Badge/Badge.js +0 -23
- package/Breadcrumb/Breadcrumb.d.ts +0 -19
- package/Breadcrumb/Breadcrumb.js +0 -23
- package/Button/Button.d.ts +0 -23
- package/Button/Button.js +0 -52
- package/Calendar/Calendar.d.ts +0 -20
- package/Calendar/Calendar.js +0 -78
- package/Card/Card.d.ts +0 -16
- package/Card/Card.js +0 -28
- package/Carousel/Carousel.d.ts +0 -37
- package/Carousel/Carousel.js +0 -132
- package/ChatBubble/ChatBubble.d.ts +0 -33
- package/ChatBubble/ChatBubble.js +0 -107
- package/Checkbox/Checkbox.d.ts +0 -12
- package/Checkbox/Checkbox.js +0 -20
- package/DataTable/DataTable.d.ts +0 -35
- package/DataTable/DataTable.js +0 -51
- package/Dialog/Dialog.d.ts +0 -35
- package/Dialog/Dialog.js +0 -130
- package/Drawer/Drawer.d.ts +0 -33
- package/Drawer/Drawer.js +0 -55
- package/DropdownMenu/DropdownMenu.d.ts +0 -27
- package/DropdownMenu/DropdownMenu.js +0 -35
- package/ErrorMessage/ErrorMessage.d.ts +0 -27
- package/ErrorMessage/ErrorMessage.js +0 -14
- package/Flex/Flex.d.ts +0 -31
- package/Flex/Flex.js +0 -64
- package/IconButton/IconButton.d.ts +0 -23
- package/IconButton/IconButton.js +0 -48
- package/Input/Input.d.ts +0 -27
- package/Input/Input.js +0 -42
- package/InputOTP/InputOTP.d.ts +0 -20
- package/InputOTP/InputOTP.js +0 -44
- package/Label/Label.d.ts +0 -13
- package/Label/Label.js +0 -19
- package/Loader/Loader.d.ts +0 -21
- package/Loader/Loader.js +0 -30
- package/Menubar/Menubar.d.ts +0 -26
- package/Menubar/Menubar.js +0 -54
- package/Menubar/index.d.ts +0 -1
- package/Menubar/index.js +0 -1
- package/Pagination/Pagination.d.ts +0 -35
- package/Pagination/Pagination.js +0 -37
- package/Popover/Popover.d.ts +0 -7
- package/Popover/Popover.js +0 -10
- package/Progress/Progress.d.ts +0 -17
- package/Progress/Progress.js +0 -33
- package/RadioGroup/RadioGroup.d.ts +0 -13
- package/RadioGroup/RadioGroup.js +0 -26
- package/ScrollArea/ScrollArea.d.ts +0 -5
- package/ScrollArea/ScrollArea.js +0 -11
- package/Select/Select.d.ts +0 -29
- package/Select/Select.js +0 -50
- package/Separator/Separator.d.ts +0 -4
- package/Separator/Separator.js +0 -7
- package/Sidebar/Sidebar.d.ts +0 -48
- package/Sidebar/Sidebar.js +0 -116
- package/Sidebar/index.d.ts +0 -2
- package/Sidebar/index.js +0 -1
- package/Skeleton/Skeleton.d.ts +0 -4
- package/Skeleton/Skeleton.js +0 -7
- package/Slider/Slider.d.ts +0 -6
- package/Slider/Slider.js +0 -7
- package/Spinner/Spinner.d.ts +0 -19
- package/Spinner/Spinner.js +0 -31
- package/Switch/Switch.d.ts +0 -12
- package/Switch/Switch.js +0 -30
- package/Table/Table.d.ts +0 -10
- package/Table/Table.js +0 -20
- package/Tabs/Tabs.d.ts +0 -15
- package/Tabs/Tabs.js +0 -24
- package/Textarea/Textarea.d.ts +0 -19
- package/Textarea/Textarea.js +0 -31
- package/Toast/Toast.d.ts +0 -12
- package/Toast/Toast.js +0 -25
- package/Tooltip/Tooltip.d.ts +0 -17
- package/Tooltip/Tooltip.js +0 -29
- package/Typography/Typography.d.ts +0 -20
- package/Typography/Typography.js +0 -43
- package/helpers.d.ts +0 -4
- package/helpers.js +0 -5
- package/icons.d.ts +0 -1
- package/index.d.ts +0 -42
- package/index.js +0 -45
- package/lib/typography-types.d.ts +0 -4
- package/lib/typography-types.js +0 -90
- package/lib/utils.d.ts +0 -3
- package/lib/utils.js +0 -14
- package/tmpclaude-2407-cwd +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
+
import { cva } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "ghost-muted" | "destructive"
|
|
8
|
+
type ButtonSize = "mini" | "small" | "regular" | "large"
|
|
9
|
+
type ButtonRoundness = "default" | "round"
|
|
10
|
+
type ButtonTextVariant = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "subtitle1" | "subtitle2" | "body1" | "body2" | "body3" | "caption"
|
|
11
|
+
|
|
12
|
+
const buttonVariants = cva(
|
|
13
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap font-normal font-sans cursor-pointer transition-colors focus-visible:outline-none focus-visible:shadow-[0_0_0_3px_#d4d4d4] disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
variant: {
|
|
17
|
+
primary:
|
|
18
|
+
"bg-accent-600 text-white hover:bg-accent-700",
|
|
19
|
+
secondary:
|
|
20
|
+
"bg-main-100 text-main-950 hover:bg-main-200",
|
|
21
|
+
outline:
|
|
22
|
+
"border border-main-300 bg-transparent text-main-950 hover:bg-main-100",
|
|
23
|
+
ghost:
|
|
24
|
+
"bg-transparent text-main-600 hover:bg-main-100 hover:text-main-950",
|
|
25
|
+
"ghost-muted":
|
|
26
|
+
"bg-transparent text-main-400 hover:bg-main-100 hover:text-main-600",
|
|
27
|
+
destructive:
|
|
28
|
+
"bg-error-600 text-white hover:bg-error-700",
|
|
29
|
+
},
|
|
30
|
+
size: {
|
|
31
|
+
mini: "h-6 px-3 text-xs rounded-md [&_svg]:size-3",
|
|
32
|
+
small: "h-8 px-3 text-sm rounded-md [&_svg]:size-3.5",
|
|
33
|
+
regular: "h-9 px-4 text-sm rounded-lg [&_svg]:size-4",
|
|
34
|
+
large: "h-10 px-4 text-base rounded-lg [&_svg]:size-5",
|
|
35
|
+
},
|
|
36
|
+
roundness: {
|
|
37
|
+
default: "",
|
|
38
|
+
round: "!rounded-full",
|
|
39
|
+
},
|
|
40
|
+
textVariant: {
|
|
41
|
+
h1: "text-[64px] font-extrabold",
|
|
42
|
+
h2: "text-[48px] font-bold",
|
|
43
|
+
h3: "text-[32px] font-bold",
|
|
44
|
+
h4: "text-[24px] font-bold",
|
|
45
|
+
h5: "text-[20px] font-bold",
|
|
46
|
+
h6: "text-[20px] font-semibold",
|
|
47
|
+
subtitle1: "text-[20px] font-semibold",
|
|
48
|
+
subtitle2: "text-[18px] font-semibold",
|
|
49
|
+
body1: "text-[16px] font-normal",
|
|
50
|
+
body2: "text-[14px] font-normal",
|
|
51
|
+
body3: "text-[12px] font-normal",
|
|
52
|
+
caption: "text-[12px] font-light",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
defaultVariants: {
|
|
56
|
+
variant: "primary",
|
|
57
|
+
size: "regular",
|
|
58
|
+
roundness: "default",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
export interface ButtonProps
|
|
64
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
65
|
+
variant?: ButtonVariant
|
|
66
|
+
size?: ButtonSize
|
|
67
|
+
roundness?: ButtonRoundness
|
|
68
|
+
textVariant?: ButtonTextVariant
|
|
69
|
+
asChild?: boolean
|
|
70
|
+
leftIcon?: React.ReactNode
|
|
71
|
+
rightIcon?: React.ReactNode
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
75
|
+
(
|
|
76
|
+
{
|
|
77
|
+
className,
|
|
78
|
+
variant,
|
|
79
|
+
size,
|
|
80
|
+
roundness,
|
|
81
|
+
textVariant,
|
|
82
|
+
asChild = false,
|
|
83
|
+
leftIcon,
|
|
84
|
+
rightIcon,
|
|
85
|
+
children,
|
|
86
|
+
...props
|
|
87
|
+
},
|
|
88
|
+
ref
|
|
89
|
+
) => {
|
|
90
|
+
const Comp = asChild ? Slot : "button"
|
|
91
|
+
return (
|
|
92
|
+
<Comp
|
|
93
|
+
className={cn(buttonVariants({ variant, size, roundness, textVariant, className }))}
|
|
94
|
+
ref={ref}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
{leftIcon && <span className="shrink-0">{leftIcon}</span>}
|
|
98
|
+
{children}
|
|
99
|
+
{rightIcon && <span className="shrink-0">{rightIcon}</span>}
|
|
100
|
+
</Comp>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
Button.displayName = "Button"
|
|
105
|
+
|
|
106
|
+
export { Button, buttonVariants }
|
|
107
|
+
export type { ButtonVariant, ButtonSize, ButtonRoundness, ButtonTextVariant }
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import type { DateRange } from "react-day-picker"
|
|
4
|
+
import { Calendar } from "./Calendar"
|
|
5
|
+
import { Flex } from "../Flex/Flex"
|
|
6
|
+
import { Typography } from "../Typography/Typography"
|
|
7
|
+
import { Button } from "../Button/Button"
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof Calendar> = {
|
|
10
|
+
title: "Components/Calendar",
|
|
11
|
+
component: Calendar,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: "centered",
|
|
14
|
+
},
|
|
15
|
+
tags: ["autodocs"],
|
|
16
|
+
argTypes: {
|
|
17
|
+
mode: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: ["single", "multiple", "range"],
|
|
20
|
+
description: "Selection mode for the calendar",
|
|
21
|
+
},
|
|
22
|
+
showOutsideDays: {
|
|
23
|
+
control: "boolean",
|
|
24
|
+
description: "Show days from adjacent months",
|
|
25
|
+
},
|
|
26
|
+
showWeekNumber: {
|
|
27
|
+
control: "boolean",
|
|
28
|
+
description: "Show week numbers",
|
|
29
|
+
},
|
|
30
|
+
captionLayout: {
|
|
31
|
+
control: "select",
|
|
32
|
+
options: ["label", "dropdown", "dropdown-months", "dropdown-years"],
|
|
33
|
+
description: "Layout of the caption (month/year selector)",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default meta
|
|
39
|
+
type Story = StoryObj<typeof Calendar>
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {
|
|
42
|
+
render: () => {
|
|
43
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
44
|
+
return (
|
|
45
|
+
<Calendar
|
|
46
|
+
mode="single"
|
|
47
|
+
selected={date}
|
|
48
|
+
onSelect={setDate}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const SingleSelection: Story = {
|
|
55
|
+
render: () => {
|
|
56
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
57
|
+
return (
|
|
58
|
+
<Flex gap={4} align="center">
|
|
59
|
+
<Calendar
|
|
60
|
+
mode="single"
|
|
61
|
+
selected={date}
|
|
62
|
+
onSelect={setDate}
|
|
63
|
+
/>
|
|
64
|
+
<Typography variant="body2" color="main-600">
|
|
65
|
+
Selected: {date ? date.toLocaleDateString() : "None"}
|
|
66
|
+
</Typography>
|
|
67
|
+
</Flex>
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const MultipleSelection: Story = {
|
|
73
|
+
render: () => {
|
|
74
|
+
const [dates, setDates] = React.useState<Date[] | undefined>([])
|
|
75
|
+
return (
|
|
76
|
+
<Flex gap={4} align="center">
|
|
77
|
+
<Calendar
|
|
78
|
+
mode="multiple"
|
|
79
|
+
selected={dates}
|
|
80
|
+
onSelect={setDates}
|
|
81
|
+
/>
|
|
82
|
+
<Typography variant="body2" color="main-600">
|
|
83
|
+
Selected: {dates && dates.length > 0
|
|
84
|
+
? dates.map(d => d.toLocaleDateString()).join(", ")
|
|
85
|
+
: "None"}
|
|
86
|
+
</Typography>
|
|
87
|
+
</Flex>
|
|
88
|
+
)
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const RangeSelection: Story = {
|
|
93
|
+
render: () => {
|
|
94
|
+
const [range, setRange] = React.useState<DateRange | undefined>()
|
|
95
|
+
return (
|
|
96
|
+
<Flex gap={4} align="center">
|
|
97
|
+
<Calendar
|
|
98
|
+
mode="range"
|
|
99
|
+
selected={range}
|
|
100
|
+
onSelect={setRange}
|
|
101
|
+
numberOfMonths={2}
|
|
102
|
+
/>
|
|
103
|
+
<Typography variant="body2" color="main-600">
|
|
104
|
+
{range?.from && range?.to
|
|
105
|
+
? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}`
|
|
106
|
+
: range?.from
|
|
107
|
+
? `${range.from.toLocaleDateString()} - Select end date`
|
|
108
|
+
: "Select a date range"}
|
|
109
|
+
</Typography>
|
|
110
|
+
</Flex>
|
|
111
|
+
)
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const WithWeekNumbers: Story = {
|
|
116
|
+
render: () => {
|
|
117
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
118
|
+
return (
|
|
119
|
+
<Calendar
|
|
120
|
+
mode="single"
|
|
121
|
+
selected={date}
|
|
122
|
+
onSelect={setDate}
|
|
123
|
+
showWeekNumber
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const WithDropdowns: Story = {
|
|
130
|
+
render: () => {
|
|
131
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
132
|
+
return (
|
|
133
|
+
<Calendar
|
|
134
|
+
mode="single"
|
|
135
|
+
selected={date}
|
|
136
|
+
onSelect={setDate}
|
|
137
|
+
captionLayout="dropdown"
|
|
138
|
+
fromYear={2020}
|
|
139
|
+
toYear={2030}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const DisabledDates: Story = {
|
|
146
|
+
render: () => {
|
|
147
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
148
|
+
|
|
149
|
+
// Disable weekends
|
|
150
|
+
const disabledDays = [
|
|
151
|
+
{ dayOfWeek: [0, 6] }, // Sunday and Saturday
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Flex gap={4} align="center">
|
|
156
|
+
<Typography variant="body2" color="main-600">
|
|
157
|
+
Weekends are disabled
|
|
158
|
+
</Typography>
|
|
159
|
+
<Calendar
|
|
160
|
+
mode="single"
|
|
161
|
+
selected={date}
|
|
162
|
+
onSelect={setDate}
|
|
163
|
+
disabled={disabledDays}
|
|
164
|
+
/>
|
|
165
|
+
</Flex>
|
|
166
|
+
)
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const LimitedDateRange: Story = {
|
|
171
|
+
render: () => {
|
|
172
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
173
|
+
|
|
174
|
+
// Only allow dates in the next 30 days
|
|
175
|
+
const today = new Date()
|
|
176
|
+
const thirtyDaysFromNow = new Date()
|
|
177
|
+
thirtyDaysFromNow.setDate(today.getDate() + 30)
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Flex gap={4} align="center">
|
|
181
|
+
<Typography variant="body2" color="main-600">
|
|
182
|
+
Only next 30 days selectable
|
|
183
|
+
</Typography>
|
|
184
|
+
<Calendar
|
|
185
|
+
mode="single"
|
|
186
|
+
selected={date}
|
|
187
|
+
onSelect={setDate}
|
|
188
|
+
fromDate={today}
|
|
189
|
+
toDate={thirtyDaysFromNow}
|
|
190
|
+
/>
|
|
191
|
+
</Flex>
|
|
192
|
+
)
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const TwoMonths: Story = {
|
|
197
|
+
render: () => {
|
|
198
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
199
|
+
return (
|
|
200
|
+
<Calendar
|
|
201
|
+
mode="single"
|
|
202
|
+
selected={date}
|
|
203
|
+
onSelect={setDate}
|
|
204
|
+
numberOfMonths={2}
|
|
205
|
+
/>
|
|
206
|
+
)
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const Error: Story = {
|
|
211
|
+
render: () => {
|
|
212
|
+
const [date, setDate] = React.useState<Date | undefined>()
|
|
213
|
+
return (
|
|
214
|
+
<Calendar
|
|
215
|
+
mode="single"
|
|
216
|
+
selected={date}
|
|
217
|
+
onSelect={setDate}
|
|
218
|
+
error="Please select a valid date"
|
|
219
|
+
/>
|
|
220
|
+
)
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const ErrorWithSelection: Story = {
|
|
225
|
+
render: () => {
|
|
226
|
+
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
227
|
+
// Simulate an invalid selection (e.g., past date when only future dates are allowed)
|
|
228
|
+
return (
|
|
229
|
+
<Flex gap={4} align="center">
|
|
230
|
+
<Calendar
|
|
231
|
+
mode="single"
|
|
232
|
+
selected={date}
|
|
233
|
+
onSelect={setDate}
|
|
234
|
+
error="Selected date cannot be in the past"
|
|
235
|
+
/>
|
|
236
|
+
<Typography variant="body2" color="error-600">
|
|
237
|
+
Please select a future date
|
|
238
|
+
</Typography>
|
|
239
|
+
</Flex>
|
|
240
|
+
)
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const FormikTouchedUntouched: Story = {
|
|
245
|
+
render: function FormikExample() {
|
|
246
|
+
const [date, setDate] = React.useState<Date | undefined>()
|
|
247
|
+
const [touched, setTouched] = React.useState(false)
|
|
248
|
+
const error = "Please select a date"
|
|
249
|
+
|
|
250
|
+
const handleSelect = (selectedDate: Date | undefined) => {
|
|
251
|
+
setDate(selectedDate)
|
|
252
|
+
setTouched(true)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<Flex gap={6} align="center">
|
|
257
|
+
<Flex gap={2}>
|
|
258
|
+
<Typography variant="subtitle2" color="main-700">
|
|
259
|
+
Formik-style touched/untouched validation
|
|
260
|
+
</Typography>
|
|
261
|
+
<Typography variant="caption" color="grey-500">
|
|
262
|
+
Error message only shows after interaction with calendar
|
|
263
|
+
</Typography>
|
|
264
|
+
</Flex>
|
|
265
|
+
|
|
266
|
+
<Calendar
|
|
267
|
+
mode="single"
|
|
268
|
+
selected={date}
|
|
269
|
+
onSelect={handleSelect}
|
|
270
|
+
error={touched && !date ? error : undefined}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
<Flex gap={2} align="center">
|
|
274
|
+
<Typography variant="caption" color="grey-400">
|
|
275
|
+
{touched ? "Touched" : "Untouched"} | Selected: {date ? date.toLocaleDateString() : "none"}
|
|
276
|
+
</Typography>
|
|
277
|
+
<Button
|
|
278
|
+
variant="secondary"
|
|
279
|
+
size="small"
|
|
280
|
+
onClick={() => {
|
|
281
|
+
setTouched(false)
|
|
282
|
+
setDate(undefined)
|
|
283
|
+
}}
|
|
284
|
+
>
|
|
285
|
+
Reset
|
|
286
|
+
</Button>
|
|
287
|
+
</Flex>
|
|
288
|
+
</Flex>
|
|
289
|
+
)
|
|
290
|
+
},
|
|
291
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
ChevronDownIcon,
|
|
6
|
+
ChevronLeftIcon,
|
|
7
|
+
ChevronRightIcon,
|
|
8
|
+
} from "lucide-react"
|
|
9
|
+
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
|
10
|
+
|
|
11
|
+
import { cn } from "../lib/utils"
|
|
12
|
+
import { Button, buttonVariants } from "../Button/Button"
|
|
13
|
+
import { Typography } from "../Typography/Typography"
|
|
14
|
+
|
|
15
|
+
export type CalendarProps = React.ComponentProps<typeof DayPicker> & {
|
|
16
|
+
/**
|
|
17
|
+
* Variant for navigation buttons
|
|
18
|
+
* @default "ghost"
|
|
19
|
+
*/
|
|
20
|
+
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
|
21
|
+
/**
|
|
22
|
+
* Error message to display below the calendar
|
|
23
|
+
*/
|
|
24
|
+
error?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Calendar({
|
|
28
|
+
className,
|
|
29
|
+
classNames,
|
|
30
|
+
showOutsideDays = true,
|
|
31
|
+
captionLayout = "label",
|
|
32
|
+
buttonVariant = "ghost",
|
|
33
|
+
formatters,
|
|
34
|
+
components,
|
|
35
|
+
error,
|
|
36
|
+
...props
|
|
37
|
+
}: CalendarProps) {
|
|
38
|
+
const defaultClassNames = getDefaultClassNames()
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex flex-col w-fit">
|
|
42
|
+
<DayPicker
|
|
43
|
+
showOutsideDays={showOutsideDays}
|
|
44
|
+
className={cn(
|
|
45
|
+
"bg-white group/calendar p-3 rounded-lg border",
|
|
46
|
+
error ? "border-error-500" : "border-grey-200",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
captionLayout={captionLayout}
|
|
50
|
+
formatters={{
|
|
51
|
+
formatMonthDropdown: (date) =>
|
|
52
|
+
date.toLocaleString("default", { month: "short" }),
|
|
53
|
+
...formatters,
|
|
54
|
+
}}
|
|
55
|
+
classNames={{
|
|
56
|
+
root: cn("w-fit", defaultClassNames.root),
|
|
57
|
+
months: cn(
|
|
58
|
+
"flex gap-4 flex-col md:flex-row relative",
|
|
59
|
+
defaultClassNames.months
|
|
60
|
+
),
|
|
61
|
+
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
|
|
62
|
+
nav: cn(
|
|
63
|
+
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
|
|
64
|
+
defaultClassNames.nav
|
|
65
|
+
),
|
|
66
|
+
button_previous: cn(
|
|
67
|
+
buttonVariants({ variant: buttonVariant, size: "small" }),
|
|
68
|
+
"size-8 aria-disabled:opacity-50 p-0 select-none",
|
|
69
|
+
defaultClassNames.button_previous
|
|
70
|
+
),
|
|
71
|
+
button_next: cn(
|
|
72
|
+
buttonVariants({ variant: buttonVariant, size: "small" }),
|
|
73
|
+
"size-8 aria-disabled:opacity-50 p-0 select-none",
|
|
74
|
+
defaultClassNames.button_next
|
|
75
|
+
),
|
|
76
|
+
month_caption: cn(
|
|
77
|
+
"flex items-center justify-center h-8 w-full px-8",
|
|
78
|
+
defaultClassNames.month_caption
|
|
79
|
+
),
|
|
80
|
+
dropdowns: cn(
|
|
81
|
+
"w-full flex items-center text-sm font-medium justify-center h-8 gap-1.5",
|
|
82
|
+
defaultClassNames.dropdowns
|
|
83
|
+
),
|
|
84
|
+
dropdown_root: cn(
|
|
85
|
+
"relative has-focus:border-grey-400 border border-grey-300 shadow-xs has-focus:ring-grey-300/50 has-focus:ring-[3px] rounded-md",
|
|
86
|
+
defaultClassNames.dropdown_root
|
|
87
|
+
),
|
|
88
|
+
dropdown: cn(
|
|
89
|
+
"absolute bg-white inset-0 opacity-0",
|
|
90
|
+
defaultClassNames.dropdown
|
|
91
|
+
),
|
|
92
|
+
caption_label: cn(
|
|
93
|
+
"select-none font-medium text-main-900",
|
|
94
|
+
captionLayout === "label"
|
|
95
|
+
? "text-sm"
|
|
96
|
+
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-main-400 [&>svg]:size-3.5",
|
|
97
|
+
defaultClassNames.caption_label
|
|
98
|
+
),
|
|
99
|
+
table: "w-full border-collapse",
|
|
100
|
+
weekdays: cn("flex", defaultClassNames.weekdays),
|
|
101
|
+
weekday: cn(
|
|
102
|
+
"text-main-500 rounded-md flex-1 font-normal text-[0.8rem] select-none w-8 h-8 flex items-center justify-center",
|
|
103
|
+
defaultClassNames.weekday
|
|
104
|
+
),
|
|
105
|
+
week: cn("flex w-full mt-2", defaultClassNames.week),
|
|
106
|
+
week_number_header: cn(
|
|
107
|
+
"select-none w-8",
|
|
108
|
+
defaultClassNames.week_number_header
|
|
109
|
+
),
|
|
110
|
+
week_number: cn(
|
|
111
|
+
"text-[0.8rem] select-none text-main-400",
|
|
112
|
+
defaultClassNames.week_number
|
|
113
|
+
),
|
|
114
|
+
day: cn(
|
|
115
|
+
"relative w-8 h-8 p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day select-none",
|
|
116
|
+
props.showWeekNumber
|
|
117
|
+
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
|
118
|
+
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
|
119
|
+
defaultClassNames.day
|
|
120
|
+
),
|
|
121
|
+
range_start: cn(
|
|
122
|
+
"rounded-l-md bg-main-100",
|
|
123
|
+
defaultClassNames.range_start
|
|
124
|
+
),
|
|
125
|
+
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
|
126
|
+
range_end: cn("rounded-r-md bg-main-100", defaultClassNames.range_end),
|
|
127
|
+
today: cn(
|
|
128
|
+
"bg-main-100 text-main-900 rounded-md data-[selected=true]:rounded-none",
|
|
129
|
+
defaultClassNames.today
|
|
130
|
+
),
|
|
131
|
+
outside: cn(
|
|
132
|
+
"text-main-300 aria-selected:text-main-400",
|
|
133
|
+
defaultClassNames.outside
|
|
134
|
+
),
|
|
135
|
+
disabled: cn(
|
|
136
|
+
"text-main-300 opacity-50",
|
|
137
|
+
defaultClassNames.disabled
|
|
138
|
+
),
|
|
139
|
+
hidden: cn("invisible", defaultClassNames.hidden),
|
|
140
|
+
...classNames,
|
|
141
|
+
}}
|
|
142
|
+
components={{
|
|
143
|
+
Root: ({ className, rootRef, ...props }) => {
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
data-slot="calendar"
|
|
147
|
+
ref={rootRef}
|
|
148
|
+
className={cn(className)}
|
|
149
|
+
{...props}
|
|
150
|
+
/>
|
|
151
|
+
)
|
|
152
|
+
},
|
|
153
|
+
Chevron: ({ className, orientation, ...props }) => {
|
|
154
|
+
if (orientation === "left") {
|
|
155
|
+
return (
|
|
156
|
+
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (orientation === "right") {
|
|
161
|
+
return (
|
|
162
|
+
<ChevronRightIcon
|
|
163
|
+
className={cn("size-4", className)}
|
|
164
|
+
{...props}
|
|
165
|
+
/>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
|
171
|
+
)
|
|
172
|
+
},
|
|
173
|
+
DayButton: CalendarDayButton,
|
|
174
|
+
WeekNumber: ({ children, ...props }) => {
|
|
175
|
+
return (
|
|
176
|
+
<td {...props}>
|
|
177
|
+
<div className="flex size-8 items-center justify-center text-center">
|
|
178
|
+
{children}
|
|
179
|
+
</div>
|
|
180
|
+
</td>
|
|
181
|
+
)
|
|
182
|
+
},
|
|
183
|
+
...components,
|
|
184
|
+
}}
|
|
185
|
+
{...props}
|
|
186
|
+
/>
|
|
187
|
+
{error && (
|
|
188
|
+
<Typography variant="caption" color="error-600" className="mt-1">
|
|
189
|
+
{error}
|
|
190
|
+
</Typography>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function CalendarDayButton({
|
|
197
|
+
className,
|
|
198
|
+
day,
|
|
199
|
+
modifiers,
|
|
200
|
+
...props
|
|
201
|
+
}: React.ComponentProps<typeof DayButton>) {
|
|
202
|
+
const defaultClassNames = getDefaultClassNames()
|
|
203
|
+
|
|
204
|
+
const ref = React.useRef<HTMLButtonElement>(null)
|
|
205
|
+
React.useEffect(() => {
|
|
206
|
+
if (modifiers.focused) ref.current?.focus()
|
|
207
|
+
}, [modifiers.focused])
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<Button
|
|
211
|
+
ref={ref}
|
|
212
|
+
variant="ghost"
|
|
213
|
+
size="small"
|
|
214
|
+
data-day={day.date.toLocaleDateString()}
|
|
215
|
+
data-selected-single={
|
|
216
|
+
modifiers.selected &&
|
|
217
|
+
!modifiers.range_start &&
|
|
218
|
+
!modifiers.range_end &&
|
|
219
|
+
!modifiers.range_middle
|
|
220
|
+
}
|
|
221
|
+
data-range-start={modifiers.range_start}
|
|
222
|
+
data-range-end={modifiers.range_end}
|
|
223
|
+
data-range-middle={modifiers.range_middle}
|
|
224
|
+
className={cn(
|
|
225
|
+
"size-8 p-0 font-normal",
|
|
226
|
+
"data-[selected-single=true]:bg-main-900 data-[selected-single=true]:text-white",
|
|
227
|
+
"data-[range-middle=true]:bg-main-100 data-[range-middle=true]:text-main-900",
|
|
228
|
+
"data-[range-start=true]:bg-main-900 data-[range-start=true]:text-white",
|
|
229
|
+
"data-[range-end=true]:bg-main-900 data-[range-end=true]:text-white",
|
|
230
|
+
"group-data-[focused=true]/day:border-grey-400 group-data-[focused=true]/day:ring-grey-300/50",
|
|
231
|
+
"hover:bg-main-100 hover:text-main-900",
|
|
232
|
+
"group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px]",
|
|
233
|
+
"data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md",
|
|
234
|
+
"data-[range-middle=true]:rounded-none",
|
|
235
|
+
"data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md",
|
|
236
|
+
defaultClassNames.day,
|
|
237
|
+
className
|
|
238
|
+
)}
|
|
239
|
+
{...props}
|
|
240
|
+
/>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
Calendar.displayName = "Calendar"
|
|
245
|
+
|
|
246
|
+
export { Calendar, CalendarDayButton }
|