├── .eslintrc.json ├── .github └── assets │ └── example.png ├── .gitignore ├── LICENSE.md ├── README.md ├── bun.lockb ├── components.json ├── content └── snippets │ ├── date-picker-form.mdx │ ├── date-picker-range-form.mdx │ ├── date-picker-range.mdx │ ├── date-picker.mdx │ ├── date-time-24h-picker-form.mdx │ ├── date-time-24h-picker.mdx │ ├── date-time-picker-form.mdx │ └── date-time-picker.mdx ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── public ├── favicon.png └── og.png ├── src ├── app │ ├── error.tsx │ ├── layout.tsx │ ├── not-found.tsx │ └── page.tsx ├── components │ ├── copy-button.tsx │ ├── date-n-time │ │ ├── date-picker-range.tsx │ │ ├── date-picker.tsx │ │ ├── date-time-picker-24h.tsx │ │ ├── date-time-picker.tsx │ │ ├── form │ │ │ ├── date-picker-form.tsx │ │ │ ├── date-picker-range-form.tsx │ │ │ ├── date-time-picker-24h-form.tsx │ │ │ ├── date-time-picker-form.tsx │ │ │ └── index.ts │ │ └── index.ts │ ├── examples │ │ ├── date-time-picker-examples.tsx │ │ ├── date-time-picker-form-examples.tsx │ │ └── wrapper.tsx │ ├── external-link.tsx │ ├── mdx │ │ ├── callout.tsx │ │ ├── codeblock.tsx │ │ ├── components.tsx │ │ └── mdx-content-renderer.tsx │ ├── root-provider.tsx │ ├── socials.tsx │ ├── theme │ │ ├── provider.tsx │ │ └── toggler.tsx │ └── ui │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── drawer.tsx │ │ ├── form.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ └── sonner.tsx ├── config │ └── site.config.ts ├── lib │ └── utils.ts ├── styles │ └── globals.css └── types │ └── index.d.ts ├── tailwind.config.ts ├── tsconfig.json └── velite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "react/no-unescaped-entities": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudrodip/shadcn-date-time-picker/102715d1a665aca584507593896be293b393adc9/.github/assets/example.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .velite 39 | public/static -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright 2024 rds_agi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShadCN Date Time Picker 2 | 3 | ![Project Overview](./public/og.png) 4 | 5 | ## Overview 6 | 7 | The ShadCN Date Time Picker project features a range of Date and Time picker components built with ShadCN. These examples highlight the components' versatility and functionality across various use cases. 8 | 9 | ## How to Use 10 | 11 | To get started, visit [time.rdsx.dev](https://time.rdsx.dev). You can click the `copy` button to copy the code or the `view code` button to see the code in detail. 12 | 13 | ![Example](.github/assets/example.png) 14 | 15 | ## Examples 16 | 17 | Check out the live examples of our Date and Time picker components: 18 | 19 | - [Date Picker](https://time.rdsx.dev) 20 | - [Date Picker Range](https://time.rdsx.dev) 21 | - [Date-Time Picker (12h)](https://time.rdsx.dev) 22 | - [Date-Time Picker](https://time.rdsx.dev) 23 | 24 | See how these components work with `react-hook-form` and `zod`: 25 | 26 | - [Date Picker Form](https://time.rdsx.dev) 27 | - [Date Picker Range Form](https://time.rdsx.dev) 28 | - [Date-Time Picker (12h) Form](https://time.rdsx.dev) 29 | - [Date-Time Picker Form](https://time.rdsx.dev) 30 | 31 | ## Contributing 32 | 33 | We welcome contributions! Follow the local setup instructions below to get started. 34 | 35 | > [!NOTE] 36 | > You can use your favorite JavaScript runtime and package manager. This project uses bun. 37 | 38 | 1. **Clone the repository:** 39 | 40 | ```sh 41 | git clone https://github.com/your-username/shadcn-date-time-picker.git 42 | cd shadcn-date-time-picker 43 | ``` 44 | 45 | 2. **Install dependencies:** 46 | 47 | ```sh 48 | bun install 49 | ``` 50 | 51 | 3. **Run the development server:** 52 | 53 | ```sh 54 | bun run dev 55 | ``` 56 | 57 | 4. **Open your browser:** 58 | 59 | Go to `http://localhost:3000` to see the project in action. 60 | 61 | You're all set! 62 | 63 | ## License 64 | 65 | This project is licensed under the [MIT License](./LICENSE.md). 66 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudrodip/shadcn-date-time-picker/102715d1a665aca584507593896be293b393adc9/bun.lockb -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /content/snippets/date-picker-form.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date picker form 3 | description: A date picker component made with shadcn components 4 | code: | 5 | "use client"; 6 | 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { CalendarIcon } from "@radix-ui/react-icons"; 9 | import { format } from "date-fns"; 10 | import { useForm } from "react-hook-form"; 11 | import { z } from "zod"; 12 | 13 | import { cn } from "@/lib/utils"; 14 | import { Button } from "@/components/ui/button"; 15 | import { Calendar } from "@/components/ui/calendar"; 16 | import { 17 | Form, 18 | FormControl, 19 | FormDescription, 20 | FormField, 21 | FormItem, 22 | FormLabel, 23 | FormMessage, 24 | } from "@/components/ui/form"; 25 | import { 26 | Popover, 27 | PopoverContent, 28 | PopoverTrigger, 29 | } from "@/components/ui/popover"; 30 | import { toast } from "sonner"; 31 | 32 | const FormSchema = z.object({ 33 | dob: z.date({ 34 | required_error: "A date of birth is required.", 35 | }), 36 | }); 37 | 38 | export function DatePickerForm() { 39 | const form = useForm>({ 40 | resolver: zodResolver(FormSchema), 41 | }); 42 | 43 | function onSubmit(data: z.infer) { 44 | toast.success(`Selected date of birth: ${format(data.dob, "PPP")}`); 45 | } 46 | 47 | return ( 48 |
49 | 50 | ( 54 | 55 | Date of birth 56 | 57 | 58 | 59 | 73 | 74 | 75 | 76 | 81 | date > new Date() || date < new Date("1900-01-01") 82 | } 83 | initialFocus 84 | /> 85 | 86 | 87 | 88 | provided by shadcn 89 | 90 | 91 | 92 | )} 93 | /> 94 | 95 | 96 | 97 | ); 98 | } 99 | --- 100 | 101 | ```tsx 102 | "use client"; 103 | 104 | import { zodResolver } from "@hookform/resolvers/zod"; 105 | import { CalendarIcon } from "@radix-ui/react-icons"; 106 | import { format } from "date-fns"; 107 | import { useForm } from "react-hook-form"; 108 | import { z } from "zod"; 109 | 110 | import { cn } from "@/lib/utils"; 111 | import { Button } from "@/components/ui/button"; 112 | import { Calendar } from "@/components/ui/calendar"; 113 | import { 114 | Form, 115 | FormControl, 116 | FormDescription, 117 | FormField, 118 | FormItem, 119 | FormLabel, 120 | FormMessage, 121 | } from "@/components/ui/form"; 122 | import { 123 | Popover, 124 | PopoverContent, 125 | PopoverTrigger, 126 | } from "@/components/ui/popover"; 127 | import { toast } from "sonner"; 128 | 129 | const FormSchema = z.object({ 130 | dob: z.date({ 131 | required_error: "A date of birth is required.", 132 | }), 133 | }); 134 | 135 | export function DatePickerForm() { 136 | const form = useForm>({ 137 | resolver: zodResolver(FormSchema), 138 | }); 139 | 140 | function onSubmit(data: z.infer) { 141 | toast.success(`Selected date of birth: ${format(data.dob, "PPP")}`); 142 | } 143 | 144 | return ( 145 |
146 | 147 | ( 151 | 152 | Date of birth 153 | 154 | 155 | 156 | 170 | 171 | 172 | 173 | 178 | date > new Date() || date < new Date("1900-01-01") 179 | } 180 | initialFocus 181 | /> 182 | 183 | 184 | 185 | provided by shadcn 186 | 187 | 188 | 189 | )} 190 | /> 191 | 192 | 193 | 194 | ); 195 | } 196 | ``` -------------------------------------------------------------------------------- /content/snippets/date-picker-range-form.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date picker with range form 3 | description: A date range picker component made with shadcn components 4 | code: | 5 | "use client"; 6 | 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { CalendarIcon } from "@radix-ui/react-icons"; 9 | import { format } from "date-fns"; 10 | import { useForm } from "react-hook-form"; 11 | import { z } from "zod"; 12 | 13 | import { cn } from "@/lib/utils"; 14 | import { Button } from "@/components/ui/button"; 15 | import { Calendar } from "@/components/ui/calendar"; 16 | import { 17 | Form, 18 | FormControl, 19 | FormDescription, 20 | FormField, 21 | FormItem, 22 | FormLabel, 23 | FormMessage, 24 | } from "@/components/ui/form"; 25 | import { 26 | Popover, 27 | PopoverContent, 28 | PopoverTrigger, 29 | } from "@/components/ui/popover"; 30 | import { toast } from "sonner"; 31 | 32 | const FormSchema = z.object({ 33 | dateRange: z.object({ 34 | from: z.date({ 35 | required_error: "A start date is required.", 36 | }), 37 | to: z.date({ 38 | required_error: "An end date is required.", 39 | }), 40 | }), 41 | }); 42 | 43 | export function DatePickerWithRangeForm() { 44 | const form = useForm>({ 45 | resolver: zodResolver(FormSchema), 46 | }); 47 | 48 | function onSubmit(data: z.infer) { 49 | toast.success(`Selected date range: From ${format(data.dateRange.from, "PPP")} to ${format(data.dateRange.to, "PPP")}`); 50 | } 51 | 52 | return ( 53 |
54 | 55 | ( 59 | 60 | Select date range 61 | 62 | 63 | 64 | 86 | 87 | 88 | 89 | 97 | 98 | 99 | 100 | Select a date range for your event. 101 | 102 | 103 | 104 | )} 105 | /> 106 | 107 | 108 | 109 | ); 110 | } 111 | --- 112 | 113 | ```tsx 114 | "use client"; 115 | 116 | import { zodResolver } from "@hookform/resolvers/zod"; 117 | import { CalendarIcon } from "@radix-ui/react-icons"; 118 | import { format } from "date-fns"; 119 | import { useForm } from "react-hook-form"; 120 | import { z } from "zod"; 121 | 122 | import { cn } from "@/lib/utils"; 123 | import { Button } from "@/components/ui/button"; 124 | import { Calendar } from "@/components/ui/calendar"; 125 | import { 126 | Form, 127 | FormControl, 128 | FormDescription, 129 | FormField, 130 | FormItem, 131 | FormLabel, 132 | FormMessage, 133 | } from "@/components/ui/form"; 134 | import { 135 | Popover, 136 | PopoverContent, 137 | PopoverTrigger, 138 | } from "@/components/ui/popover"; 139 | import { toast } from "sonner"; 140 | 141 | const FormSchema = z.object({ 142 | dateRange: z.object({ 143 | from: z.date({ 144 | required_error: "A start date is required.", 145 | }), 146 | to: z.date({ 147 | required_error: "An end date is required.", 148 | }), 149 | }), 150 | }); 151 | 152 | export function DatePickerWithRangeForm() { 153 | const form = useForm>({ 154 | resolver: zodResolver(FormSchema), 155 | }); 156 | 157 | function onSubmit(data: z.infer) { 158 | toast.success(`Selected date range: From ${format(data.dateRange.from, "PPP")} to ${format(data.dateRange.to, "PPP")}`); 159 | } 160 | 161 | return ( 162 |
163 | 164 | ( 168 | 169 | Select date range 170 | 171 | 172 | 173 | 195 | 196 | 197 | 198 | 206 | 207 | 208 | 209 | Select a date range for your event. 210 | 211 | 212 | 213 | )} 214 | /> 215 | 216 | 217 | 218 | ); 219 | } 220 | ``` -------------------------------------------------------------------------------- /content/snippets/date-picker-range.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date picker with range 3 | description: A date range picker component made with shadcn components 4 | code: | 5 | "use client" 6 | 7 | import * as React from "react" 8 | import { CalendarIcon } from "@radix-ui/react-icons" 9 | import { addDays, format } from "date-fns" 10 | import { DateRange } from "react-day-picker" 11 | 12 | import { cn } from "@/lib/utils" 13 | import { Button } from "@/components/ui/button" 14 | import { Calendar } from "@/components/ui/calendar" 15 | import { 16 | Popover, 17 | PopoverContent, 18 | PopoverTrigger, 19 | } from "@/components/ui/popover" 20 | 21 | export function DatePickerWithRange({ 22 | className, 23 | }: React.HTMLAttributes) { 24 | const [date, setDate] = React.useState({ 25 | from: new Date(), 26 | to: addDays(new Date(), 20), 27 | }) 28 | 29 | return ( 30 |
31 | 32 | 33 | 55 | 56 | 57 | 65 | 66 | 67 |
68 | ) 69 | } 70 | --- 71 | 72 | ```tsx 73 | "use client" 74 | 75 | import * as React from "react" 76 | import { CalendarIcon } from "@radix-ui/react-icons" 77 | import { addDays, format } from "date-fns" 78 | import { DateRange } from "react-day-picker" 79 | 80 | import { cn } from "@/lib/utils" 81 | import { Button } from "@/components/ui/button" 82 | import { Calendar } from "@/components/ui/calendar" 83 | import { 84 | Popover, 85 | PopoverContent, 86 | PopoverTrigger, 87 | } from "@/components/ui/popover" 88 | 89 | export function DatePickerWithRange({ 90 | className, 91 | }: React.HTMLAttributes) { 92 | const [date, setDate] = React.useState({ 93 | from: new Date(), 94 | to: addDays(new Date(), 20), 95 | }) 96 | 97 | return ( 98 |
99 | 100 | 101 | 123 | 124 | 125 | 133 | 134 | 135 |
136 | ) 137 | } 138 | ``` -------------------------------------------------------------------------------- /content/snippets/date-picker.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date picker 3 | description: A date picker component made with shadcn components 4 | code: | 5 | "use client" 6 | 7 | import * as React from "react" 8 | import { CalendarIcon } from "@radix-ui/react-icons" 9 | import { format } from "date-fns" 10 | 11 | import { cn } from "@/lib/utils" 12 | import { Button } from "@/components/ui/button" 13 | import { Calendar } from "@/components/ui/calendar" 14 | import { 15 | Popover, 16 | PopoverContent, 17 | PopoverTrigger, 18 | } from "@/components/ui/popover" 19 | 20 | export function DatePickerDemo() { 21 | const [date, setDate] = React.useState() 22 | 23 | return ( 24 | 25 | 26 | 36 | 37 | 38 | 44 | 45 | 46 | ) 47 | } 48 | --- 49 | 50 | ```tsx 51 | "use client" 52 | 53 | import * as React from "react" 54 | import { CalendarIcon } from "@radix-ui/react-icons" 55 | import { format } from "date-fns" 56 | 57 | import { cn } from "@/lib/utils" 58 | import { Button } from "@/components/ui/button" 59 | import { Calendar } from "@/components/ui/calendar" 60 | import { 61 | Popover, 62 | PopoverContent, 63 | PopoverTrigger, 64 | } from "@/components/ui/popover" 65 | 66 | export function DatePickerDemo() { 67 | const [date, setDate] = React.useState() 68 | 69 | return ( 70 | 71 | 72 | 82 | 83 | 84 | 90 | 91 | 92 | ) 93 | } 94 | ``` -------------------------------------------------------------------------------- /content/snippets/date-time-24h-picker-form.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date-time picker (24h) form 3 | description: A date-time (24h) picker component made with shadcn components 4 | code: | 5 | "use client"; 6 | 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { CalendarIcon } from "@radix-ui/react-icons"; 9 | import { format } from "date-fns"; 10 | import { useForm } from "react-hook-form"; 11 | import { z } from "zod"; 12 | 13 | import { cn } from "@/lib/utils"; 14 | import { Button } from "@/components/ui/button"; 15 | import { Calendar } from "@/components/ui/calendar"; 16 | import { 17 | Form, 18 | FormControl, 19 | FormDescription, 20 | FormField, 21 | FormItem, 22 | FormLabel, 23 | FormMessage, 24 | } from "@/components/ui/form"; 25 | import { 26 | Popover, 27 | PopoverContent, 28 | PopoverTrigger, 29 | } from "@/components/ui/popover"; 30 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 31 | import { toast } from "sonner"; 32 | 33 | const FormSchema = z.object({ 34 | time: z.date({ 35 | required_error: "A date and time is required.", 36 | }), 37 | }); 38 | 39 | export function DateTimePicker24hForm() { 40 | const form = useForm>({ 41 | resolver: zodResolver(FormSchema), 42 | }); 43 | 44 | function onSubmit(data: z.infer) { 45 | toast.success(`Selected date and time: ${format(data.time, "PPPP HH:mm")}`); 46 | } 47 | 48 | function handleDateSelect(date: Date | undefined) { 49 | if (date) { 50 | form.setValue("time", date); 51 | } 52 | } 53 | 54 | function handleTimeChange(type: "hour" | "minute", value: string) { 55 | const currentDate = form.getValues("time") || new Date(); 56 | let newDate = new Date(currentDate); 57 | 58 | if (type === "hour") { 59 | const hour = parseInt(value, 10); 60 | newDate.setHours(hour); 61 | } else if (type === "minute") { 62 | newDate.setMinutes(parseInt(value, 10)); 63 | } 64 | 65 | form.setValue("time", newDate); 66 | } 67 | 68 | return ( 69 |
70 | 71 | ( 75 | 76 | Enter your date & time (24h) 77 | 78 | 79 | 80 | 94 | 95 | 96 | 97 |
98 | 104 |
105 | 106 |
107 | {Array.from({ length: 24 }, (_, i) => i) 108 | .reverse() 109 | .map((hour) => ( 110 | 126 | ))} 127 |
128 | 129 |
130 | 131 |
132 | {Array.from({ length: 12 }, (_, i) => i * 5).map( 133 | (minute) => ( 134 | 150 | ) 151 | )} 152 |
153 | 154 |
155 |
156 |
157 |
158 |
159 | 160 | Please select your preferred date and time. 161 | 162 | 163 |
164 | )} 165 | /> 166 | 167 | 168 | 169 | ); 170 | } 171 | --- 172 | 173 | ```tsx 174 | "use client"; 175 | 176 | import { zodResolver } from "@hookform/resolvers/zod"; 177 | import { CalendarIcon } from "@radix-ui/react-icons"; 178 | import { format } from "date-fns"; 179 | import { useForm } from "react-hook-form"; 180 | import { z } from "zod"; 181 | 182 | import { cn } from "@/lib/utils"; 183 | import { Button } from "@/components/ui/button"; 184 | import { Calendar } from "@/components/ui/calendar"; 185 | import { 186 | Form, 187 | FormControl, 188 | FormDescription, 189 | FormField, 190 | FormItem, 191 | FormLabel, 192 | FormMessage, 193 | } from "@/components/ui/form"; 194 | import { 195 | Popover, 196 | PopoverContent, 197 | PopoverTrigger, 198 | } from "@/components/ui/popover"; 199 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 200 | import { toast } from "sonner"; 201 | 202 | const FormSchema = z.object({ 203 | time: z.date({ 204 | required_error: "A date and time is required.", 205 | }), 206 | }); 207 | 208 | export function DateTimePicker24hForm() { 209 | const form = useForm>({ 210 | resolver: zodResolver(FormSchema), 211 | }); 212 | 213 | function onSubmit(data: z.infer) { 214 | toast.success(`Selected date and time: ${format(data.time, "PPPP HH:mm")}`); 215 | } 216 | 217 | function handleDateSelect(date: Date | undefined) { 218 | if (date) { 219 | form.setValue("time", date); 220 | } 221 | } 222 | 223 | function handleTimeChange(type: "hour" | "minute", value: string) { 224 | const currentDate = form.getValues("time") || new Date(); 225 | let newDate = new Date(currentDate); 226 | 227 | if (type === "hour") { 228 | const hour = parseInt(value, 10); 229 | newDate.setHours(hour); 230 | } else if (type === "minute") { 231 | newDate.setMinutes(parseInt(value, 10)); 232 | } 233 | 234 | form.setValue("time", newDate); 235 | } 236 | 237 | return ( 238 |
239 | 240 | ( 244 | 245 | Enter your date & time (24h) 246 | 247 | 248 | 249 | 263 | 264 | 265 | 266 |
267 | 273 |
274 | 275 |
276 | {Array.from({ length: 24 }, (_, i) => i) 277 | .reverse() 278 | .map((hour) => ( 279 | 294 | ))} 295 |
296 | 300 |
301 | 302 |
303 | {Array.from({ length: 12 }, (_, i) => i * 5).map( 304 | (minute) => ( 305 | 321 | ) 322 | )} 323 |
324 | 328 |
329 |
330 |
331 |
332 |
333 | 334 | Please select your preferred date and time. 335 | 336 | 337 |
338 | )} 339 | /> 340 | 341 | 342 | 343 | ); 344 | } 345 | ``` 346 | -------------------------------------------------------------------------------- /content/snippets/date-time-24h-picker.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date-time picker (24h) 3 | description: A date-time (24h) picker component made with shadcn components 4 | code: | 5 | "use client"; 6 | 7 | import * as React from "react"; 8 | import { CalendarIcon } from "@radix-ui/react-icons" 9 | import { format } from "date-fns"; 10 | 11 | import { cn } from "@/lib/utils"; 12 | import { Button } from "@/components/ui/button"; 13 | import { Calendar } from "@/components/ui/calendar"; 14 | import { 15 | Popover, 16 | PopoverContent, 17 | PopoverTrigger, 18 | } from "@/components/ui/popover"; 19 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 20 | 21 | export function DateTimePicker24h() { 22 | const [date, setDate] = React.useState(); 23 | const [isOpen, setIsOpen] = React.useState(false); 24 | 25 | const hours = Array.from({ length: 24 }, (_, i) => i); 26 | const handleDateSelect = (selectedDate: Date | undefined) => { 27 | if (selectedDate) { 28 | setDate(selectedDate); 29 | } 30 | }; 31 | 32 | const handleTimeChange = ( 33 | type: "hour" | "minute", 34 | value: string 35 | ) => { 36 | if (date) { 37 | const newDate = new Date(date); 38 | if (type === "hour") { 39 | newDate.setHours(parseInt(value)); 40 | } else if (type === "minute") { 41 | newDate.setMinutes(parseInt(value)); 42 | } 43 | setDate(newDate); 44 | } 45 | }; 46 | 47 | return ( 48 | 49 | 50 | 64 | 65 | 66 |
67 | 73 |
74 | 75 |
76 | {hours.reverse().map((hour) => ( 77 | 86 | ))} 87 |
88 | 89 |
90 | 91 |
92 | {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( 93 | 102 | ))} 103 |
104 | 105 |
106 |
107 |
108 |
109 |
110 | ); 111 | } 112 | --- 113 | 114 | ```tsx 115 | "use client"; 116 | 117 | import * as React from "react"; 118 | import { CalendarIcon } from "@radix-ui/react-icons" 119 | import { format } from "date-fns"; 120 | 121 | import { cn } from "@/lib/utils"; 122 | import { Button } from "@/components/ui/button"; 123 | import { Calendar } from "@/components/ui/calendar"; 124 | import { 125 | Popover, 126 | PopoverContent, 127 | PopoverTrigger, 128 | } from "@/components/ui/popover"; 129 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 130 | 131 | export function DateTimePicker24h() { 132 | const [date, setDate] = React.useState(); 133 | const [isOpen, setIsOpen] = React.useState(false); 134 | 135 | const hours = Array.from({ length: 24 }, (_, i) => i); 136 | const handleDateSelect = (selectedDate: Date | undefined) => { 137 | if (selectedDate) { 138 | setDate(selectedDate); 139 | } 140 | }; 141 | 142 | const handleTimeChange = ( 143 | type: "hour" | "minute", 144 | value: string 145 | ) => { 146 | if (date) { 147 | const newDate = new Date(date); 148 | if (type === "hour") { 149 | newDate.setHours(parseInt(value)); 150 | } else if (type === "minute") { 151 | newDate.setMinutes(parseInt(value)); 152 | } 153 | setDate(newDate); 154 | } 155 | }; 156 | 157 | return ( 158 | 159 | 160 | 174 | 175 | 176 |
177 | 183 |
184 | 185 |
186 | {hours.reverse().map((hour) => ( 187 | 196 | ))} 197 |
198 | 199 |
200 | 201 |
202 | {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( 203 | 212 | ))} 213 |
214 | 215 |
216 |
217 |
218 |
219 |
220 | ); 221 | } 222 | ``` 223 | -------------------------------------------------------------------------------- /content/snippets/date-time-picker-form.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date-time picker form 3 | description: A date-time picker component made with shadcn components 4 | code: | 5 | "use client"; 6 | 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { CalendarIcon } from "@radix-ui/react-icons"; 9 | import { format } from "date-fns"; 10 | import { useForm } from "react-hook-form"; 11 | import { z } from "zod"; 12 | 13 | import { cn } from "@/lib/utils"; 14 | import { Button } from "@/components/ui/button"; 15 | import { Calendar } from "@/components/ui/calendar"; 16 | import { 17 | Form, 18 | FormControl, 19 | FormDescription, 20 | FormField, 21 | FormItem, 22 | FormLabel, 23 | FormMessage, 24 | } from "@/components/ui/form"; 25 | import { 26 | Popover, 27 | PopoverContent, 28 | PopoverTrigger, 29 | } from "@/components/ui/popover"; 30 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 31 | import { toast } from "sonner"; 32 | 33 | const FormSchema = z.object({ 34 | time: z.date({ 35 | required_error: "A date and time is required.", 36 | }), 37 | }); 38 | 39 | export function DateTimePickerForm() { 40 | const form = useForm>({ 41 | resolver: zodResolver(FormSchema), 42 | }); 43 | 44 | function onSubmit(data: z.infer) { 45 | toast.success(`Selected date and time: ${format(data.time, "PPPPpppp")}`); 46 | } 47 | 48 | function handleDateSelect(date: Date | undefined) { 49 | if (date) { 50 | form.setValue("time", date); 51 | } 52 | } 53 | 54 | function handleTimeChange(type: "hour" | "minute" | "ampm", value: string) { 55 | const currentDate = form.getValues("time") || new Date(); 56 | let newDate = new Date(currentDate); 57 | 58 | if (type === "hour") { 59 | const hour = parseInt(value, 10); 60 | newDate.setHours(newDate.getHours() >= 12 ? hour + 12 : hour); 61 | } else if (type === "minute") { 62 | newDate.setMinutes(parseInt(value, 10)); 63 | } else if (type === "ampm") { 64 | const hours = newDate.getHours(); 65 | if (value === "AM" && hours >= 12) { 66 | newDate.setHours(hours - 12); 67 | } else if (value === "PM" && hours < 12) { 68 | newDate.setHours(hours + 12); 69 | } 70 | } 71 | 72 | form.setValue("time", newDate); 73 | } 74 | 75 | return ( 76 |
77 | 78 | ( 82 | 83 | Enter your date & time (12h) 84 | 85 | 86 | 87 | 101 | 102 | 103 | 104 |
105 | 111 |
112 | 113 |
114 | {Array.from({ length: 12 }, (_, i) => i + 1) 115 | .reverse() 116 | .map((hour) => ( 117 | 133 | ))} 134 |
135 | 136 |
137 | 138 |
139 | {Array.from({ length: 12 }, (_, i) => i * 5).map( 140 | (minute) => ( 141 | 157 | ) 158 | )} 159 |
160 | 161 |
162 | 163 |
164 | {["AM", "PM"].map((ampm) => ( 165 | 182 | ))} 183 |
184 |
185 |
186 |
187 |
188 |
189 | 190 | Please select your preferred date and time. 191 | 192 | 193 |
194 | )} 195 | /> 196 | 197 | 198 | 199 | ); 200 | } 201 | --- 202 | 203 | ```tsx 204 | "use client"; 205 | 206 | import { zodResolver } from "@hookform/resolvers/zod"; 207 | import { CalendarIcon } from "@radix-ui/react-icons"; 208 | import { format } from "date-fns"; 209 | import { useForm } from "react-hook-form"; 210 | import { z } from "zod"; 211 | 212 | import { cn } from "@/lib/utils"; 213 | import { Button } from "@/components/ui/button"; 214 | import { Calendar } from "@/components/ui/calendar"; 215 | import { 216 | Form, 217 | FormControl, 218 | FormDescription, 219 | FormField, 220 | FormItem, 221 | FormLabel, 222 | FormMessage, 223 | } from "@/components/ui/form"; 224 | import { 225 | Popover, 226 | PopoverContent, 227 | PopoverTrigger, 228 | } from "@/components/ui/popover"; 229 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 230 | import { toast } from "sonner"; 231 | 232 | const FormSchema = z.object({ 233 | time: z.date({ 234 | required_error: "A date and time is required.", 235 | }), 236 | }); 237 | 238 | export function DateTimePickerForm() { 239 | const form = useForm>({ 240 | resolver: zodResolver(FormSchema), 241 | }); 242 | 243 | function onSubmit(data: z.infer) { 244 | toast.success(`Selected date and time: ${format(data.time, "PPPPpppp")}`); 245 | } 246 | 247 | function handleDateSelect(date: Date | undefined) { 248 | if (date) { 249 | form.setValue("time", date); 250 | } 251 | } 252 | 253 | function handleTimeChange(type: "hour" | "minute" | "ampm", value: string) { 254 | const currentDate = form.getValues("time") || new Date(); 255 | let newDate = new Date(currentDate); 256 | 257 | if (type === "hour") { 258 | const hour = parseInt(value, 10); 259 | newDate.setHours(newDate.getHours() >= 12 ? hour + 12 : hour); 260 | } else if (type === "minute") { 261 | newDate.setMinutes(parseInt(value, 10)); 262 | } else if (type === "ampm") { 263 | const hours = newDate.getHours(); 264 | if (value === "AM" && hours >= 12) { 265 | newDate.setHours(hours - 12); 266 | } else if (value === "PM" && hours < 12) { 267 | newDate.setHours(hours + 12); 268 | } 269 | } 270 | 271 | form.setValue("time", newDate); 272 | } 273 | 274 | return ( 275 |
276 | 277 | ( 281 | 282 | Enter your date & time (12h) 283 | 284 | 285 | 286 | 300 | 301 | 302 | 303 |
304 | 310 |
311 | 312 |
313 | {Array.from({ length: 12 }, (_, i) => i + 1) 314 | .reverse() 315 | .map((hour) => ( 316 | 332 | ))} 333 |
334 | 338 |
339 | 340 |
341 | {Array.from({ length: 12 }, (_, i) => i * 5).map( 342 | (minute) => ( 343 | 359 | ) 360 | )} 361 |
362 | 366 |
367 | 368 |
369 | {["AM", "PM"].map((ampm) => ( 370 | 387 | ))} 388 |
389 |
390 |
391 |
392 |
393 |
394 | 395 | Please select your preferred date and time. 396 | 397 | 398 |
399 | )} 400 | /> 401 | 402 | 403 | 404 | ); 405 | } 406 | ``` 407 | -------------------------------------------------------------------------------- /content/snippets/date-time-picker.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Date-time picker 3 | description: A date-time picker component made with shadcn components 4 | code: | 5 | "use client"; 6 | 7 | import * as React from "react"; 8 | import { CalendarIcon } from "@radix-ui/react-icons"; 9 | import { format } from "date-fns"; 10 | 11 | import { cn } from "@/lib/utils"; 12 | import { Button } from "@/components/ui/button"; 13 | import { Calendar } from "@/components/ui/calendar"; 14 | import { 15 | Popover, 16 | PopoverContent, 17 | PopoverTrigger, 18 | } from "@/components/ui/popover"; 19 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 20 | 21 | export function DateTimePicker() { 22 | const [date, setDate] = React.useState(); 23 | const [isOpen, setIsOpen] = React.useState(false); 24 | 25 | const hours = Array.from({ length: 12 }, (_, i) => i + 1); 26 | const handleDateSelect = (selectedDate: Date | undefined) => { 27 | if (selectedDate) { 28 | setDate(selectedDate); 29 | } 30 | }; 31 | 32 | const handleTimeChange = ( 33 | type: "hour" | "minute" | "ampm", 34 | value: string 35 | ) => { 36 | if (date) { 37 | const newDate = new Date(date); 38 | if (type === "hour") { 39 | newDate.setHours( 40 | (parseInt(value) % 12) + (newDate.getHours() >= 12 ? 12 : 0) 41 | ); 42 | } else if (type === "minute") { 43 | newDate.setMinutes(parseInt(value)); 44 | } else if (type === "ampm") { 45 | const currentHours = newDate.getHours(); 46 | newDate.setHours( 47 | value === "PM" ? currentHours + 12 : currentHours - 12 48 | ); 49 | } 50 | setDate(newDate); 51 | } 52 | }; 53 | 54 | return ( 55 | 56 | 57 | 71 | 72 | 73 |
74 | 80 |
81 | 82 |
83 | {hours.reverse().map((hour) => ( 84 | 97 | ))} 98 |
99 | 100 |
101 | 102 |
103 | {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( 104 | 119 | ))} 120 |
121 | 122 |
123 | 124 |
125 | {["AM", "PM"].map((ampm) => ( 126 | 141 | ))} 142 |
143 |
144 |
145 |
146 |
147 |
148 | ); 149 | } 150 | --- 151 | 152 | ```tsx 153 | "use client"; 154 | 155 | import * as React from "react"; 156 | import { CalendarIcon } from "@radix-ui/react-icons"; 157 | import { format } from "date-fns"; 158 | 159 | import { cn } from "@/lib/utils"; 160 | import { Button } from "@/components/ui/button"; 161 | import { Calendar } from "@/components/ui/calendar"; 162 | import { 163 | Popover, 164 | PopoverContent, 165 | PopoverTrigger, 166 | } from "@/components/ui/popover"; 167 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 168 | 169 | export function DateTimePicker() { 170 | const [date, setDate] = React.useState(); 171 | const [isOpen, setIsOpen] = React.useState(false); 172 | 173 | const hours = Array.from({ length: 12 }, (_, i) => i + 1); 174 | const handleDateSelect = (selectedDate: Date | undefined) => { 175 | if (selectedDate) { 176 | setDate(selectedDate); 177 | } 178 | }; 179 | 180 | const handleTimeChange = ( 181 | type: "hour" | "minute" | "ampm", 182 | value: string 183 | ) => { 184 | if (date) { 185 | const newDate = new Date(date); 186 | if (type === "hour") { 187 | newDate.setHours( 188 | (parseInt(value) % 12) + (newDate.getHours() >= 12 ? 12 : 0) 189 | ); 190 | } else if (type === "minute") { 191 | newDate.setMinutes(parseInt(value)); 192 | } else if (type === "ampm") { 193 | const currentHours = newDate.getHours(); 194 | newDate.setHours( 195 | value === "PM" ? currentHours + 12 : currentHours - 12 196 | ); 197 | } 198 | setDate(newDate); 199 | } 200 | }; 201 | 202 | return ( 203 | 204 | 205 | 219 | 220 | 221 |
222 | 228 |
229 | 230 |
231 | {hours.reverse().map((hour) => ( 232 | 245 | ))} 246 |
247 | 248 |
249 | 250 |
251 | {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( 252 | 267 | ))} 268 |
269 | 270 |
271 | 272 |
273 | {["AM", "PM"].map((ampm) => ( 274 | 289 | ))} 290 |
291 |
292 |
293 |
294 |
295 |
296 | ); 297 | } 298 | ``` 299 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-date-time-picker", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "velite && next dev", 7 | "build": "velite && next build", 8 | "start": "velite && next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@hookform/resolvers": "^3.9.0", 13 | "@radix-ui/react-icons": "^1.3.0", 14 | "@radix-ui/react-label": "^2.1.0", 15 | "@radix-ui/react-popover": "^1.1.1", 16 | "@radix-ui/react-scroll-area": "^1.1.0", 17 | "@radix-ui/react-select": "^2.1.1", 18 | "@radix-ui/react-separator": "^1.1.0", 19 | "@radix-ui/react-slot": "^1.1.0", 20 | "@radix-ui/react-tooltip": "^1.1.2", 21 | "class-variance-authority": "^0.7.0", 22 | "clsx": "^2.1.1", 23 | "date-fns": "^3.6.0", 24 | "next": "14.2.5", 25 | "next-themes": "^0.3.0", 26 | "react": "^18", 27 | "react-day-picker": "8.10.1", 28 | "react-dom": "^18", 29 | "react-hook-form": "^7.53.0", 30 | "remark": "^15.0.1", 31 | "sonner": "^1.5.0", 32 | "tailwind-merge": "^2.4.0", 33 | "tailwindcss-animate": "^1.0.7", 34 | "vaul": "^0.9.1", 35 | "zod": "^3.23.8" 36 | }, 37 | "devDependencies": { 38 | "@types/node": "^20", 39 | "@types/react": "^18", 40 | "@types/react-dom": "^18", 41 | "eslint": "^8", 42 | "eslint-config-next": "14.2.5", 43 | "postcss": "^8", 44 | "rehype-autolink-headings": "^7.1.0", 45 | "rehype-katex": "^7.0.0", 46 | "rehype-pretty-code": "^0.13.2", 47 | "rehype-slug": "^6.0.0", 48 | "remark-gfm": "^4.0.0", 49 | "remark-math": "^6.0.0", 50 | "tailwindcss": "^3.4.1", 51 | "typescript": "^5", 52 | "velite": "^0.1.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudrodip/shadcn-date-time-picker/102715d1a665aca584507593896be293b393adc9/public/favicon.png -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudrodip/shadcn-date-time-picker/102715d1a665aca584507593896be293b393adc9/public/og.png -------------------------------------------------------------------------------- /src/app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import Link from "next/link"; 5 | 6 | export default function ErrorPage() { 7 | return ( 8 |
9 |
10 |
11 | Oops! Something went wrong 12 |
13 |
14 |

Sorry for the inconvenience

15 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Manrope, Poppins } from "next/font/google"; 3 | import { RootProvider } from "@/components/root-provider"; 4 | import { siteConfig } from "@/config/site.config"; 5 | import { cn } from "@/lib/utils"; 6 | import "@/styles/globals.css"; 7 | 8 | const fontSans = Manrope({ 9 | subsets: ["latin"], 10 | variable: "--font-sans", 11 | }); 12 | 13 | const fontHeading = Poppins({ 14 | subsets: ["latin"], 15 | weight: "400", 16 | variable: "--font-heading", 17 | }); 18 | 19 | export const metadata: Metadata = { 20 | metadataBase: new URL(siteConfig.siteUrl), 21 | title: siteConfig.title, 22 | description: siteConfig.description, 23 | keywords: siteConfig.keywords, 24 | creator: siteConfig.name, 25 | icons: { 26 | icon: "/favicon.png", 27 | shortcut: "/favicon.png", 28 | }, 29 | openGraph: { 30 | title: siteConfig.title, 31 | description: siteConfig.description, 32 | url: siteConfig.siteUrl, 33 | siteName: siteConfig.name, 34 | images: [ 35 | { 36 | url: siteConfig.ogImage, 37 | width: 2880, 38 | height: 1800, 39 | alt: siteConfig.name, 40 | }, 41 | ], 42 | type: "website", 43 | locale: "en_US", 44 | }, 45 | twitter: { 46 | card: "summary_large_image", 47 | site: siteConfig.links.x, 48 | title: siteConfig.title, 49 | description: siteConfig.description, 50 | images: { 51 | url: siteConfig.ogImage, 52 | width: 2880, 53 | height: 1800, 54 | alt: siteConfig.name, 55 | }, 56 | }, 57 | }; 58 | 59 | export default function RootLayout({ 60 | children, 61 | }: Readonly<{ 62 | children: React.ReactNode; 63 | }>) { 64 | return ( 65 | 66 | 73 | 74 |
75 | {children} 76 |
77 |
78 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import Link from "next/link" 3 | 4 | export default function NotFoundPage() { 5 | return ( 6 |
7 |
8 |
9 | 404 10 |
11 |
12 |

Sorry we can't find this page

13 | 18 |
19 | ) 20 | } -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Socials from "@/components/socials"; 2 | import { Examples } from "@/components/examples/date-time-picker-examples"; 3 | import { FormExamples } from "@/components/examples/date-time-picker-form-examples"; 4 | import { ExternalLink } from "@/components/external-link"; 5 | import { siteConfig } from "@/config/site.config"; 6 | 7 | export default function Home() { 8 | return ( 9 |
10 |
11 | 12 | 13 |

14 | made by{" "} 15 | rds_agi 16 |

17 |
18 | ); 19 | } 20 | 21 | const Header = () => { 22 | return ( 23 |
24 |

Date & Time picker

25 | 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/copy-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | import { CheckIcon, ClipboardCopyIcon, ClipboardIcon } from "@radix-ui/react-icons"; 5 | import { cn } from "@/lib/utils"; 6 | 7 | export const CopyButton = ({ children, className, iconClassName }: { children: React.ReactNode, className?: string, iconClassName?: string }) => { 8 | const [isCopied, setIsCopied] = useState(false); 9 | 10 | const copy = async () => { 11 | const sourceCode = extractSourceCode(children); 12 | await navigator.clipboard.writeText(sourceCode); 13 | setIsCopied(true); 14 | 15 | setTimeout(() => { 16 | setIsCopied(false); 17 | }, 1000); 18 | }; 19 | 20 | const extractSourceCode = (node: React.ReactNode): string => { 21 | if (typeof node === "string") { 22 | return node; 23 | } 24 | if (Array.isArray(node)) { 25 | return node.map(extractSourceCode).join(""); 26 | } 27 | if (React.isValidElement(node)) { 28 | const { type, props } = node; 29 | const children = React.Children.map(props.children, extractSourceCode)?.join(""); 30 | const propPairs = Object.entries(props) 31 | .map(([key, value]) => `${key}={${JSON.stringify(value)}}`) 32 | .join(" "); 33 | return `${children}`; 34 | } 35 | return ""; 36 | }; 37 | 38 | return ( 39 | 42 | ); 43 | }; -------------------------------------------------------------------------------- /src/components/date-n-time/date-picker-range.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { CalendarIcon } from "@radix-ui/react-icons" 5 | import { addDays, format } from "date-fns" 6 | import { DateRange } from "react-day-picker" 7 | 8 | import { cn } from "@/lib/utils" 9 | import { Button } from "@/components/ui/button" 10 | import { Calendar } from "@/components/ui/calendar" 11 | import { 12 | Popover, 13 | PopoverContent, 14 | PopoverTrigger, 15 | } from "@/components/ui/popover" 16 | 17 | export function DatePickerWithRange({ 18 | className, 19 | }: React.HTMLAttributes) { 20 | const [date, setDate] = React.useState({ 21 | from: new Date(), 22 | to: addDays(new Date(), 20), 23 | }) 24 | 25 | return ( 26 |
27 | 28 | 29 | 51 | 52 | 53 | 61 | 62 | 63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /src/components/date-n-time/date-picker.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { CalendarIcon } from "@radix-ui/react-icons" 5 | import { format } from "date-fns" 6 | 7 | import { cn } from "@/lib/utils" 8 | import { Button } from "@/components/ui/button" 9 | import { Calendar } from "@/components/ui/calendar" 10 | import { 11 | Popover, 12 | PopoverContent, 13 | PopoverTrigger, 14 | } from "@/components/ui/popover" 15 | 16 | export function DatePickerDemo() { 17 | const [date, setDate] = React.useState() 18 | 19 | return ( 20 | 21 | 22 | 32 | 33 | 34 | 40 | 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/date-n-time/date-time-picker-24h.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { CalendarIcon } from "@radix-ui/react-icons" 5 | import { format } from "date-fns"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Calendar } from "@/components/ui/calendar"; 10 | import { 11 | Popover, 12 | PopoverContent, 13 | PopoverTrigger, 14 | } from "@/components/ui/popover"; 15 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 16 | 17 | export function DateTimePicker24h() { 18 | const [date, setDate] = React.useState(); 19 | const [isOpen, setIsOpen] = React.useState(false); 20 | 21 | const hours = Array.from({ length: 24 }, (_, i) => i); 22 | const handleDateSelect = (selectedDate: Date | undefined) => { 23 | if (selectedDate) { 24 | setDate(selectedDate); 25 | } 26 | }; 27 | 28 | const handleTimeChange = ( 29 | type: "hour" | "minute", 30 | value: string 31 | ) => { 32 | if (date) { 33 | const newDate = new Date(date); 34 | if (type === "hour") { 35 | newDate.setHours(parseInt(value)); 36 | } else if (type === "minute") { 37 | newDate.setMinutes(parseInt(value)); 38 | } 39 | setDate(newDate); 40 | } 41 | }; 42 | 43 | return ( 44 | 45 | 46 | 60 | 61 | 62 |
63 | 69 |
70 | 71 |
72 | {hours.reverse().map((hour) => ( 73 | 82 | ))} 83 |
84 | 85 |
86 | 87 |
88 | {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( 89 | 98 | ))} 99 |
100 | 101 |
102 |
103 |
104 |
105 |
106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /src/components/date-n-time/date-time-picker.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { CalendarIcon } from "@radix-ui/react-icons"; 5 | import { format } from "date-fns"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Calendar } from "@/components/ui/calendar"; 10 | import { 11 | Popover, 12 | PopoverContent, 13 | PopoverTrigger, 14 | } from "@/components/ui/popover"; 15 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 16 | 17 | export function DateTimePicker() { 18 | const [date, setDate] = React.useState(); 19 | const [isOpen, setIsOpen] = React.useState(false); 20 | 21 | const hours = Array.from({ length: 12 }, (_, i) => i + 1); 22 | const handleDateSelect = (selectedDate: Date | undefined) => { 23 | if (selectedDate) { 24 | setDate(selectedDate); 25 | } 26 | }; 27 | 28 | const handleTimeChange = ( 29 | type: "hour" | "minute" | "ampm", 30 | value: string 31 | ) => { 32 | if (date) { 33 | const newDate = new Date(date); 34 | if (type === "hour") { 35 | newDate.setHours( 36 | (parseInt(value) % 12) + (newDate.getHours() >= 12 ? 12 : 0) 37 | ); 38 | } else if (type === "minute") { 39 | newDate.setMinutes(parseInt(value)); 40 | } else if (type === "ampm") { 41 | const currentHours = newDate.getHours(); 42 | newDate.setHours( 43 | value === "PM" ? currentHours + 12 : currentHours - 12 44 | ); 45 | } 46 | setDate(newDate); 47 | } 48 | }; 49 | 50 | return ( 51 | 52 | 53 | 67 | 68 | 69 |
70 | 76 |
77 | 78 |
79 | {hours.reverse().map((hour) => ( 80 | 93 | ))} 94 |
95 | 96 |
97 | 98 |
99 | {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( 100 | 115 | ))} 116 |
117 | 118 |
119 | 120 |
121 | {["AM", "PM"].map((ampm) => ( 122 | 137 | ))} 138 |
139 |
140 |
141 |
142 |
143 |
144 | ); 145 | } 146 | -------------------------------------------------------------------------------- /src/components/date-n-time/form/date-picker-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { zodResolver } from "@hookform/resolvers/zod"; 4 | import { CalendarIcon } from "@radix-ui/react-icons"; 5 | import { format } from "date-fns"; 6 | import { useForm } from "react-hook-form"; 7 | import { z } from "zod"; 8 | 9 | import { cn } from "@/lib/utils"; 10 | import { Button } from "@/components/ui/button"; 11 | import { Calendar } from "@/components/ui/calendar"; 12 | import { 13 | Form, 14 | FormControl, 15 | FormDescription, 16 | FormField, 17 | FormItem, 18 | FormLabel, 19 | FormMessage, 20 | } from "@/components/ui/form"; 21 | import { 22 | Popover, 23 | PopoverContent, 24 | PopoverTrigger, 25 | } from "@/components/ui/popover"; 26 | import { toast } from "sonner"; 27 | 28 | const FormSchema = z.object({ 29 | dob: z.date({ 30 | required_error: "A date of birth is required.", 31 | }), 32 | }); 33 | 34 | export function DatePickerForm() { 35 | const form = useForm>({ 36 | resolver: zodResolver(FormSchema), 37 | }); 38 | 39 | function onSubmit(data: z.infer) { 40 | toast.success(`Selected date of birth: ${format(data.dob, "PPP")}`); 41 | } 42 | 43 | return ( 44 |
45 | 46 | ( 50 | 51 | {/* Date of birth */} 52 | 53 | 54 | 55 | 69 | 70 | 71 | 72 | 77 | date > new Date() || date < new Date("1900-01-01") 78 | } 79 | initialFocus 80 | /> 81 | 82 | 83 | 84 | provided by shadcn 85 | 86 | 87 | 88 | )} 89 | /> 90 | 91 | 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/date-n-time/form/date-picker-range-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { zodResolver } from "@hookform/resolvers/zod"; 4 | import { CalendarIcon } from "@radix-ui/react-icons"; 5 | import { format } from "date-fns"; 6 | import { useForm } from "react-hook-form"; 7 | import { z } from "zod"; 8 | 9 | import { cn } from "@/lib/utils"; 10 | import { Button } from "@/components/ui/button"; 11 | import { Calendar } from "@/components/ui/calendar"; 12 | import { 13 | Form, 14 | FormControl, 15 | FormDescription, 16 | FormField, 17 | FormItem, 18 | FormLabel, 19 | FormMessage, 20 | } from "@/components/ui/form"; 21 | import { 22 | Popover, 23 | PopoverContent, 24 | PopoverTrigger, 25 | } from "@/components/ui/popover"; 26 | import { toast } from "sonner"; 27 | 28 | const FormSchema = z.object({ 29 | dateRange: z.object({ 30 | from: z.date({ 31 | required_error: "A start date is required.", 32 | }), 33 | to: z.date({ 34 | required_error: "An end date is required.", 35 | }), 36 | }), 37 | }); 38 | 39 | export function DatePickerWithRangeForm() { 40 | const form = useForm>({ 41 | resolver: zodResolver(FormSchema), 42 | }); 43 | 44 | function onSubmit(data: z.infer) { 45 | toast.success(`Selected date range: From ${format(data.dateRange.from, "PPP")} to ${format(data.dateRange.to, "PPP")}`); 46 | } 47 | 48 | return ( 49 |
50 | 51 | ( 55 | 56 | {/* Select date range */} 57 | 58 | 59 | 60 | 82 | 83 | 84 | 85 | 93 | 94 | 95 | 96 | Select a date range for your event. 97 | 98 | 99 | 100 | )} 101 | /> 102 | 103 | 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /src/components/date-n-time/form/date-time-picker-24h-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { zodResolver } from "@hookform/resolvers/zod"; 4 | import { CalendarIcon } from "@radix-ui/react-icons"; 5 | import { format } from "date-fns"; 6 | import { useForm } from "react-hook-form"; 7 | import { z } from "zod"; 8 | 9 | import { cn } from "@/lib/utils"; 10 | import { Button } from "@/components/ui/button"; 11 | import { Calendar } from "@/components/ui/calendar"; 12 | import { 13 | Form, 14 | FormControl, 15 | FormDescription, 16 | FormField, 17 | FormItem, 18 | FormLabel, 19 | FormMessage, 20 | } from "@/components/ui/form"; 21 | import { 22 | Popover, 23 | PopoverContent, 24 | PopoverTrigger, 25 | } from "@/components/ui/popover"; 26 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 27 | import { toast } from "sonner"; 28 | 29 | const FormSchema = z.object({ 30 | time: z.date({ 31 | required_error: "A date and time is required.", 32 | }), 33 | }); 34 | 35 | export function DateTimePicker24hForm() { 36 | const form = useForm>({ 37 | resolver: zodResolver(FormSchema), 38 | }); 39 | 40 | function onSubmit(data: z.infer) { 41 | toast.success(`Selected date and time: ${format(data.time, "PPPP HH:mm")}`); 42 | } 43 | 44 | function handleDateSelect(date: Date | undefined) { 45 | if (date) { 46 | form.setValue("time", date); 47 | } 48 | } 49 | 50 | function handleTimeChange(type: "hour" | "minute", value: string) { 51 | const currentDate = form.getValues("time") || new Date(); 52 | let newDate = new Date(currentDate); 53 | 54 | if (type === "hour") { 55 | const hour = parseInt(value, 10); 56 | newDate.setHours(hour); 57 | } else if (type === "minute") { 58 | newDate.setMinutes(parseInt(value, 10)); 59 | } 60 | 61 | form.setValue("time", newDate); 62 | } 63 | 64 | return ( 65 |
66 | 67 | ( 71 | 72 | 73 | 74 | 75 | 89 | 90 | 91 | 92 |
93 | 99 |
100 | 101 |
102 | {Array.from({ length: 24 }, (_, i) => i) 103 | .reverse() 104 | .map((hour) => ( 105 | 121 | ))} 122 |
123 | 124 |
125 | 126 |
127 | {Array.from({ length: 12 }, (_, i) => i * 5).map( 128 | (minute) => ( 129 | 145 | ) 146 | )} 147 |
148 | 149 |
150 |
151 |
152 |
153 |
154 | 155 | Please select your preferred date and time. 156 | 157 | 158 |
159 | )} 160 | /> 161 | 162 | 163 | 164 | ); 165 | } 166 | -------------------------------------------------------------------------------- /src/components/date-n-time/form/date-time-picker-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { zodResolver } from "@hookform/resolvers/zod"; 4 | import { CalendarIcon } from "@radix-ui/react-icons"; 5 | import { format } from "date-fns"; 6 | import { useForm } from "react-hook-form"; 7 | import { z } from "zod"; 8 | 9 | import { cn } from "@/lib/utils"; 10 | import { Button } from "@/components/ui/button"; 11 | import { Calendar } from "@/components/ui/calendar"; 12 | import { 13 | Form, 14 | FormControl, 15 | FormDescription, 16 | FormField, 17 | FormItem, 18 | FormMessage, 19 | } from "@/components/ui/form"; 20 | import { 21 | Popover, 22 | PopoverContent, 23 | PopoverTrigger, 24 | } from "@/components/ui/popover"; 25 | import { toast } from "sonner"; 26 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 27 | 28 | const FormSchema = z.object({ 29 | time: z.date({ 30 | required_error: "A date and time is required.", 31 | }), 32 | }); 33 | 34 | export function DateTimePickerForm() { 35 | const form = useForm>({ 36 | resolver: zodResolver(FormSchema), 37 | }); 38 | 39 | function onSubmit(data: z.infer) { 40 | toast.success(`Selected date and time: ${format(data.time, "PPPPpppp")}`); 41 | } 42 | 43 | function handleDateSelect(date: Date | undefined) { 44 | if (date) { 45 | form.setValue("time", date); 46 | } 47 | } 48 | 49 | function handleTimeChange(type: "hour" | "minute" | "ampm", value: string) { 50 | const currentDate = form.getValues("time") || new Date(); 51 | let newDate = new Date(currentDate); 52 | 53 | if (type === "hour") { 54 | const hour = parseInt(value, 10); 55 | newDate.setHours(newDate.getHours() >= 12 ? hour + 12 : hour); 56 | } else if (type === "minute") { 57 | newDate.setMinutes(parseInt(value, 10)); 58 | } else if (type === "ampm") { 59 | const hours = newDate.getHours(); 60 | if (value === "AM" && hours >= 12) { 61 | newDate.setHours(hours - 12); 62 | } else if (value === "PM" && hours < 12) { 63 | newDate.setHours(hours + 12); 64 | } 65 | } 66 | 67 | form.setValue("time", newDate); 68 | } 69 | 70 | return ( 71 |
72 | 73 | ( 77 | 78 | 79 | 80 | 81 | 95 | 96 | 97 | 98 |
99 | 105 |
106 | 107 |
108 | {Array.from({ length: 12 }, (_, i) => i + 1) 109 | .reverse() 110 | .map((hour) => ( 111 | 127 | ))} 128 |
129 | 130 |
131 | 132 |
133 | {Array.from({ length: 12 }, (_, i) => i * 5).map( 134 | (minute) => ( 135 | 151 | ) 152 | )} 153 |
154 | 155 |
156 | 157 |
158 | {["AM", "PM"].map((ampm) => ( 159 | 176 | ))} 177 |
178 |
179 |
180 |
181 |
182 |
183 | 184 | Please select your preferred date and time. 185 | 186 | 187 |
188 | )} 189 | /> 190 | 191 | 192 | 193 | ); 194 | } 195 | -------------------------------------------------------------------------------- /src/components/date-n-time/form/index.ts: -------------------------------------------------------------------------------- 1 | import { DatePickerForm } from "@/components/date-n-time/form/date-picker-form"; 2 | import { DatePickerWithRangeForm } from "@/components/date-n-time/form/date-picker-range-form"; 3 | import { DateTimePickerForm } from "@/components/date-n-time/form/date-time-picker-form"; 4 | import { DateTimePicker24hForm } from "@/components/date-n-time/form/date-time-picker-24h-form"; 5 | 6 | export { DatePickerForm, DatePickerWithRangeForm, DateTimePickerForm, DateTimePicker24hForm }; -------------------------------------------------------------------------------- /src/components/date-n-time/index.ts: -------------------------------------------------------------------------------- 1 | import { DatePickerDemo } from "@/components/date-n-time/date-picker"; 2 | import { DatePickerWithRange } from "@/components/date-n-time/date-picker-range"; 3 | import { DateTimePicker } from "@/components/date-n-time/date-time-picker"; 4 | import { DateTimePicker24h } from "@/components/date-n-time/date-time-picker-24h"; 5 | 6 | export { DatePickerDemo, DatePickerWithRange, DateTimePicker, DateTimePicker24h }; -------------------------------------------------------------------------------- /src/components/examples/date-time-picker-examples.tsx: -------------------------------------------------------------------------------- 1 | import { DatePickerDemo, DatePickerWithRange, DateTimePicker, DateTimePicker24h } from "@/components/date-n-time"; 2 | import { ComponentWrapper } from "./wrapper"; 3 | 4 | export const Examples = () => { 5 | return ( 6 |
7 |

8 | Date & Time picker components built with React &{" "} 9 | 14 | ShadCN UI 15 | 16 |

17 |
18 | ( 23 | 24 | provided by{" "} 25 | 30 | shadcn 31 | 32 | 33 | )} 34 | /> 35 | ( 40 | 41 | provided by{" "} 42 | 47 | shadcn 48 | 49 | 50 | )} 51 | /> 52 | 57 | 62 |
63 |
64 | ); 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/examples/date-time-picker-form-examples.tsx: -------------------------------------------------------------------------------- 1 | import { DatePickerForm, DatePickerWithRangeForm, DateTimePickerForm, DateTimePicker24hForm } from "@/components/date-n-time/form"; 2 | import { ComponentWrapper } from "./wrapper"; 3 | 4 | export const FormExamples = () => { 5 | return ( 6 |
7 |

8 | Example with{" "} 9 | 10 | react-hook-form & zod 11 | 12 |

13 |
14 | 19 | 24 | 29 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/examples/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { snippets } from "#site/content"; 2 | import { Label } from "@/components/ui/label"; 3 | import { CopyButton } from "../copy-button"; 4 | import { 5 | Drawer, 6 | DrawerContent, 7 | DrawerDescription, 8 | DrawerHeader, 9 | DrawerTitle, 10 | DrawerTrigger, 11 | } from "@/components/ui/drawer"; 12 | import { MDXContentRenderer } from "@/components/mdx/mdx-content-renderer"; 13 | 14 | export const ComponentWrapper = ({ 15 | Component, 16 | label, 17 | slug, 18 | Footer, 19 | }: { 20 | Component: React.ElementType; 21 | label?: string; 22 | slug: string; 23 | Footer?: React.ElementType; 24 | }) => { 25 | const filteredSnippets = snippets.filter(snippet => snippet.slugAsParams === slug); 26 | if (filteredSnippets.length === 0) { 27 | return null; 28 | } 29 | const snippet = filteredSnippets[0]; 30 | return ( 31 |
32 |
33 | 34 |
35 | 36 | {snippet.code} 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | {snippet.title} 46 | 47 | {snippet.description} 48 | 49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 | 58 | {Footer &&
} 59 |
60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /src/components/external-link.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { ExternalLinkIcon } from "@radix-ui/react-icons"; 3 | 4 | type ExternalLinkProps = { 5 | href: string; 6 | children: React.ReactNode; 7 | className?: string; 8 | } 9 | 10 | export const ExternalLink = ({ href, children, className }: ExternalLinkProps) => { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } -------------------------------------------------------------------------------- /src/components/mdx/callout.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | interface CalloutProps { 4 | icon?: string; 5 | children?: React.ReactNode; 6 | type?: "default" | "warning" | "danger"; 7 | } 8 | 9 | export function Callout({ 10 | children, 11 | icon, 12 | type = "default", 13 | ...props 14 | }: CalloutProps) { 15 | return ( 16 |
23 | {icon && {icon}} 24 |
{children}
25 |
26 | ); 27 | } -------------------------------------------------------------------------------- /src/components/mdx/codeblock.tsx: -------------------------------------------------------------------------------- 1 | import { CopyButton } from "@/components/copy-button"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | export const CodeBlock = ({ 5 | children, 6 | className, 7 | ...props 8 | }: React.HTMLAttributes) => { 9 | return ( 10 |
11 |
12 | {children} 13 |
14 |
18 |         {children}
19 |       
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/mdx/components.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { CodeBlock } from "./codeblock"; 3 | import Image, { type ImageProps } from "next/image"; 4 | import { Callout } from "./callout"; 5 | 6 | export const mdxComponents = { 7 | h1: ({ className, ...props }: React.HTMLAttributes) => ( 8 |

15 | ), 16 | h2: ({ className, ...props }: React.HTMLAttributes) => ( 17 |

24 | ), 25 | h3: ({ className, ...props }: React.HTMLAttributes) => ( 26 |

33 | ), 34 | h4: ({ className, ...props }: React.HTMLAttributes) => ( 35 |

42 | ), 43 | h5: ({ className, ...props }: React.HTMLAttributes) => ( 44 |
51 | ), 52 | h6: ({ className, ...props }: React.HTMLAttributes) => ( 53 |
60 | ), 61 | a: ({ className, ...props }: React.HTMLAttributes) => ( 62 | 66 | ), 67 | p: ({ 68 | className, 69 | ...props 70 | }: React.HTMLAttributes) => ( 71 |

75 | ), 76 | ul: ({ className, ...props }: React.HTMLAttributes) => ( 77 |

    78 | ), 79 | ol: ({ className, ...props }: React.HTMLAttributes) => ( 80 |
      81 | ), 82 | li: ({ className, ...props }: React.HTMLAttributes) => ( 83 |
    1. 84 | ), 85 | blockquote: ({ 86 | className, 87 | ...props 88 | }: React.HTMLAttributes) => ( 89 |
      *]:text-muted-foreground", 92 | className 93 | )} 94 | {...props} 95 | /> 96 | ), 97 | img: ({ 98 | className, 99 | alt, 100 | ...props 101 | }: React.ImgHTMLAttributes) => ( 102 | // eslint-disable-next-line @next/next/no-img-element 103 | {alt} 108 | ), 109 | hr: ({ ...props }) =>
      , 110 | table: ({ 111 | className, 112 | ...props 113 | }: React.HTMLAttributes) => ( 114 |
      115 | 116 | 117 | ), 118 | tr: ({ 119 | className, 120 | ...props 121 | }: React.HTMLAttributes) => ( 122 | 126 | ), 127 | th: ({ 128 | className, 129 | ...props 130 | }: React.HTMLAttributes) => ( 131 |
      138 | ), 139 | td: ({ 140 | className, 141 | ...props 142 | }: React.HTMLAttributes) => ( 143 | 150 | ), 151 | pre: CodeBlock, 152 | code: ({ className, ...props }: React.HTMLAttributes) => ( 153 | 160 | ), 161 | Image: (props: ImageProps) => blog image, 162 | Callout, 163 | }; -------------------------------------------------------------------------------- /src/components/mdx/mdx-content-renderer.tsx: -------------------------------------------------------------------------------- 1 | import * as runtime from 'react/jsx-runtime' 2 | import { mdxComponents } from './components' 3 | import 'katex/dist/katex.min.css'; 4 | 5 | const useMDXComponent = (code: string) => { 6 | const fn = new Function(code) 7 | return fn({ ...runtime }).default 8 | } 9 | 10 | interface MDXProps { 11 | code: string 12 | components?: Record 13 | } 14 | 15 | export const MDXContentRenderer = ({ code, components }: MDXProps) => { 16 | const Component = useMDXComponent(code) 17 | return 18 | } -------------------------------------------------------------------------------- /src/components/root-provider.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/theme/provider"; 2 | import { Toaster } from "@/components/ui/sonner"; 3 | 4 | type RootProvider = { 5 | children: React.ReactNode; 6 | }; 7 | 8 | export const RootProvider: React.FC = ({ children }) => { 9 | return ( 10 | 16 | 17 | {children} 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/socials.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import ThemeToggler from "@/components/theme/toggler"; 3 | import { siteConfig } from "@/config/site.config"; 4 | import { 5 | CodeIcon, 6 | GitHubLogoIcon, 7 | TwitterLogoIcon, 8 | } from "@radix-ui/react-icons"; 9 | 10 | export default function Socials() { 11 | return ( 12 |
      13 | 14 | 24 | 34 | 48 |
      49 | ); 50 | } -------------------------------------------------------------------------------- /src/components/theme/provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | import { type ThemeProviderProps } from "next-themes/dist/types" 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } 10 | -------------------------------------------------------------------------------- /src/components/theme/toggler.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; 4 | import { useTheme } from "next-themes"; 5 | import { Button } from "@/components/ui/button"; 6 | import { useState, useEffect } from "react"; 7 | 8 | export default function ThemeToggler() { 9 | const { theme, setTheme } = useTheme(); 10 | const [systemTheme, setSystemTheme] = useState<"light" | "dark">("light"); 11 | 12 | useEffect(() => { 13 | const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); 14 | setSystemTheme(mediaQuery.matches ? "dark" : "light"); 15 | 16 | const handleChange = (e: MediaQueryListEvent) => { 17 | setSystemTheme(e.matches ? "dark" : "light"); 18 | }; 19 | 20 | mediaQuery.addEventListener("change", handleChange); 21 | return () => mediaQuery.removeEventListener("change", handleChange); 22 | }, []); 23 | 24 | const SWITCH = () => { 25 | switch (theme) { 26 | case "light": 27 | setTheme("dark"); 28 | break; 29 | case "dark": 30 | setTheme("light"); 31 | break; 32 | case "system": 33 | setTheme(systemTheme === "light" ? "dark" : "light"); 34 | break; 35 | default: 36 | break; 37 | } 38 | }; 39 | 40 | const TOGGLE_THEME = () => { 41 | //@ts-ignore 42 | if (!document.startViewTransition) SWITCH(); 43 | 44 | //@ts-ignore 45 | document.startViewTransition(SWITCH); 46 | }; 47 | 48 | return ( 49 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /src/components/ui/calendar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" 5 | import { DayPicker } from "react-day-picker" 6 | 7 | import { cn } from "@/lib/utils" 8 | import { buttonVariants } from "@/components/ui/button" 9 | 10 | export type CalendarProps = React.ComponentProps 11 | 12 | function Calendar({ 13 | className, 14 | classNames, 15 | showOutsideDays = true, 16 | ...props 17 | }: CalendarProps) { 18 | return ( 19 | .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" 43 | : "[&:has([aria-selected])]:rounded-md" 44 | ), 45 | day: cn( 46 | buttonVariants({ variant: "ghost" }), 47 | "h-8 w-8 p-0 font-normal aria-selected:opacity-100" 48 | ), 49 | day_range_start: "day-range-start", 50 | day_range_end: "day-range-end", 51 | day_selected: 52 | "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", 53 | day_today: "bg-accent text-accent-foreground", 54 | day_outside: 55 | "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", 56 | day_disabled: "text-muted-foreground opacity-50", 57 | day_range_middle: 58 | "aria-selected:bg-accent aria-selected:text-accent-foreground", 59 | day_hidden: "invisible", 60 | ...classNames, 61 | }} 62 | components={{ 63 | IconLeft: ({ ...props }) => , 64 | IconRight: ({ ...props }) => , 65 | }} 66 | {...props} 67 | /> 68 | ) 69 | } 70 | Calendar.displayName = "Calendar" 71 | 72 | export { Calendar } 73 | -------------------------------------------------------------------------------- /src/components/ui/drawer.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { Drawer as DrawerPrimitive } from "vaul" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Drawer = ({ 9 | shouldScaleBackground = true, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 16 | ) 17 | Drawer.displayName = "Drawer" 18 | 19 | const DrawerTrigger = DrawerPrimitive.Trigger 20 | 21 | const DrawerPortal = DrawerPrimitive.Portal 22 | 23 | const DrawerClose = DrawerPrimitive.Close 24 | 25 | const DrawerOverlay = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 34 | )) 35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName 36 | 37 | const DrawerContent = React.forwardRef< 38 | React.ElementRef, 39 | React.ComponentPropsWithoutRef 40 | >(({ className, children, ...props }, ref) => ( 41 | 42 | 43 | 51 |
      52 | {children} 53 | 54 | 55 | )) 56 | DrawerContent.displayName = "DrawerContent" 57 | 58 | const DrawerHeader = ({ 59 | className, 60 | ...props 61 | }: React.HTMLAttributes) => ( 62 |
      66 | ) 67 | DrawerHeader.displayName = "DrawerHeader" 68 | 69 | const DrawerFooter = ({ 70 | className, 71 | ...props 72 | }: React.HTMLAttributes) => ( 73 |
      77 | ) 78 | DrawerFooter.displayName = "DrawerFooter" 79 | 80 | const DrawerTitle = React.forwardRef< 81 | React.ElementRef, 82 | React.ComponentPropsWithoutRef 83 | >(({ className, ...props }, ref) => ( 84 | 92 | )) 93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName 94 | 95 | const DrawerDescription = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, ...props }, ref) => ( 99 | 104 | )) 105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName 106 | 107 | export { 108 | Drawer, 109 | DrawerPortal, 110 | DrawerOverlay, 111 | DrawerTrigger, 112 | DrawerClose, 113 | DrawerContent, 114 | DrawerHeader, 115 | DrawerFooter, 116 | DrawerTitle, 117 | DrawerDescription, 118 | } 119 | -------------------------------------------------------------------------------- /src/components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { Slot } from "@radix-ui/react-slot" 6 | import { 7 | Controller, 8 | ControllerProps, 9 | FieldPath, 10 | FieldValues, 11 | FormProvider, 12 | useFormContext, 13 | } from "react-hook-form" 14 | 15 | import { cn } from "@/lib/utils" 16 | import { Label } from "@/components/ui/label" 17 | 18 | const Form = FormProvider 19 | 20 | type FormFieldContextValue< 21 | TFieldValues extends FieldValues = FieldValues, 22 | TName extends FieldPath = FieldPath 23 | > = { 24 | name: TName 25 | } 26 | 27 | const FormFieldContext = React.createContext( 28 | {} as FormFieldContextValue 29 | ) 30 | 31 | const FormField = < 32 | TFieldValues extends FieldValues = FieldValues, 33 | TName extends FieldPath = FieldPath 34 | >({ 35 | ...props 36 | }: ControllerProps) => { 37 | return ( 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | const useFormField = () => { 45 | const fieldContext = React.useContext(FormFieldContext) 46 | const itemContext = React.useContext(FormItemContext) 47 | const { getFieldState, formState } = useFormContext() 48 | 49 | const fieldState = getFieldState(fieldContext.name, formState) 50 | 51 | if (!fieldContext) { 52 | throw new Error("useFormField should be used within ") 53 | } 54 | 55 | const { id } = itemContext 56 | 57 | return { 58 | id, 59 | name: fieldContext.name, 60 | formItemId: `${id}-form-item`, 61 | formDescriptionId: `${id}-form-item-description`, 62 | formMessageId: `${id}-form-item-message`, 63 | ...fieldState, 64 | } 65 | } 66 | 67 | type FormItemContextValue = { 68 | id: string 69 | } 70 | 71 | const FormItemContext = React.createContext( 72 | {} as FormItemContextValue 73 | ) 74 | 75 | const FormItem = React.forwardRef< 76 | HTMLDivElement, 77 | React.HTMLAttributes 78 | >(({ className, ...props }, ref) => { 79 | const id = React.useId() 80 | 81 | return ( 82 | 83 |
      84 | 85 | ) 86 | }) 87 | FormItem.displayName = "FormItem" 88 | 89 | const FormLabel = React.forwardRef< 90 | React.ElementRef, 91 | React.ComponentPropsWithoutRef 92 | >(({ className, ...props }, ref) => { 93 | const { error, formItemId } = useFormField() 94 | 95 | return ( 96 |