From a6c74c29f659a742bd0d13c4d2e235404fd5f748 Mon Sep 17 00:00:00 2001 From: Ben Keen Date: Tue, 26 Nov 2024 21:40:14 -0800 Subject: [PATCH] Beginning TS conversion --- package.json | 4 +- rollup.config.js | 10 +- src/CountryDropdown.js | 122 ----------------------- src/CountryDropdown.tsx | 97 ++++++++++++++++++ src/RegionDropdown.js | 214 ---------------------------------------- src/RegionDropdown.tsx | 153 ++++++++++++++++++++++++++++ src/constants.js | 6 -- src/constants.ts | 2 + src/index.js | 5 - src/index.ts | 5 + src/rcrs.types.ts | 40 ++++++++ tsconfig.json | 10 ++ yarn.lock | 13 +++ 13 files changed, 330 insertions(+), 351 deletions(-) delete mode 100644 src/CountryDropdown.js create mode 100644 src/CountryDropdown.tsx delete mode 100644 src/RegionDropdown.js create mode 100644 src/RegionDropdown.tsx delete mode 100644 src/constants.js create mode 100644 src/constants.ts delete mode 100644 src/index.js create mode 100644 src/index.ts create mode 100644 src/rcrs.types.ts create mode 100644 tsconfig.json diff --git a/package.json b/package.json index 37df1c2..c26d7ff 100644 --- a/package.json +++ b/package.json @@ -43,12 +43,14 @@ "react-dom": "^16.4.1", "react-scripts": "^5.0.1", "rollup": "^4.27.4", + "typescript": "^5.7.2", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-terser": "0.4.4", - "@rollup/plugin-url": "^8.0.2" + "@rollup/plugin-url": "^8.0.2", + "@rollup/plugin-typescript": "^12.1.1" }, "files": [ "dist", diff --git a/rollup.config.js b/rollup.config.js index 194ebad..0e4d9f8 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,6 +6,7 @@ const terser = require('@rollup/plugin-terser'); const pkg = require('./package.json'); const argv = require('minimist')(process.argv.slice(2)); const parseCountryList = require('./rollup-plugin-parse-country-list'); +const typescript = require('@rollup/plugin-typescript'); // e.g. rollup -c --config-countries=GB,CA,US let countries = []; @@ -14,7 +15,7 @@ if (argv.hasOwnProperty('config-countries')) { } module.exports = { - input: 'src/index.js', + input: 'src/index.ts', output: [ { file: pkg.main, @@ -39,8 +40,11 @@ module.exports = { // TODO check https://github.com/rollup/plugins/tree/master/packages/babel#babelhelpers babelHelpers: 'bundled', }), - resolve(), + resolve({ + extensions: ['.ts', '.tsx', '.js'], + }), terser(), + typescript(), ], - external: ['react', 'react-dom', 'prop-types'], + external: ['react', 'react-dom', 'react/jsx-runtime'], }; diff --git a/src/CountryDropdown.js b/src/CountryDropdown.js deleted file mode 100644 index 8e7acbf..0000000 --- a/src/CountryDropdown.js +++ /dev/null @@ -1,122 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import CountryRegionData from '../node_modules/country-region-data/data.json'; -import C from './constants'; -import * as helpers from './helpers'; - -export default class CountryDropdown extends Component { - constructor(props) { - super(props); - - this.state = { - countries: helpers.filterCountries( - CountryRegionData, - props.priorityOptions, - props.whitelist, - props.blacklist - ), - }; - } - - getCountries() { - const { valueType, labelType } = this.props; - - return this.state.countries.map(([countryName, countrySlug]) => ( - - )); - } - - getDefaultOption() { - const { showDefaultOption, defaultOptionLabel } = this.props; - if (!showDefaultOption) { - return null; - } - return ( - - ); - } - - render() { - // unused properties deliberately added so arbitraryProps gets populated with anything else the user specifies - const { - name, - id, - classes, - value, - onChange, - onBlur, - disabled, - showDefaultOption, - defaultOptionLabel, - labelType, - valueType, - whitelist, - blacklist, - customOptions, - priorityOptions, - ...arbitraryProps - } = this.props; - - const attrs = { - ...arbitraryProps, - name, - value, - onChange: (e) => onChange(e.target.value, e), - onBlur: (e) => onBlur(e.target.value, e), - disabled, - }; - if (id) { - attrs.id = id; - } - if (classes) { - attrs.className = classes; - } - - return ( - - ); - } -} - -CountryDropdown.propTypes = { - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - name: PropTypes.string, - id: PropTypes.string, - classes: PropTypes.string, - showDefaultOption: PropTypes.bool, - defaultOptionLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - priorityOptions: PropTypes.array, - onChange: PropTypes.func, - onBlur: PropTypes.func, - labelType: PropTypes.oneOf([C.DISPLAY_TYPE_FULL, C.DISPLAY_TYPE_SHORT]), - valueType: PropTypes.oneOf([C.DISPLAY_TYPE_FULL, C.DISPLAY_TYPE_SHORT]), - whitelist: PropTypes.array, - blacklist: PropTypes.array, - disabled: PropTypes.bool, -}; -CountryDropdown.defaultProps = { - value: '', - name: 'rcrs-country', - id: '', - classes: '', - showDefaultOption: true, - defaultOptionLabel: 'Select Country', - priorityOptions: [], - onChange: () => {}, - onBlur: () => {}, - labelType: C.DISPLAY_TYPE_FULL, - valueType: C.DISPLAY_TYPE_FULL, - whitelist: [], - blacklist: [], - disabled: false, -}; diff --git a/src/CountryDropdown.tsx b/src/CountryDropdown.tsx new file mode 100644 index 0000000..5b32cf7 --- /dev/null +++ b/src/CountryDropdown.tsx @@ -0,0 +1,97 @@ +import React, { FC } from 'react'; +import CountryRegionData from 'country-region-data/data.json'; +import * as C from './constants'; +import * as helpers from './helpers'; +import type { CountryDropdownProps } from './rcrs.types'; + +export const CountryDropdown: FC = ({ + value = '', + name = 'rcrs-country', + id = '', + classes = '', + showDefaultOption = true, + defaultOptionLabel = 'Select Country', + priorityOptions = [], + onChange = () => {}, + onBlur = () => {}, + labelType = 'full', + valueType = 'full', + whitelist = [], + blacklist = [], + disabled = false, + // ...arbitraryProps +}) => { + // countries: helpers.filterCountries( + // CountryRegionData, + // props.priorityOptions, + // props.whitelist, + // props.blacklist + // ), + // }; + + const getCountries = () => { + // return countries.map(([countryName, countrySlug]) => ( + // + // )); + }; + + const getDefaultOption = () => { + // const { showDefaultOption, defaultOptionLabel } = this.props; + // if (!showDefaultOption) { + // return null; + // } + // return ( + // + // ); + }; + + // render() { + // // unused properties deliberately added so arbitraryProps gets populated with anything else the user specifies + // const { + // name, + // id, + // classes, + // value, + // onChange, + // onBlur, + // disabled, + // showDefaultOption, + // defaultOptionLabel, + // labelType, + // valueType, + // whitelist, + // blacklist, + // customOptions, + // priorityOptions, + // ...arbitraryProps + // } = this.props; + + const attrs: any = { + ...arbitraryProps, + name, + value, + onChange: (e) => onChange(e.target.value, e), + onBlur: (e) => onBlur(e.target.value, e), + disabled, + }; + if (id) { + attrs.id = id; + } + if (classes) { + attrs.className = classes; + } + + return ( + + ); +}; diff --git a/src/RegionDropdown.js b/src/RegionDropdown.js deleted file mode 100644 index dce80be..0000000 --- a/src/RegionDropdown.js +++ /dev/null @@ -1,214 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import CountryRegionData from '../node_modules/country-region-data/data.json'; -import { filterRegions } from './helpers'; -import C from './constants'; - -export default class RegionDropdown extends PureComponent { - constructor(props) { - super(props); - this.state = { - regions: this.getRegions(props.country), - }; - this.getRegions = this.getRegions.bind(this); - } - - componentDidUpdate(prevProps) { - const { country } = this.props; - if (country === prevProps.country) { - return; - } - - const defaultRegions = this.getRegions(country); - - this.setState({ - regions: [...defaultRegions, ...this.getCustomOptions(defaultRegions)], - }); - } - - getCustomOptions(regions) { - const { customOptions } = this.props; - - const duplicateRegions = this.getDuplicates(regions); - - if (duplicateRegions.length) { - console.error( - 'Error: Duplicate regions present: ' + - duplicateRegions.toString() + - '.\nThe above item(s) is/are already getting added to the region dropdown by the library.' - ); - return []; - } - - return customOptions.map((option) => { - if (option) { - return { regionName: option, regionShortCode: option }; - } - }); - } - - getDuplicates(regions) { - const { customOptions, valueType } = this.props; - const regionKey = - valueType === C.DISPLAY_TYPE_FULL ? 'regionName' : 'regionShortCode'; - - return regions - .filter((region) => customOptions.indexOf(region[regionKey]) !== -1) - .map((region) => region[regionKey]); - } - - getRegions(country) { - if (!country) { - return []; - } - - const { countryValueType, whitelist, blacklist } = this.props; - const searchIndex = countryValueType === C.DISPLAY_TYPE_FULL ? 0 : 1; - let regions = []; - CountryRegionData.forEach((i) => { - if (i[searchIndex] === country) { - regions = i; - } - }); - - // this could happen if the user is managing the state of the region/country themselves and screws up passing - // in a valid country - if (!regions || regions.length === 0) { - console.error( - 'Error. Unknown country passed: ' + - country + - '. If you\'re passing a country shortcode, be sure to include countryValueType="short" on the RegionDropdown' - ); - return []; - } - - const filteredRegions = filterRegions(regions, whitelist, blacklist); - - return filteredRegions[2] - .split(C.REGION_LIST_DELIMITER) - .map((regionPair) => { - let [regionName, regionShortCode = null] = regionPair.split( - C.SINGLE_REGION_DELIMITER - ); - return { regionName, regionShortCode }; - }); - } - - getRegionList() { - const { labelType, valueType } = this.props; - return this.state.regions.map(({ regionName, regionShortCode }) => { - const label = - labelType === C.DISPLAY_TYPE_FULL ? regionName : regionShortCode; - const value = - valueType === C.DISPLAY_TYPE_FULL ? regionName : regionShortCode; - return ( - - ); - }); - } - - // there are two default options. The "blank" option which shows up when the user hasn't selected a country yet, and - // a "default" option which shows - getDefaultOption() { - const { blankOptionLabel, showDefaultOption, defaultOptionLabel, country } = - this.props; - if (!country) { - return ; - } - if (showDefaultOption) { - return ; - } - return null; - } - - render() { - const { - value, - country, - onChange, - onBlur, - id, - name, - classes, - disabled, - blankOptionLabel, - showDefaultOption, - defaultOptionLabel, - labelType, - valueType, - countryValueType, - disableWhenEmpty, - customOptions, - ...arbitraryProps - } = this.props; - - const isDisabled = disabled || (disableWhenEmpty && country === ''); - const attrs = { - ...arbitraryProps, - name, - value, - onChange: (e) => onChange(e.target.value, e), - onBlur: (e) => onBlur(e.target.value, e), - disabled: isDisabled, - }; - if (id) { - attrs.id = id; - } - if (classes) { - attrs.className = classes; - } - - return ( - - ); - } -} - -RegionDropdown.propTypes = { - country: PropTypes.string, - countryValueType: PropTypes.oneOf([ - C.DISPLAY_TYPE_FULL, - C.DISPLAY_TYPE_SHORT, - ]), - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - name: PropTypes.string, - id: PropTypes.string, - classes: PropTypes.string, - blankOptionLabel: PropTypes.string, - showDefaultOption: PropTypes.bool, - defaultOptionLabel: PropTypes.string, - onChange: PropTypes.func, - onBlur: PropTypes.func, - labelType: PropTypes.string, - valueType: PropTypes.string, - whitelist: PropTypes.object, - blacklist: PropTypes.object, - disabled: PropTypes.bool, - disableWhenEmpty: PropTypes.bool, - customOptions: PropTypes.array, -}; -RegionDropdown.defaultProps = { - country: '', - value: '', - name: 'rcrs-region', - id: '', - classes: '', - blankOptionLabel: '-', - showDefaultOption: true, - defaultOptionLabel: 'Select Region', - onChange: () => {}, - onBlur: () => {}, - countryValueType: C.DISPLAY_TYPE_FULL, - labelType: C.DISPLAY_TYPE_FULL, - valueType: C.DISPLAY_TYPE_FULL, - whitelist: {}, - blacklist: {}, - disabled: false, - disableWhenEmpty: false, - customOptions: [], -}; diff --git a/src/RegionDropdown.tsx b/src/RegionDropdown.tsx new file mode 100644 index 0000000..ae0223b --- /dev/null +++ b/src/RegionDropdown.tsx @@ -0,0 +1,153 @@ +import { FC, useMemo } from 'react'; +import CountryRegionData from 'country-region-data/data.json'; +import { filterRegions } from './helpers'; +import * as C from './constants'; +import type { RegionDropdownProps } from './rcrs.types'; + +export const RegionDropdown: FC = ({ + onChange, + country = '', + value = '', + onBlur = () => null, + id = '', + name = 'rcrs-region', + classes = '', + disabled = false, + blankOptionLabel = '-', + showDefaultOption = true, + defaultOptionLabel = 'Select Region', + labelType = 'full', + valueType = 'full', + countryValueType = 'full', + disableWhenEmpty = false, + customOptions = [], + whitelist = {}, + blacklist = {}, + // ...arbitraryProps +}) => { + const regions = useMemo(() => { + if (!country) { + return []; + } + + const searchIndex = countryValueType === 'full' ? 0 : 1; + let regionArray = []; + CountryRegionData.forEach((i) => { + if (i[searchIndex] === country) { + regionArray = i; + } + }); + + // this could happen if the user is managing the state of the region/country themselves and screws up passing + // in a valid country + if (!regions || regions.length === 0) { + console.error( + 'Error. Unknown country passed: ' + + country + + '. If you\'re passing a country shortcode, be sure to include countryValueType="short" on the RegionDropdown' + ); + return []; + } + const filteredRegions = filterRegions(regions, whitelist, blacklist); + + return filteredRegions[2] + .split(C.REGION_LIST_DELIMITER) + .map((regionPair) => { + let [regionName, regionShortCode = null] = regionPair.split( + C.SINGLE_REGION_DELIMITER + ); + + const label = labelType === 'full' ? regionName : regionShortCode; + const value = valueType === 'full' ? regionName : regionShortCode; + + return ( + + ); + }); + }, [country, countryValueType, whitelist, blacklist]); + + // useEffect(() => { + + // }, []); + + // componentDidUpdate(prevProps) { + // const { country } = this.props; + // if (country === prevProps.country) { + // return; + // } + + // const defaultRegions = this.getRegions(country); + + // this.setState({ + // regions: [...defaultRegions, ...this.getCustomOptions(defaultRegions)], + // }); + // } + + // const getCustomOptions = (regions) => { + // const { customOptions } = this.props; + + // const duplicateRegions = this.getDuplicates(regions); + + // if (duplicateRegions.length) { + // console.error( + // 'Error: Duplicate regions present: ' + + // duplicateRegions.toString() + + // '.\nThe above item(s) is/are already getting added to the region dropdown by the library.' + // ); + // return []; + // } + + // return customOptions.map((option) => { + // if (option) { + // return { regionName: option, regionShortCode: option }; + // } + // }); + // }; + + // getDuplicates(regions) { + // const { customOptions, valueType } = this.props; + // const regionKey = + // valueType === 'full' ? 'regionName' : 'regionShortCode'; + + // return regions + // .filter((region) => customOptions.indexOf(region[regionKey]) !== -1) + // .map((region) => region[regionKey]); + // } + + // there are two default options. The "blank" option which shows up when the user hasn't selected a country yet, and + // a "default" option which shows + const getDefaultOption = () => { + if (!country) { + return ; + } + if (showDefaultOption) { + return ; + } + return null; + }; + + const isDisabled = disabled || (disableWhenEmpty && country === ''); + const attrs: any = { + ...arbitraryProps, + name, + value, + onChange: (e) => onChange(e.target.value, e), + onBlur: (e) => onBlur(e.target.value, e), + disabled: isDisabled, + }; + if (id) { + attrs.id = id; + } + if (classes) { + attrs.className = classes; + } + + return ( + + ); +}; diff --git a/src/constants.js b/src/constants.js deleted file mode 100644 index adf582e..0000000 --- a/src/constants.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - DISPLAY_TYPE_FULL: 'full', - DISPLAY_TYPE_SHORT: 'short', - REGION_LIST_DELIMITER: '|', - SINGLE_REGION_DELIMITER: '~', -}; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..5341d86 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,2 @@ +export const REGION_LIST_DELIMITER = '|'; +export const SINGLE_REGION_DELIMITER = '~'; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 297e370..0000000 --- a/src/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import CountryDropdown from './CountryDropdown'; -import RegionDropdown from './RegionDropdown'; -import CountryRegionData from '../node_modules/country-region-data/data.json'; - -export { CountryDropdown, RegionDropdown, CountryRegionData }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7b5f48e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +import { CountryDropdown } from './CountryDropdown'; +import { RegionDropdown } from './RegionDropdown'; +import CountryRegionData from 'country-region-data/data.json'; + +export { CountryDropdown, RegionDropdown, CountryRegionData }; diff --git a/src/rcrs.types.ts b/src/rcrs.types.ts new file mode 100644 index 0000000..77c8952 --- /dev/null +++ b/src/rcrs.types.ts @@ -0,0 +1,40 @@ +export type DisplayType = 'full' | 'short'; + +export type CountryDropdownProps = { + readonly name: string; + readonly id: string; + readonly classes: string; + readonly value: string; + readonly onChange?: (value: string, event: any) => null; + readonly onBlur?: (value: string, event: any) => null; + readonly disabled: boolean; + readonly showDefaultOption: boolean; + readonly defaultOptionLabel: string; + readonly labelType: DisplayType; + readonly valueType: DisplayType; + readonly whitelist: object; + readonly blacklist: object; + readonly customOptions: string[]; + readonly priorityOptions: string[]; +}; + +export type RegionDropdownProps = { + readonly country: string; + readonly value?: string | number; + readonly id?: string; + readonly name?: string; + readonly blankOptionLabel?: string; + readonly classes?: string; + readonly showDefaultOption?: boolean; + readonly defaultOptionLabel: string; + readonly disabled?: boolean; + readonly disableWhenEmpty?: boolean; + readonly labelType?: DisplayType; + readonly countryValueType?: DisplayType; + readonly valueType?: DisplayType; + readonly customOptions: string[]; + readonly onChange?: (value: string, event: any) => null; + readonly onBlur?: (value: string, event: any) => null; + readonly whitelist?: object; + readonly blacklist?: object; +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..58159de --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "dist/", + "lib": ["es2022", "dom"], + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index 8dd26ea..f59cc4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1671,6 +1671,14 @@ smob "^1.0.0" terser "^5.17.4" +"@rollup/plugin-typescript@^12.1.1": + version "12.1.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-12.1.1.tgz#008d16b8283a422650c463f99ae0c610cf6c9727" + integrity sha512-t7O653DpfB5MbFrqPe/VcKFFkvRuFNp9qId3xq4Eth5xlyymzxNpye2z8Hrl0RIMuXTSr5GGcFpkdlMeacUiFQ== + dependencies: + "@rollup/pluginutils" "^5.1.0" + resolve "^1.22.1" + "@rollup/plugin-url@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@rollup/plugin-url/-/plugin-url-8.0.2.tgz#aab4e209e9e012f65582bd99eb80b3bbdfe15afb" @@ -9659,6 +9667,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"