diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..84fd1276 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,9 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool +that works with multi-package repos, or single-package repos to help you version and publish your +code. You can find the full documentation for it +[in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..96cdec90 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.npmignore b/.npmignore index 943f8cd7..f9f9d267 100644 --- a/.npmignore +++ b/.npmignore @@ -5,7 +5,9 @@ node_modules .idea assets .git -pages +.github +.changeset +app styles .next .rollup.cache diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3dbd1595 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# @sciendis/react-tailwindcss-datepicker + +## 1.8.1 + +### Patch Changes + +- fix dependency array in datepicker to prevent rerenders and fix max update depth bug + +## 1.8.0 + +### Minor Changes + +- switch back to rollup +- bring back explicit types + +## 1.7.4 (reverted) + +### Patch Changes + +- fix (hopefully) tsup config +- remove type folder from dist + +## 1.7.3 + +### Patch Changes + +- 1715a0a: explicitly export types diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 879010fa..fdbacfd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,27 +3,18 @@ Thanks for your interest in contributing to `react-tailwindcss-datepicker`! Please take a moment to review this document **before submitting a pull request**. -- [Pull requests](#pull-requests) - [Installation](#installation) - [Coding standards](#coding-standards) - [Running playground](#running-playgrounds) - [Before you make a Pull Request](#before-you-make-a-pull-request) - -## Pull requests - -**Please ask first before starting work on any significant new features.** - -It's never a fun experience to have your pull request declined after investing a lot of time and -effort into a new feature. To avoid this from happening, we request that contributors create -[an issue](https://github.com/onesine/react-tailwindcss-datepicker/issues) to first discuss any -significant new features. +- [Publish the updated Datepicker](#publish-the-updated-datepicker) ## Installation -You only require a `yarn install` in the root directory to install everything you need. +You only require a `npm install` in the root directory to install everything you need. ```sh -yarn install +npm install ``` ## Coding standards @@ -31,14 +22,6 @@ yarn install We use `prettier` for making sure that the codebase is formatted consistently. To automatically fix any style violations in your code, you can run: -**Using yarn** - -```sh -yarn pret:fix -``` - -**Using npm** - ```sh npm pret:fix ``` @@ -51,14 +34,6 @@ You can run the `dev` script and open your browser to `http://localhost:8888`. See complete `props` usage in `pages/index.js` file. -**Using yarn** - -```sh -yarn dev -``` - -**Using npm** - ```sh npm dev ``` @@ -71,12 +46,80 @@ Request **Let's clean the code first** ```sh -yarn pret:fix +npm pret:fix ``` **Test a build of your changes** ```sh -yarn build +npm build + +``` + +**Add a changeset** +Everytime you make a change you should add a changeset. Changesets hold two key bits of information: +a version type (following semver), and change information to be added to a changelog. For details, +check out the +[documentation](https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md) + +```sh +npx changeset +``` + +Follow the prompts to document your changes. + +**Commit your changes** + +It is generally advised to create a feature branch for your changes and open a merge request. + +Create a commit, preferrably using [Commitizen](https://commitizen-tools.github.io/commitizen/). + +```sh +git cz commit +git push +``` + +# Publish the updated Datepicker + +**Update npm package** + +To release a new version use + +```sh +npx changeset version +``` + +This consumes all changesets, and updates to the most appropriate semver version based on those +changesets. It also writes changelog entries for each consumed changeset. + +Make sure to review all changes. Once you are confident that these are correct, and have made any +necessary tweaks to changelogs, you can publish your packages: + +```sh +npx changeset publish +``` + +**Update github repo** + +Make sure to also commit und push the updated package and changelogs to github. + +```sh +git commit -m +git push +``` + +Add a tag with the latest version number to mark a new release. The tag name should reflect the +updated version property in the `package.json` file. + +```sh +git tag + +``` + +Note that tags are not automatically pushed. You will have to explicitly push tags to a shared +server after you have created them. + +```sh +git push origin ``` diff --git a/README.md b/README.md index 325ebfdb..2b48bc17 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,10 @@ React Tailwindcss Datepicker

- A modern date range picker component for React using Tailwind 3 and dayjs. Alternative to Litepie Datepicker which uses Vuejs. + A modern date range picker component for React using Tailwind 3 and dayjs.
+ Forked from React Tailwindcss Datepicker.

-
- -[![npm version](https://img.shields.io/npm/v/react-tailwindcss-datepicker?style=flat-square)](https://www.npmjs.com/package/react-tailwindcss-datepicker) -[![npm downloads](https://img.shields.io/npm/dt/react-tailwindcss-datepicker?style=flat-square)](https://www.npmjs.com/package/react-tailwindcss-datepicker) - -
- ## Contents - [Features](#features) @@ -51,7 +45,7 @@ Go to [full documentation](https://react-tailwindcss-datepicker.vercel.app/) ### Install via npm ``` -$ npm install react-tailwindcss-datepicker +$ npm install @sciendis/react-tailwindcss-datepicker ``` ### Install via yarn @@ -153,14 +147,16 @@ Open a browser and navigate to `http://localhost:8888` ## Contributing See -[CONTRIBUTING.md](https://github.com/onesine/react-tailwindcss-datepicker/blob/master/CONTRIBUTING.md) +[CONTRIBUTING.md](https://github.com/sciendis/react-tailwind-datepicker/blob/master/CONTRIBUTING.md) ## Official Documentation repo +Onesine's original documentation is still valid for this fork: [https://github.com/onesine/react-tailwindcss-datepicker-doc](https://github.com/onesine/react-tailwindcss-datepicker-doc) ## Thanks to +- [React Tailwindcss Datepicker ](https://github.com/onesine/react-tailwindcss-datepicker). - [Vue Tailwind Datepicker](https://vue-tailwind-datepicker.com/) - [React](https://reactjs.org/) - [Tailwind CSS](https://tailwindcss.com/) @@ -170,4 +166,4 @@ I thank you in advance for your contribution to this project. ## License -[MIT](LICENSE) Licensed. Copyright (c) Lewhe Onesine 2022. +[MIT](LICENSE) Licensed. diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..6dd60374 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ + +module.exports = { + testEnvironment: "node", + transform: { + "^.+.tsx?$": ["ts-jest", {}] + }, + testPathIgnorePatterns: [".rollup.cache", "dist"] +}; diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03d..a4a7b3f5 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. diff --git a/next.config.js b/next.config.js index eda88d1a..4678774e 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true -}; +const nextConfig = {}; -module.exports = nextConfig; +export default nextConfig; diff --git a/package.json b/package.json index ac9c06cf..de3f9df3 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,30 @@ { - "name": "react-tailwindcss-datepicker", - "version": "1.6.6", - "description": "A modern React Datepicker using Tailwind CSS 3", + "name": "@sciendis/react-tailwindcss-datepicker", + "version": "1.8.1", + "description": " Modern date range picker component for React using Tailwind and dayjs.", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts", - "author": "onesine", + "author": "sciendis", "license": "MIT", "scripts": { - "watch": "rollup -c -w", - "clean": "rm -rf dist", + "clean": "rm -rf dist tsconfig.tsbuildinfo .rollup.cache", "lint": "eslint --ignore-path .gitignore .", "lint:fix": "eslint --ignore-path .gitignore --fix .", "pret": "prettier -c .", "pret:fix": "prettier --ignore-path .gitignore --config ./.prettierrc --write './**/*.{js,jsx,ts,tsx,css,md,json}'", "code-style": "npm run pret && npm run lint", "code-style:fix": "npm run pret:fix && npm run pret:fix", - "build": "npm run code-style && npm run clean && rollup -c", + "build": "npm run code-style && npm run clean && rollup -c rollup.config.js --bundleConfigAsCjs", "pub": "npm run build && npm publish", "dev": "next dev -p 8888", - "prepare": "husky install" + "test": "jest", + "prepare": "husky install", + "ci": "pnpm run lint && pnpm run test && pnpm run build" }, "repository": { "type": "git", - "url": "https://github.com/onesine/react-tailwindcss-datepicker" + "url": "git+https://github.com/sciendis/react-tailwindcss-datepicker.git" }, "keywords": [ "react-tailwindcss-datepicker", @@ -42,12 +43,14 @@ "react": "^17.0.2 || ^18.2.0" }, "devDependencies": { - "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-typescript": "^11.0.0", + "@changesets/cli": "^2.27.9", + "@jest/globals": "^29.7.0", + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-typescript": "^12.1.1", "@tailwindcss/forms": "^0.5.3", - "@types/node": "18.14.5", - "@types/react": "^18.0.21", + "@types/node": "^22.7.5", + "@types/react": "^18.3.11", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.13", @@ -60,16 +63,18 @@ "eslint-plugin-react": "^7.31.11", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.0", - "lint-staged": "^13.2.3", - "next": "^13.1.1", + "jest": "^29.7.0", + "lint-staged": "^15.2.10", + "next": "^14.1.2", "postcss": "^8.4.19", "prettier": "^2.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^2.77.2", + "rollup": "^4.24.0", "tailwindcss": "^3.2.4", - "tslib": "^2.4.0", - "typescript": "^4.8.4" + "ts-jest": "^29.2.5", + "tslib": "^2.7.0", + "typescript": "^5.6.2" }, "lint-staged": { "*.{ts,tsx}": [ @@ -78,5 +83,12 @@ "*.{ts,tsx,css,scss,md}": [ "npm run pret:fix" ] - } + }, + "bugs": { + "url": "https://github.com/sciendis/react-tailwindcss-datepicker/issues" + }, + "homepage": "https://github.com/sciendis/react-tailwindcss-datepicker#readme", + "files": [ + "dist" + ] } diff --git a/pages/index.js b/pages/index.js index 5ce6034e..df6ba7f0 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,6 +1,6 @@ import dayjs from "dayjs"; import Head from "next/head"; -import { useState } from "react"; +import React, { useState } from "react"; import Datepicker from "../src"; import { COLORS, DATE_LOOKING_OPTIONS } from "../src/constants"; @@ -32,10 +32,8 @@ export default function Playground() { const [startFrom, setStartFrom] = useState("2023-03-01"); const [startWeekOn, setStartWeekOn] = useState(""); - const handleChange = (value, e) => { + const handleChange = value => { setValue(value); - console.log(e); - console.log("value", value); }; return (
@@ -482,7 +480,7 @@ export default function Playground() {
{ + const defaultClassName = "default-class"; + + test("should return result of toggleClassName function when it is a function", () => { + const toggleFunction = jest.fn(() => "custom-class"); + const result = checkClassName(defaultClassName, toggleFunction); + expect(result).toBe("custom-class"); + expect(toggleFunction).toHaveBeenCalledWith(defaultClassName); + }); + + test("should return toggleClassName when it is a non-empty string", () => { + const result = checkClassName(defaultClassName, "custom-class"); + expect(result).toBe("custom-class"); + }); + + test("should return defaultToggleClassName when toggleClassName is an empty string", () => { + const result = checkClassName(defaultClassName, ""); + expect(result).toBe(defaultClassName); + }); + + test("should return defaultToggleClassName when toggleClassName is null", () => { + const result = checkClassName(defaultClassName, null); + expect(result).toBe(defaultClassName); + }); + + test("should return defaultToggleClassName when toggleClassName is undefined", () => { + const result = checkClassName(defaultClassName, undefined); + expect(result).toBe(defaultClassName); + }); +}); diff --git a/src/__tests__/clearInvalidInput.test.ts b/src/__tests__/clearInvalidInput.test.ts new file mode 100644 index 00000000..b862f5ca --- /dev/null +++ b/src/__tests__/clearInvalidInput.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, test } from "@jest/globals"; + +import { clearInvalidInput } from "../helpers"; + +describe("clearInvalidInput", () => { + test("should return the same string if the last character is numeric", () => { + const input = "1234"; + const result = clearInvalidInput(input); + expect(result).toBe("1234"); + }); + + test("should remove the last character if it is non-numeric", () => { + const input = "1234a"; + const result = clearInvalidInput(input); + expect(result).toBe("1234"); + }); + + test("should return an empty string if the input is a single non-numeric character", () => { + const input = "a"; + const result = clearInvalidInput(input); + expect(result).toBe(""); + }); + + test("should return an empty string if the input is empty", () => { + const input = ""; + const result = clearInvalidInput(input); + expect(result).toBe(""); + }); + + test("should handle multiple non-numeric characters at the end by removing only the last one", () => { + const input = "1234abc"; + const result = clearInvalidInput(input); + expect(result).toBe("1234ab"); + }); + + test("should return the same string if all characters are numeric", () => { + const input = "9876543210"; + const result = clearInvalidInput(input); + expect(result).toBe("9876543210"); + }); + + test("should correctly handle a string with special characters at the end", () => { + const input = "1234#"; + const result = clearInvalidInput(input); + expect(result).toBe("1234"); + }); + + test("should correctly handle a string with spaces at the end", () => { + const input = "1234 "; + const result = clearInvalidInput(input); + expect(result).toBe("1234"); + }); +}); diff --git a/src/components/Calendar/Years.tsx b/src/components/Calendar/Years.tsx index 0bbef7b5..0431689d 100644 --- a/src/components/Calendar/Years.tsx +++ b/src/components/Calendar/Years.tsx @@ -1,10 +1,9 @@ import React, { useContext } from "react"; +import DatepickerContext from "../../contexts/DatepickerContext"; import { generateArrayNumber } from "../../helpers"; import { RoundedButton } from "../utils"; -import DatepickerContext from "contexts/DatepickerContext"; - interface Props { year: number; currentYear: number; diff --git a/src/components/Calendar/index.tsx b/src/components/Calendar/index.tsx index 4a374375..f5ca794c 100644 --- a/src/components/Calendar/index.tsx +++ b/src/components/Calendar/index.tsx @@ -14,6 +14,7 @@ import { nextMonth, previousMonth } from "../../helpers"; +import { DateType } from "../../types"; import { ChevronLeftIcon, ChevronRightIcon, @@ -27,8 +28,6 @@ import Months from "./Months"; import Week from "./Week"; import Years from "./Years"; -import { DateType } from "types"; - interface Props { date: dayjs.Dayjs; minDate?: DateType | null; diff --git a/src/components/Datepicker.tsx b/src/components/Datepicker.tsx index 54e90b78..dd8ad400 100644 --- a/src/components/Datepicker.tsx +++ b/src/components/Datepicker.tsx @@ -24,7 +24,7 @@ const Datepicker: React.FC = ({ asSingle = false, placeholder = null, separator = "~", - startFrom = null, + startFrom = new Date(), i18n = LANGUAGE, disabled = false, inputClassName = null, @@ -52,6 +52,7 @@ const Datepicker: React.FC = ({ const [firstDate, setFirstDate] = useState( startFrom && dayjs(startFrom).isValid() ? dayjs(startFrom) : dayjs() ); + const [secondDate, setSecondDate] = useState(nextMonth(firstDate)); const [period, setPeriod] = useState({ start: null, @@ -235,7 +236,7 @@ const Datepicker: React.FC = ({ setSecondDate(nextMonth(dayjs(startFrom))); } } - }, [asSingle, startFrom, value]); + }, [asSingle, startFrom?.getDate(), value]); // Variables const safePrimaryColor = useMemo(() => { @@ -333,7 +334,8 @@ const Datepicker: React.FC = ({
diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 38fc856e..2473f098 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -3,7 +3,13 @@ import React, { useCallback, useContext, useEffect, useRef } from "react"; import { BORDER_COLOR, DATE_FORMAT, RING_COLOR } from "../constants"; import DatepickerContext from "../contexts/DatepickerContext"; -import { dateIsValid, parseFormattedDate } from "../helpers"; +import { + checkClassName, + clearInvalidInput, + dateIsValid, + parseFormattedDate, + shortString +} from "../helpers"; import ToggleButton from "./ToggleButton"; @@ -51,22 +57,77 @@ const Input: React.FC = (e: Props) => { return classNames.input(input); } - const border = BORDER_COLOR.focus[primaryColor as keyof typeof BORDER_COLOR.focus]; - const ring = - RING_COLOR["second-focus"][primaryColor as keyof (typeof RING_COLOR)["second-focus"]]; + const border = BORDER_COLOR.focus[primaryColor]; + const ring = RING_COLOR["second-focus"][primaryColor]; const defaultInputClassName = `relative transition-all duration-300 py-2.5 pl-4 pr-14 w-full border-gray-300 dark:bg-slate-800 dark:text-white/80 dark:border-slate-600 rounded-lg tracking-wide font-light text-sm placeholder-gray-400 bg-white focus:ring disabled:opacity-40 disabled:cursor-not-allowed ${border} ${ring}`; - return typeof inputClassName === "function" - ? inputClassName(defaultInputClassName) - : typeof inputClassName === "string" && inputClassName !== "" - ? inputClassName - : defaultInputClassName; - }, [inputRef, classNames, primaryColor, inputClassName]); + return checkClassName(defaultInputClassName, inputClassName); + }, [classNames, inputClassName, primaryColor]); + + /** + * automatically adds correct separator character to date input + */ + const addSeparatorToDate = useCallback( + (inputValue: string, displayFormat: string) => { + // fallback separator; replaced by user defined separator; + const separators = ["-", "-"]; + const separatorIndices: number[] = []; + let formattedInput = inputValue; + + // note that we are not using locale to avoid redundancy; + // instead preferred locale is determined by displayFormat + const localeSeparators = displayFormat.match(/\W/g); + if (localeSeparators?.length) { + // replace fallbacks with localized separators + separators.splice(0, separators.length, ...localeSeparators); + } + + // find indices of separators + // required to distinguish between i.a. YDM and DMY + let start = 0; + separators.forEach(localeSeparator => { + const idx = displayFormat.indexOf(localeSeparator, start); + if (idx !== -1) { + start = idx + 1; + separatorIndices.push(idx); + } + }); + + // adding separator after day and month + separatorIndices.forEach((separatorIndex, idx) => { + if (inputValue.length === separatorIndex) { + formattedInput = inputValue + separators[idx]; + } + }); + + // add middle separator for range dates and format end date + if (!asSingle && inputValue.length >= displayFormat.length) { + // get startDate and add separator + let rangeDate = inputValue.substring(0, displayFormat.length); + rangeDate = rangeDate + " " + separator + " "; + + // cut off everything startdate and separator including blank spaces + let endDate = inputValue.substring(displayFormat.length + 2 + separator.length); + if (endDate.length) { + separatorIndices.forEach((separatorIndex, idx) => { + if (endDate.length === separatorIndex) { + endDate = endDate + separators[idx]; + } + }); + rangeDate = rangeDate + endDate; + } + return rangeDate; + } + + return formattedInput; + }, + [asSingle, separator] + ); const handleInputChange = useCallback( (e: React.ChangeEvent) => { - const inputValue = e.target.value; + const inputValue = clearInvalidInput(e.target.value); const dates = []; @@ -112,13 +173,40 @@ const Input: React.FC = (e: Props) => { else changeDayHover(dates[0]); } - changeInputText(e.target.value); + changeInputText(addSeparatorToDate(inputValue, displayFormat)); }, - [asSingle, displayFormat, separator, changeDatepickerValue, changeDayHover, changeInputText] + [ + addSeparatorToDate, + asSingle, + changeDatepickerValue, + changeDayHover, + changeInputText, + displayFormat, + separator + ] ); const handleInputKeyDown = useCallback( (e: React.KeyboardEvent) => { + if (e.key === "Backspace") { + // stop propagation + e.preventDefault(); + + // force deletion of separators + const input = inputRef.current; + // necessary because the addSeparator function will overwrite regular deletion + if (input?.value.length) { + let lastChar = input.value[input.value.length - 1]; + // cut off all non-numeric values + while (RegExp(/\D/).exec(lastChar)) { + const shortenedString = shortString(input.value, input.value.length - 1); + input.value = shortenedString; + lastChar = shortenedString[shortenedString.length - 1]; + } + // cut off last numeric value + input.value = shortString(input.value, input.value.length - 1); + } + } if (e.key === "Enter") { const input = inputRef.current; if (input) { @@ -155,12 +243,8 @@ const Input: React.FC = (e: Props) => { const defaultToggleClassName = "absolute right-0 h-full px-3 text-gray-400 focus:outline-none disabled:opacity-40 disabled:cursor-not-allowed"; - return typeof toggleClassName === "function" - ? toggleClassName(defaultToggleClassName) - : typeof toggleClassName === "string" && toggleClassName !== "" - ? toggleClassName - : defaultToggleClassName; - }, [toggleClassName, buttonRef, classNames]); + return checkClassName(defaultToggleClassName, toggleClassName); + }, [toggleClassName, classNames]); // UseEffects && UseLayoutEffect useEffect(() => { @@ -222,7 +306,7 @@ const Input: React.FC = (e: Props) => { const arrow = arrowContainer?.current; function showCalendarContainer() { - if (arrow && div && div.classList.contains("hidden")) { + if (arrow && div?.classList.contains("hidden")) { div.classList.remove("hidden"); div.classList.add("block"); @@ -274,15 +358,13 @@ const Input: React.FC = (e: Props) => { disabled={disabled} readOnly={readOnly} placeholder={ - placeholder - ? placeholder - : `${displayFormat}${asSingle ? "" : ` ${separator} ${displayFormat}`}` + placeholder || + `${displayFormat}${asSingle ? "" : ` ${separator} ${displayFormat}`}` } value={inputText} id={inputId} name={inputName} autoComplete="off" - role="presentation" onChange={handleInputChange} onKeyDown={handleInputKeyDown} /> @@ -293,7 +375,7 @@ const Input: React.FC = (e: Props) => { disabled={disabled} className={getToggleClassName()} > - {renderToggleIcon(inputText == null || !inputText?.length)} + {renderToggleIcon(!inputText.length)} ); diff --git a/src/components/utils.tsx b/src/components/utils.tsx index 6bdb31a7..e61f13ed 100644 --- a/src/components/utils.tsx +++ b/src/components/utils.tsx @@ -119,11 +119,12 @@ export const DoubleChevronRightIcon: React.FC = ({ className = "w-6 h }; // eslint-disable-next-line react/display-name,@typescript-eslint/ban-types -export const Arrow = React.forwardRef((props, ref) => { +export const Arrow = React.forwardRef((_props, ref) => { return (
); }); diff --git a/src/contexts/DatepickerContext.ts b/src/contexts/DatepickerContext.ts index d42aadd8..cacdc9d3 100644 --- a/src/contexts/DatepickerContext.ts +++ b/src/contexts/DatepickerContext.ts @@ -3,14 +3,15 @@ import React, { createContext } from "react"; import { DATE_FORMAT, LANGUAGE, START_WEEK } from "../constants"; import { + ClassNamesTypeProp, + ClassType, + ColorKeys, Configs, - Period, - DateValueType, - DateType, DateRangeType, - ClassNamesTypeProp, - PopoverDirectionType, - ColorKeys + DateType, + DateValueType, + Period, + PopoverDirectionType } from "../types"; interface DatepickerStore { @@ -35,9 +36,9 @@ interface DatepickerStore { i18n: string; value: DateValueType; disabled?: boolean; - inputClassName?: ((className: string) => string) | string | null; - containerClassName?: ((className: string) => string) | string | null; - toggleClassName?: ((className: string) => string) | string | null; + inputClassName?: ClassType; + containerClassName?: ClassType; + toggleClassName?: ClassType; toggleIcon?: (open: boolean) => React.ReactNode; readOnly?: boolean; startWeekOn?: string | null; @@ -60,19 +61,19 @@ const DatepickerContext = createContext({ arrowContainer: null, period: { start: null, end: null }, // eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars - changePeriod: period => {}, + changePeriod: _period => {}, // eslint-disable-next-line @typescript-eslint/no-empty-function hideDatepicker: () => {}, dayHover: null, // eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars - changeDayHover: (day: string | null) => {}, + changeDayHover: (_day: string | null) => {}, inputText: "", // eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars - changeInputText: text => {}, + changeInputText: _text => {}, // eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars - updateFirstDate: date => {}, + updateFirstDate: _date => {}, // eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars - changeDatepickerValue: (value: DateValueType, e: HTMLInputElement | null | undefined) => {}, + changeDatepickerValue: (_value: DateValueType, _e: HTMLInputElement | null | undefined) => {}, showFooter: false, value: null, i18n: LANGUAGE, diff --git a/src/helpers/index.ts b/src/helpers/index.ts index d59d919a..83abb85e 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -11,6 +11,33 @@ export function classNames(...classes: (false | null | undefined | string)[]) { return classes.filter(Boolean).join(" "); } +/** + * detect and delete non-numeric user input + * @returns shortened string + */ +export const clearInvalidInput = (value: string): string => { + if (value[value.length - 1]?.match(/\D/g)) { + return shortString(value, value.length - 1); + } + return value; +}; + +/** + * checks and returns user-defined className or default className + */ +export const checkClassName = ( + defaultToggleClassName: string, + toggleClassName?: string | ((className: string) => string) | null +) => { + if (typeof toggleClassName === "function") { + return toggleClassName(defaultToggleClassName); + } + if (typeof toggleClassName === "string" && toggleClassName !== "") { + return toggleClassName; + } + return defaultToggleClassName; +}; + export function getTextColorByPrimaryColor(color: string) { switch (color) { case "blue": diff --git a/src/index.tsx b/src/index.tsx index 1a4ded83..d0b0828b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,3 @@ import Datepicker from "./components/Datepicker"; - export * from "./types"; export default Datepicker; diff --git a/src/types/index.ts b/src/types/index.ts index b9e561cd..940f510c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -44,6 +44,8 @@ export type DateRangeType = { export type DateValueType = DateRangeType | null; +export type ClassType = ((className: string) => string) | string | null; + export type ClassNamesTypeProp = { container?: (p?: object | null | undefined) => string | undefined; input?: (p?: object | null | undefined) => string | undefined; @@ -67,10 +69,10 @@ export interface DatepickerType { startFrom?: Date | null; i18n?: string; disabled?: boolean; - classNames?: ClassNamesTypeProp | undefined; - containerClassName?: ((className: string) => string) | string | null; - inputClassName?: ((className: string) => string) | string | null; - toggleClassName?: ((className: string) => string) | string | null; + classNames?: ClassNamesTypeProp; + containerClassName?: ClassType; + inputClassName?: ClassType; + toggleClassName?: ClassType; toggleIcon?: (open: boolean) => React.ReactNode; inputId?: string; inputName?: string; diff --git a/tsconfig.json b/tsconfig.json index cf845ae2..fc9fbef9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,9 @@ "target": "esnext", "lib": ["dom", "esnext"], "module": "esnext", - "jsx": "preserve", + "jsx": "react-jsx", + "noUnusedLocals": true, + "noUnusedParameters": true, "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "strict": true, @@ -12,16 +14,24 @@ "esModuleInterop": true, "baseUrl": "src/", "declaration": true, + "declarationDir": "./dist", "outDir": "./dist", "inlineSources": true, "sourceMap": true, "rootDir": "src", "allowJs": true, + "plugins": [ + { + "name": "next" + } + ], "skipLibCheck": true, "noEmit": true, "resolveJsonModule": true, - "isolatedModules": true + "isolatedModules": true, + // "noUncheckedIndexedAccess": true // should be enabled + "incremental": true }, "include": ["src/**/*"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "dist", "src/**/*.spec.ts", "src/**/*.test.ts", "src/**/__tests__"] }