Skip to content

Commit

Permalink
Merge pull request #43 from gaurangrshah/date-time-input
Browse files Browse the repository at this point in the history
Cleans up: Date time input
  • Loading branch information
BelkacemYerfa authored Apr 9, 2024
2 parents 9fb4267 + 40428d9 commit 281f6de
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 34 deletions.
14 changes: 10 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@
"source.fixAll.eslint": "always"
},
"reccomendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"streetsidesoftware.code-spell-checker"
]
"deque-systems.vscode-axe-linter",
"esbenp.prettier-vscode",
"github.vscode-pull-request-github",
"oouo-diogo-perdigao.docthis",
"streetsidesoftware.code-spell-checker",
"suhelmakkad.shadcn-ui",
"unifiedjs.vscode-mdx",
"usernamehw.errorlens"
],
"typescript.tsdk": "node_modules/typescript/lib"
}
79 changes: 49 additions & 30 deletions src/registry/default/extension/smart-datetime-input.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"use client";

import React from "react";
import { parseDate } from "chrono-node";

import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import * as chrono from "chrono-node";
import { Calendar } from "lucide-react";
import React, { useState } from "react";
import { Button } from "react-day-picker";

/* -------------------------------------------------------------------------- */
/* Inspired By: */
Expand All @@ -22,14 +21,15 @@ import { Button } from "react-day-picker";
*/
export const parseDateTime = (str: Date | string) => {
if (str instanceof Date) return str;
return chrono.parseDate(str);
return parseDate(str);
};

/**
* Converts a given timestamp or the current date and time to a string representation in the local time zone.
* format: `HH:mm`, adjusted for the local time zone.
*
* @param timestamp {Date | string}
* @returns A string representation of the timestamp in the format `HH:mm`, adjusted for the local time zone.
* @returns A string representation of the timestamp
*/
export const getDateTimeLocal = (timestamp?: Date): string => {
const d = timestamp ? new Date(timestamp) : new Date();
Expand All @@ -43,9 +43,10 @@ export const getDateTimeLocal = (timestamp?: Date): string => {

/**
* Formats a given date and time object or string into a human-readable string representation.
* "MMM D, YYYY h:mm A" (e.g. "Jan 1, 2023 12:00 PM").
*
* @param datetime - {Date | string}
* @returns A string representation of the date and time in the format "MMM D, YYYY h:mm A" (e.g. "Jan 1, 2023 12:00 PM").
* @returns A string representation of the date and time
*/
export const formatDateTime = (datetime: Date | string) => {
return new Date(datetime).toLocaleTimeString("en-US", {
Expand All @@ -59,7 +60,12 @@ export const formatDateTime = (datetime: Date | string) => {
};

const inputBase =
"focus:outline-none focus:ring-0 focus-within:outline-none focus-within:ring-0sm:text-sm disabled:cursor-not-allowed disabled:opacity-50";
"bg-transparent focus:outline-none focus:ring-0 focus-within:outline-none focus-within:ring-0sm:text-sm disabled:cursor-not-allowed disabled:opacity-50";

// @source: https://www.perplexity.ai/search/in-javascript-how-RfI7fMtITxKr5c.V9Lv5KA#1
// use this pattern to validate the transformed date string for the natural language input
const naturalInputValidationPattern =
"^[A-Z][a-z]{2}sd{1,2},sd{4},sd{1,2}:d{2}s[AP]M$";

const NaturalLanguageInput = React.forwardRef<
HTMLInputElement,
Expand All @@ -71,24 +77,24 @@ const NaturalLanguageInput = React.forwardRef<
}
>(({ placeholder, value, onChange }, ref) => {
const _placeholder = placeholder ?? 'e.g. "tomorrow at 5pm" or "in 2 hours"';

const handleParse = (e: React.ChangeEvent<HTMLInputElement>) => {
// parse the date string when the input field loses focus
const parsedDateTime = parseDateTime(e.target.value);
if (parsedDateTime) {
onChange(parsedDateTime);
e.target.value = formatDateTime(parsedDateTime);
}
};

return (
<Input
ref={ref}
type="text"
placeholder={_placeholder}
pattern="^[A-Z][a-z]{2}\s\d{1,2},\s\d{4},\s\d{1,2}:\d{2}\s[AP]M$"
defaultValue={value ? formatDateTime(value) : ""}
onBlur={(e) => {
// parse the date string when the input field loses focus
if (e.target.value.length > 0) {
const parsedDateTime = parseDateTime(e.target.value);
if (parsedDateTime) {
onChange(parsedDateTime);
e.target.value = formatDateTime(parsedDateTime);
}
}
}}
className={cn("px-2 mr-1 flex-1 border-none bg-transparent", inputBase)}
onBlur={handleParse}
className={cn("px-2 mr-1 flex-1 border-none", inputBase)}
/>
);
});
Expand All @@ -115,8 +121,28 @@ const DateTimeLocalInput = React.forwardRef<
ref.current = inputRef.current;
}
}

return () => {
// cleanup
if (ref) {
if (typeof ref === "function") {
ref(null);
} else {
ref.current = null;
}
}
};
}, [ref]);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const expiryDate = new Date(e.target.value);
onChange(expiryDate);
// set the formatted date string in the text input field to keep them in sync
if (inputRef.current) {
inputRef.current.value = formatDateTime(expiryDate);
}
};

const _name = name ?? "expiresAt";
return (
<div className="group">
Expand All @@ -127,16 +153,9 @@ const DateTimeLocalInput = React.forwardRef<
id={_name}
name={_name}
value={value ? getDateTimeLocal(value) : ""}
onChange={(e) => {
const expiryDate = new Date(e.target.value);
onChange(expiryDate);
// set the formatted date string in the text input field to keep them in sync
if (inputRef.current) {
inputRef.current.value = formatDateTime(expiryDate);
}
}}
onChange={handleChange}
className={cn(
"peer flex justify-end w-[44px] bg-transparent text-gray-500 z-[-1]",
"peer flex justify-end w-[44px] text-gray-500 z-[-1]",
inputBase
)}
/>
Expand Down Expand Up @@ -165,7 +184,7 @@ export const SmartDatetimeInput = React.forwardRef<
{ className, name, defaultValue, value, onChange, placeholder, disabled },
ref
) => {
const [dateTime, setDateTime] = useState<Date | undefined>(
const [dateTime, setDateTime] = React.useState<Date | undefined>(
defaultValue ?? value ?? undefined
);

Expand Down

0 comments on commit 281f6de

Please sign in to comment.