├── eslintignore.json ├── .gitignore ├── src ├── font │ ├── icomoon.ttf │ ├── icomoon.woff │ └── icomoon.svg ├── js │ └── component │ │ ├── index.ts │ │ ├── global.d.ts │ │ ├── index.global.ts │ │ ├── utils.ts │ │ ├── react-custom-flag-select.css │ │ └── ReactCustomFlagSelect.tsx ├── css │ ├── example.css.json │ └── example.css ├── html │ └── layout.html ├── __tests__ │ └── ReactCustomFlagSelect.js └── image │ └── flags │ ├── cn.svg │ └── us.svg ├── index.js ├── react-custom-flag-select.gif ├── react-custom-flag-select.jpg ├── tea.yaml ├── .travis.yml ├── .npmignore ├── webpack ├── umd.global.config.babel.js ├── umd.local.config.babel.js ├── build_path.js ├── development.config.babel.js ├── production.config.babel.js ├── umd.base.config.babel.js └── base.babel.js ├── tsconfig.json ├── CHANGELOG.md ├── .babelrc ├── .all-contributorsrc ├── stylelint.config.js ├── jest.config.js ├── LICENSE ├── .eslintrc.json ├── package.json ├── example └── index.js └── README.md /eslintignore.json: -------------------------------------------------------------------------------- 1 | coverage/ 2 | dist/ 3 | lib/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /*.log 3 | /log/*.log 4 | coverage 5 | /notes 6 | /lib 7 | -------------------------------------------------------------------------------- /src/font/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardfxiao/react-custom-flag-select/HEAD/src/font/icomoon.ttf -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var ReactCustomFlagSelect = require('./lib/components/index.js'); 2 | module.exports = ReactCustomFlagSelect; -------------------------------------------------------------------------------- /src/font/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardfxiao/react-custom-flag-select/HEAD/src/font/icomoon.woff -------------------------------------------------------------------------------- /src/js/component/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ReactCustomFlagSelect'; 2 | export { default } from './ReactCustomFlagSelect'; -------------------------------------------------------------------------------- /react-custom-flag-select.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardfxiao/react-custom-flag-select/HEAD/react-custom-flag-select.gif -------------------------------------------------------------------------------- /react-custom-flag-select.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardfxiao/react-custom-flag-select/HEAD/react-custom-flag-select.jpg -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xb60FCefB862640cbfF058e04CD64B4ed8EaFCA1F' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /src/js/component/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css' { 2 | interface IClassNames { 3 | [className: string]: string 4 | } 5 | const classNames: IClassNames; 6 | export = classNames; 7 | } -------------------------------------------------------------------------------- /src/js/component/index.global.ts: -------------------------------------------------------------------------------- 1 | import ReactCustomFlagSelect from './ReactCustomFlagSelect'; 2 | if (typeof window !== 'undefined') { 3 | (window).ReactCustomFlagSelect = ReactCustomFlagSelect; 4 | } 5 | 6 | export default ReactCustomFlagSelect; 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | before_script: 5 | - npm i 6 | script: npm run prepublish 7 | after_script: "cat coverage/lcov.info | node_modules/coveralls/bin/coveralls.js" 8 | env: 9 | - REACT=16 10 | notifications: 11 | email: 12 | - email:edwardfhsiao@gmail.com 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /example 3 | /webpack 4 | /src 5 | /.babelrc 6 | /.eslintrc.json 7 | /eslintignore.json 8 | /.gitignore 9 | /.travis.yml 10 | /jest.config.js 11 | /postcss.config.js 12 | /stylelint.config.js 13 | /tsconfig.json 14 | /tslint.json 15 | /*.gif 16 | /dist 17 | /*.html 18 | /.github 19 | /rev-manifest.json 20 | /docs -------------------------------------------------------------------------------- /src/css/example.css.json: -------------------------------------------------------------------------------- 1 | {"radiobox__wrapper":"example__radiobox__wrapper___2_d0T","checked":"example__checked___-yCSv","my-button":"example__my-button___1Bf6W","my-button__red":"example__my-button__red___1o6AE","sub-section-title-wrapper":"example__sub-section-title-wrapper___2VSCo","sub-section-title":"example__sub-section-title___1qXUD","block":"example__block___MEy5M"} -------------------------------------------------------------------------------- /webpack/umd.global.config.babel.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./umd.base.config.babel.js'); 2 | const PATH = require('./build_path'); 3 | module.exports = { 4 | ...baseConfig, 5 | entry: PATH.ROOT_PATH + 'src/js/component/index.global.ts', 6 | output: { 7 | ...baseConfig.output, 8 | path: PATH.ROOT_PATH + '/lib', 9 | }, 10 | externals: { 11 | react: 'React', 12 | 'react-dom': 'ReactDOM', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /webpack/umd.local.config.babel.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./umd.base.config.babel.js'); 2 | const PATH = require('./build_path'); 3 | module.exports = { 4 | ...baseConfig, 5 | entry: PATH.ROOT_PATH + 'src/js/component/index.ts', 6 | devtool: false, 7 | output: { 8 | ...baseConfig.output, 9 | path: PATH.ROOT_PATH + '/lib/components', 10 | filename: 'index.js', 11 | }, 12 | externals: { 13 | react: 'react', 14 | 'react-dom': 'react-dom', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib/", 4 | "sourceMap": true, 5 | "strictNullChecks": false, 6 | "declaration": true, 7 | "noImplicitAny": true, 8 | "module": "commonjs", 9 | "target": "es5", 10 | "downlevelIteration": true, 11 | "lib": ["es2016", "dom"], 12 | "jsx": "react", 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "strict": true, 16 | }, 17 | "include": ["./src/**/*"] 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.1.0 2 | 3 | - `title` and `ellipsis` for option item 4 | 5 | # 3.0.9 6 | 7 | - update readme 8 | 9 | # 3.0.8 10 | 11 | - fix bug of cached disabled in handleOnItemClick 12 | 13 | # 3.0.7 14 | 15 | - Remove classname hash 16 | 17 | # 3.0.5 18 | 19 | - Better TypeScript support (export interface ReactCustomFlagSelectProps) 20 | 21 | # 3.0.4 22 | 23 | - Fix wrong active class in search box shows 24 | 25 | # 3.0.3 26 | 27 | - Support IE9+ again when compiling with webpack 28 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "env": 4 | { 5 | "development": 6 | { 7 | 8 | }, 9 | "production": 10 | { 11 | 12 | }, 13 | "lib": 14 | { 15 | "plugins": [ 16 | ["css-modules-transform", 17 | { 18 | "generateScopedName": "[name]__[local]___[hash:base64:5]" 19 | }], 20 | "@babel/proposal-class-properties", 21 | "@babel/proposal-object-rest-spread" 22 | ], 23 | }, 24 | } 25 | } -------------------------------------------------------------------------------- /webpack/build_path.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ROOT_PATH = path.join(__dirname, '../'); 3 | const ASSET_PATH = path.join(ROOT_PATH, 'dist'); 4 | const NODE_MODULES_PATH = path.join(ROOT_PATH, './node_modules'); 5 | const HTML_PATH = path.join(ROOT_PATH, './src/html'); 6 | const SOURCE_PATH = path.join(ROOT_PATH, './src'); 7 | 8 | module.exports = { 9 | ROOT_PATH: ROOT_PATH, 10 | ASSET_PATH: ASSET_PATH, 11 | NODE_MODULES_PATH: NODE_MODULES_PATH, 12 | HTML_PATH: HTML_PATH, 13 | SOURCE_PATH: SOURCE_PATH 14 | }; 15 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "edwardfxiao", 10 | "name": "Edward Xiao", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/11728228?v=4", 12 | "profile": "https://github.com/edwardfxiao", 13 | "contributions": [ 14 | "code", 15 | "doc", 16 | "infra", 17 | "test", 18 | "review" 19 | ] 20 | } 21 | ], 22 | "contributorsPerLine": 7, 23 | "projectName": "react-custom-flag-select", 24 | "projectOwner": "edwardfxiao", 25 | "repoType": "github", 26 | "repoHost": "https://github.com", 27 | "skipCi": true 28 | } 29 | -------------------------------------------------------------------------------- /src/html/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

react-custom-flag-select

16 | fork me on Github@edwardfxiao
17 |
18 |
19 |
20 | <%= htmlWebpackPlugin.options.customJs %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/css/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-size: 100%; 4 | padding: 10px; 5 | } 6 | 7 | .radiobox__wrapper label:before { 8 | border-color: #000; 9 | } 10 | 11 | .radiobox__wrapper.checked label:before { 12 | background-color: #000!important; 13 | } 14 | 15 | :global .icon { 16 | color: #7eb4ef; 17 | } 18 | 19 | .my-button { 20 | font-size: 14px; 21 | } 22 | 23 | .my-button__red { 24 | color: #fff; 25 | border-radius: 2px; 26 | background: #ff5a57; 27 | padding: 10px; 28 | margin: 0 auto; 29 | text-align: center; 30 | cursor: pointer; 31 | } 32 | 33 | .sub-section-title-wrapper { 34 | padding: 2%; 35 | } 36 | 37 | .sub-section-title { 38 | background: #7eb4ef; 39 | color: #fff; 40 | padding: 30px; 41 | font-size: 32px; 42 | } 43 | 44 | .block { 45 | box-shadow: 0px 0px 13px 0px rgba(0, 0, 0, 0.75); 46 | padding: 2%; 47 | } -------------------------------------------------------------------------------- /src/js/component/utils.ts: -------------------------------------------------------------------------------- 1 | export const cx = (...params: Array) => { 2 | const classes = []; 3 | for (let i = 0; i < params.length; i += 1) { 4 | const arg = params[i]; 5 | if (!arg) continue; 6 | const argType = typeof arg; 7 | if (argType === 'string' || argType === 'number') { 8 | classes.push(arg); 9 | } else if (Array.isArray(arg) && arg.length) { 10 | const inner: string = cx.apply(null, arg); 11 | if (inner) { 12 | classes.push(inner); 13 | } 14 | } else if (argType === 'object') { 15 | for (const key in arg) { 16 | if ({}.hasOwnProperty.call(arg, key) && arg[key]) { 17 | classes.push(key); 18 | } 19 | } 20 | } 21 | } 22 | return classes.join(' '); 23 | }; 24 | 25 | export const getRandomId = () => { 26 | return Math.random() 27 | .toString(36) 28 | .slice(-8); 29 | }; 30 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: "stylelint-config-standard", 3 | rules: { 4 | 'block-no-empty': true, 5 | 'color-hex-case': 'lower', 6 | 'color-hex-length': null, 7 | 'color-no-invalid-hex': true, 8 | 'length-zero-no-unit': true, 9 | 'comment-empty-line-before': ['always', { 10 | 'except': ['first-nested'], 11 | 'ignore': ['stylelint-commands', 'between-comments'], 12 | }], 13 | 'declaration-colon-space-after': 'always', 14 | 'max-empty-lines': 2, 15 | 'unit-whitelist': ['em', 'rem', '%', 's', 'ms', 'px', 'deg', 'vw', 'vh', 'dpi', 'dppx'], 16 | 'selector-combinator-space-after': null, 17 | 'selector-pseudo-element-colon-notation': null, 18 | 'selector-list-comma-newline-after': null, 19 | 'comment-empty-line-before': null, 20 | 'block-closing-brace-newline-before': null, 21 | 'number-leading-zero': null, 22 | 'rule-empty-line-before': null, 23 | 'declaration-block-trailing-semicolon': null 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/__tests__/ReactCustomFlagSelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { configure, mount } from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import ReactCustomFlagSelect from '../js/ReactCustomFlagSelect/ReactCustomFlagSelect.tsx'; 6 | 7 | const SELECTED_INDEX = 2; 8 | 9 | const find = (arr, obj) => { 10 | const res = []; 11 | arr.filter(o => { 12 | let match = false; 13 | Object.keys(obj).map(i => { 14 | if (obj[i] == o[i]) { 15 | match = true; 16 | } 17 | }); 18 | if (match) { 19 | res.push(o); 20 | } 21 | }); 22 | return res; 23 | }; 24 | 25 | const OPTION_LIST = [ 26 | { id: '', name: 'Please select one country', displayText: 'Please select one country', flag: 'https://dummyimage.com/600x400/000/fff' }, 27 | { id: 'us', name: 'US', displayText: 'US', flag: 'https://dummyimage.com/600x400/000/fff' }, 28 | { id: 'ca', name: 'CA', displayText: 'CA', flag: 'https://dummyimage.com/600x400/000/fff' }, 29 | ]; 30 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | moduleNameMapper: { 4 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/assetsTransformer.js', 5 | '\\.(css|less)$': 'identity-obj-proxy', 6 | '^STYLES(.*)$': '/src/css$1', 7 | '^COMPONENTS(.*)$': '/src/js/app/components$1', 8 | '^API(.*)$': '/src/js/api$1', 9 | '^CONFIG(.*)$': '/src/config$1', 10 | '^IMAGES(.*)$': '/src/image$1', 11 | '^AUDIOS(.*)$': '/audio/api$1', 12 | '^VIDEOS(.*)$': '/src/video$1', 13 | '^LOCALES(.*)$': '/src/locales$1', 14 | '^COMMON(.*)$': '/src/js/common$1', 15 | '^APP(.*)$': '/src/js/app$1', 16 | '^CONSTS(.*)$': '/src/js/consts$1', 17 | '^PAGES(.*)$': '/src/js/api$1', 18 | '^ACTIONS(.*)$': '/src/js/actions$1', 19 | '^STORE(.*)$': '/src/js/store$1', 20 | '^REDUCERS(.*)$': '/src/js/reducers$1', 21 | '^VENDOR(.*)$': '/src/js/vendor$1', 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Edward Xiao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | // Extend existing configuration 4 | // from ESlint and eslint-plugin-react defaults. 5 | "extends": ["eslint:recommended", "plugin:react/recommended"], 6 | // Enable ES6 support. If you want to use custom Babel 7 | // features, you will need to enable a custom parser 8 | // as described in a section below. 9 | "parserOptions": { 10 | "ecmaVersion": 6, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "experimentalObjectRestSpread": true 14 | } 15 | }, 16 | "env": { 17 | "es6": true, 18 | "browser": true, 19 | "node": true, 20 | "jquery": true 21 | }, 22 | // Enable custom plugin known as eslint-plugin-react 23 | "plugins": ["react", "react-hooks"], 24 | "rules": { 25 | // Disable `no-console` rule 26 | "no-console": 0, 27 | // Give a warning if identifiers contain underscores 28 | "no-underscore-dangle": 0, 29 | "no-empty-pattern": 0, 30 | "react/prop-types": 0, 31 | "react-hooks/rules-of-hooks": "error", 32 | "no-empty": [ 33 | "error", 34 | { 35 | "allowEmptyCatch": true 36 | } 37 | ], 38 | "react/display-name": [0, { "ignoreTranspilerName": true }] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webpack/development.config.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import ESLintPlugin from 'eslint-webpack-plugin'; 5 | import base from './base.babel.js'; 6 | const PATH = require('./build_path'); 7 | const config = { 8 | ...base, 9 | mode: 'development', 10 | output: { 11 | publicPath: '/', 12 | filename: '[name].js', 13 | }, 14 | }; 15 | config.plugins.push( 16 | new ESLintPlugin({ 17 | context: 'src', 18 | emitWarning: true, 19 | failOnError: false, 20 | exclude: ['data', 'locales'], 21 | extensions: ['airbnb-typescript'], 22 | }), 23 | ); 24 | config.plugins.push( 25 | new MiniCssExtractPlugin({ filename: 'css/[name].css' }), 26 | new HtmlWebpackPlugin({ 27 | template: PATH.HTML_PATH + '/layout.html', 28 | title: 'react-custom-flag-select', 29 | page: 'index', 30 | filename: 'index.html', 31 | hash: false, 32 | chunksSortMode: function (chunk1, chunk2) { 33 | var orders = ['index']; 34 | var order1 = orders.indexOf(chunk1.names[0]); 35 | var order2 = orders.indexOf(chunk2.names[0]); 36 | if (order1 > order2) { 37 | return 1; 38 | } else if (order1 < order2) { 39 | return -1; 40 | } else { 41 | return 0; 42 | } 43 | }, 44 | }), 45 | ); 46 | module.exports = config; 47 | -------------------------------------------------------------------------------- /webpack/production.config.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import TerserPlugin from 'terser-webpack-plugin'; 5 | import base from './base.babel.js'; 6 | const PATH = require('./build_path'); 7 | const config = { 8 | ...base, 9 | mode: 'production', 10 | devtool: 'cheap-source-map', 11 | output: { 12 | publicPath: '/react-custom-flag-select/dist/', 13 | filename: '[name]-[chunkhash].js', 14 | }, 15 | optimization: { 16 | minimizer: [ 17 | new TerserPlugin({ 18 | terserOptions: { 19 | ecma: undefined, 20 | warnings: false, 21 | parse: {}, 22 | compress: {}, 23 | mangle: true, 24 | module: false, 25 | output: null, 26 | toplevel: false, 27 | nameCache: null, 28 | ie8: false, 29 | keep_classnames: undefined, 30 | keep_fnames: false, 31 | safari10: false, 32 | }, 33 | extractComments: true, 34 | }), 35 | ], 36 | splitChunks: { 37 | chunks: 'all', 38 | minSize: 30000, 39 | minChunks: 1, 40 | maxAsyncRequests: 5, 41 | maxInitialRequests: 3, 42 | name: 'asset', 43 | cacheGroups: { 44 | vendors: { 45 | name: 'b', 46 | test: /[\\/]node_modules[\\/]/, 47 | priority: -10, 48 | }, 49 | default: { 50 | name: 'c', 51 | minChunks: 2, 52 | priority: -20, 53 | reuseExistingChunk: true, 54 | }, 55 | }, 56 | }, 57 | }, 58 | }; 59 | config.plugins.push( 60 | new MiniCssExtractPlugin({ filename: 'css/[name]-[hash].css' }), 61 | new HtmlWebpackPlugin({ 62 | template: PATH.HTML_PATH + '/layout.html', 63 | title: 'react-custom-flag-select', 64 | page: 'index', 65 | filename: '../index.html', 66 | hash: false, 67 | chunksSortMode: function(chunk1, chunk2) { 68 | var orders = ['index']; 69 | var order1 = orders.indexOf(chunk1.names[0]); 70 | var order2 = orders.indexOf(chunk2.names[0]); 71 | if (order1 > order2) { 72 | return 1; 73 | } else if (order1 < order2) { 74 | return -1; 75 | } else { 76 | return 0; 77 | } 78 | }, 79 | }), 80 | ); 81 | module.exports = config; 82 | -------------------------------------------------------------------------------- /src/image/flags/cn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 11 | 14 | 17 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /webpack/umd.base.config.babel.js: -------------------------------------------------------------------------------- 1 | const env = require('yargs').argv.env; // use --env with webpack 2 2 | const path = require('path'); 3 | const PATH = require('./build_path'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | 6 | let libraryName = 'react-custom-flag-select'; 7 | 8 | let plugins = [], 9 | outputFile; 10 | 11 | if (env === 'minify') { 12 | plugins.push(new MiniCssExtractPlugin({ filename: libraryName + '.min.css' })); 13 | outputFile = libraryName + '.min.js'; 14 | } else { 15 | plugins.push(new MiniCssExtractPlugin({ filename: libraryName + '.css' })); 16 | outputFile = libraryName + '.js'; 17 | } 18 | 19 | module.exports = { 20 | mode: 'production', 21 | context: PATH.ROOT_PATH, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.mp3?$/, 26 | include: [PATH.ROOT_PATH], 27 | exclude: [PATH.NODE_MODULES_PATH], 28 | loader: 'file-loader', 29 | options: { 30 | name: 'audio/[name]-[hash].[ext]', 31 | }, 32 | }, 33 | { 34 | test: /\.(woff|woff2|eot|ttf|otf)\??.*$/, 35 | include: [PATH.ROOT_PATH], 36 | // exclude: [PATH.NODE_MODULES_PATH], 37 | loader: 'url-loader', 38 | options: { 39 | limit: 1, 40 | name: 'font/[name]-[hash].[ext]', 41 | }, 42 | }, 43 | { 44 | test: /\.(jpe?g|png|gif|svg)\??.*$/, 45 | include: [PATH.ROOT_PATH], 46 | // exclude: [PATH.NODE_MODULES_PATH], 47 | loader: 'url-loader', 48 | options: { 49 | limit: 1, 50 | name: 'img/[name]-[hash].[ext]', 51 | }, 52 | }, 53 | { 54 | test: /\.jsx?$/, 55 | include: [PATH.ROOT_PATH], 56 | exclude: [PATH.NODE_MODULES_PATH], 57 | enforce: 'post', 58 | loader: 'babel-loader', 59 | }, 60 | { 61 | test: /\.(ts|tsx)$/, 62 | include: [PATH.ROOT_PATH], 63 | exclude: [PATH.NODE_MODULES_PATH], 64 | enforce: 'post', 65 | loader: 'ts-loader', 66 | }, 67 | { 68 | test: /\.css$/, 69 | enforce: 'post', 70 | use: [ 71 | MiniCssExtractPlugin.loader, 72 | { 73 | loader: 'css-loader', 74 | options: { 75 | modules: { 76 | localIdentName: '[name]__[local]', 77 | }, 78 | }, 79 | }, 80 | { 81 | loader: 'postcss-loader', 82 | options: { 83 | postcssOptions: { 84 | plugins: [ 85 | ['postcss-import', {}], 86 | ['postcss-preset-env', { stage: 0 }], 87 | ['cssnano', { safe: true }], 88 | ], 89 | }, 90 | }, 91 | }, 92 | ], 93 | }, 94 | ], 95 | }, 96 | resolve: { 97 | modules: ['node_modules', path.resolve(__dirname, 'app')], 98 | extensions: ['.ts', '.tsx', '.js', '.json', '.jsx', '.css'], 99 | }, 100 | devtool: 'source-map', 101 | output: { 102 | path: PATH.ROOT_PATH + '/lib', 103 | filename: outputFile, 104 | library: libraryName, 105 | libraryTarget: 'umd', 106 | globalObject: 'this', 107 | }, 108 | plugins, 109 | target: ['web', 'es5'], 110 | }; 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-custom-flag-select", 3 | "version": "3.1.0", 4 | "description": "A react component for custom flag (country code) select.", 5 | "main": "index.js", 6 | "types": "./lib/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/edwardfxiao/react-custom-flag-select" 10 | }, 11 | "keywords": [ 12 | "flag", 13 | "select", 14 | "country code", 15 | "area code", 16 | "phone code", 17 | "telephone code", 18 | "react-custom-flag-select" 19 | ], 20 | "author": "Edward Xiao", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/edwardfxiao/react-custom-flag-select/issues" 24 | }, 25 | "homepage": "https://github.com/edwardfxiao/react-custom-flag-select#readme", 26 | "scripts": { 27 | "test": "NODE_ENV=test jest", 28 | "tslint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'", 29 | "build_gh_page": "rm -rf lib && rm -rf dist && NODE_ENV=production ./node_modules/.bin/webpack --config ./webpack/production.config.babel.js --progress", 30 | "umd_local": "./node_modules/.bin/webpack --config ./webpack/umd.local.config.babel.js", 31 | "umd_global": "./node_modules/.bin/webpack --config ./webpack/umd.global.config.babel.js", 32 | "umd_global_min": "./node_modules/.bin/webpack --config ./webpack/umd.global.config.babel.js --env minify", 33 | "dev": "node_modules/.bin/webpack-dev-server --config ./webpack/development.config.babel.js", 34 | "compile": "rimraf dist lib && npm run umd_global && npm run umd_global_min && npm run umd_local && rm ./lib/components/*.css" 35 | }, 36 | "peerDependencies": { 37 | "react": ">= 16.8.6", 38 | "react-dom": ">= 16.8.6" 39 | }, 40 | "devDependencies": { 41 | "@babel/cli": "^7.17.0", 42 | "@babel/core": "^7.17.2", 43 | "@babel/eslint-parser": "^7.17.0", 44 | "@babel/plugin-proposal-class-properties": "^7.16.7", 45 | "@babel/preset-env": "^7.16.11", 46 | "@babel/preset-react": "^7.16.7", 47 | "@babel/register": "^7.17.0", 48 | "@swc/core": "^1.2.139", 49 | "@swc/wasm": "^1.2.139", 50 | "@types/jest": "^27.4.0", 51 | "@types/react": "^16.8.14", 52 | "@types/react-dom": "^16.8.4", 53 | "babel-jest": "^27.5.1", 54 | "babel-loader": "^8.2.3", 55 | "babel-plugin-css-modules-transform": "^1.6.2", 56 | "bufferutil": "^4.0.6", 57 | "chai": "^4.2.0", 58 | "core-js": "^3.21.0", 59 | "coveralls": "^3.1.1", 60 | "css-loader": "^6.4.0", 61 | "cssnano": "^5.0.8", 62 | "enzyme": "^3.10.0", 63 | "enzyme-adapter-react-16": "^1.13.2", 64 | "esbuild": "^0.14.21", 65 | "eslint": "^8.0.1", 66 | "eslint-plugin-react": "^7.26.1", 67 | "eslint-plugin-react-hooks": "^4.2.0", 68 | "eslint-webpack-plugin": "^3.1.1", 69 | "file-loader": "^6.2.0", 70 | "html-webpack-plugin": "^5.4.0", 71 | "identity-obj-proxy": "^3.0.0", 72 | "inline-style-prefix-all": "^2.0.2", 73 | "jest": "^27.4.0", 74 | "mini-css-extract-plugin": "^2.4.3", 75 | "node-notifier": "^10.0.1", 76 | "postcss-css-variables": "^0.17.0", 77 | "postcss-custom-properties": "^9.1.1", 78 | "postcss-import": "^14.0.2", 79 | "postcss-loader": "^6.2.0", 80 | "postcss-preset-env": "^7.3.1", 81 | "postcss-simple-vars": "^6.0.3", 82 | "prismjs": "^1.16.0", 83 | "react": "^16.8.6", 84 | "react-dom": "^16.8.6", 85 | "react-inputs-validation": "^4.8.8", 86 | "react-markdown": "^8.0.0", 87 | "regenerator-runtime": "^0.13.9", 88 | "rimraf": "^3.0.2", 89 | "ts-jest": "^27.1.3", 90 | "ts-loader": "^9.2.6", 91 | "ts-node": "^10.5.0", 92 | "typescript": "^4.5.5", 93 | "url-loader": "^4.1.1", 94 | "utf-8-validate": "^5.0.8", 95 | "validator": "^13.7.0", 96 | "webpack": "^5.68.0", 97 | "webpack-assets-manifest": "^5.1.0", 98 | "webpack-cli": "^4.9.2", 99 | "webpack-dev-server": "^4.7.4" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /webpack/base.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import path from 'path'; 3 | import PATH from './build_path'; 4 | import WebpackAssetsManifest from 'webpack-assets-manifest'; 5 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 6 | export default { 7 | context: PATH.ROOT_PATH, 8 | entry: { 9 | index: PATH.ROOT_PATH + 'example/index.js', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.mp3?$/, 15 | include: [PATH.ROOT_PATH], 16 | exclude: [PATH.NODE_MODULES_PATH], 17 | use: [{ loader: 'file-loader?name=audio/[name]-[hash].[ext]' }], 18 | }, 19 | { 20 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 21 | type: 'asset/resource', 22 | }, 23 | { 24 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 25 | type: 'asset/resource', 26 | }, 27 | { 28 | test: /\.jsx?$/, 29 | include: [PATH.ROOT_PATH], 30 | exclude: [PATH.NODE_MODULES_PATH], 31 | enforce: 'post', 32 | loader: 'babel-loader', 33 | }, 34 | { 35 | test: /\.(ts|tsx)$/, 36 | include: [PATH.ROOT_PATH], 37 | exclude: [PATH.NODE_MODULES_PATH], 38 | enforce: 'post', 39 | loader: 'ts-loader', 40 | }, 41 | { 42 | test: /\.css$/, 43 | include: [PATH.NODE_MODULES_PATH], 44 | enforce: 'post', 45 | use: [ 46 | MiniCssExtractPlugin.loader, 47 | { 48 | loader: 'css-loader', 49 | options: {}, 50 | }, 51 | { 52 | loader: 'postcss-loader', 53 | options: { 54 | postcssOptions: { 55 | plugins: [ 56 | ['postcss-import', {}], 57 | ['postcss-preset-env', {}], 58 | ], 59 | }, 60 | }, 61 | }, 62 | ], 63 | }, 64 | { 65 | test: /\.css$/, 66 | include: [PATH.SOURCE_PATH], 67 | exclude: [PATH.NODE_MODULES_PATH], 68 | enforce: 'post', 69 | use: [ 70 | MiniCssExtractPlugin.loader, 71 | { 72 | loader: 'css-loader', 73 | options: { 74 | modules: { 75 | localIdentName: '[name]__[local]', 76 | }, 77 | }, 78 | }, 79 | { 80 | loader: 'postcss-loader', 81 | options: { 82 | postcssOptions: { 83 | plugins: [ 84 | ['postcss-import', {}], 85 | ['postcss-preset-env', { stage: 0 }], 86 | ['cssnano', { safe: true }], 87 | ], 88 | }, 89 | }, 90 | }, 91 | ], 92 | }, 93 | ], 94 | }, 95 | resolve: { 96 | modules: ['node_modules', path.resolve(__dirname, 'app')], 97 | extensions: ['.ts', '.tsx', '.js', '.json', '.jsx', '.css'], 98 | fallback: { 99 | path: false, 100 | }, 101 | }, 102 | devtool: 'source-map', 103 | devServer: { 104 | compress: true, 105 | host: '0.0.0.0', 106 | port: 9000, 107 | // https: { 108 | // cert: helper.ROOT_PATH + 'src/https/cert.pem', // path to cert, 109 | // key: helper.ROOT_PATH + 'src/https/key.pem', // path to key, 110 | // }, 111 | historyApiFallback: true, 112 | client: { overlay: false }, 113 | static: [ 114 | { 115 | directory: path.join(__dirname, 'dist'), 116 | watch: true, 117 | }, 118 | ], 119 | devMiddleware: { 120 | writeToDisk: filePath => { 121 | return /\.css$/.test(filePath); 122 | }, 123 | }, 124 | }, 125 | plugins: [ 126 | new webpack.ContextReplacementPlugin(/\.\/locale$/, 'empty-module', false, /js$/), 127 | new webpack.ProvidePlugin({ 128 | React: 'React', 129 | react: 'React', 130 | 'window.react': 'React', 131 | 'window.React': 'React', 132 | }), 133 | new WebpackAssetsManifest({ 134 | output: 'manifest-rev.json', 135 | }), 136 | ], 137 | target: ['web', 'es5'], 138 | }; 139 | -------------------------------------------------------------------------------- /src/js/component/react-custom-flag-select.css: -------------------------------------------------------------------------------- 1 | .ellipsis { 2 | white-space: nowrap; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | } 6 | 7 | .select__button { 8 | width: 100%; 9 | display: inline-block; 10 | text-align: left; 11 | cursor: pointer; 12 | background: none; 13 | border: none; 14 | margin: 0; 15 | padding: 0; 16 | height: 100%; 17 | overflow: visible; 18 | } 19 | 20 | /*.select__button:focus { 21 | outline: auto !important; 22 | }*/ 23 | 24 | /*.select__wrapper, 25 | .select__wrapper *:focus { 26 | outline: none; 27 | }*/ 28 | 29 | .select__wrapper.disabled, 30 | .select__wrapper.disabled div { 31 | cursor: not-allowed; 32 | } 33 | 34 | .select__wrapper { 35 | cursor: pointer; 36 | outline: none; 37 | position: relative; 38 | height: 100%; 39 | } 40 | 41 | .select__input { 42 | opacity: 0; 43 | position: absolute; 44 | top: 0; 45 | bottom: 0; 46 | left: 0; 47 | right: 0; 48 | width: 100%; 49 | height: 100%; 50 | cursor: pointer; 51 | } 52 | 53 | .select__buttonWrapper { 54 | height: 100%; 55 | } 56 | 57 | .select__searchInputWrapper { 58 | padding: 6px; 59 | box-sizing: border-box; 60 | width: 100%; 61 | position: relative; 62 | display: flex; 63 | background-color: #fff; 64 | } 65 | 66 | .select__searchInputSearchIcon { 67 | position: absolute; 68 | top: 50%; 69 | left: 8px; 70 | transform: translateY(-50%); 71 | } 72 | 73 | .select__searchInputRemoveIcon { 74 | position: absolute; 75 | top: 50%; 76 | right: 8px; 77 | transform: translateY(-50%); 78 | cursor: pointer; 79 | } 80 | 81 | .select__searchInput { 82 | padding: 0 20px; 83 | box-sizing: border-box; 84 | border-radius: 2px; 85 | font-size: 14px; 86 | height: 40px; 87 | width: 100%; 88 | border: 1px solid #dfe2e7; 89 | color: #7f8fa4; 90 | display: block; 91 | } 92 | 93 | .select__container { 94 | position: relative; 95 | height: 100%; 96 | } 97 | 98 | .select__options-item { 99 | cursor: none; 100 | background: #fff; 101 | color: #4a4a4a; 102 | transition: background 0.4s, color 0.2s; 103 | padding: 10px; 104 | display: flex; 105 | align-items: center; 106 | justify-content: space-between; 107 | } 108 | 109 | .select__options-item div { 110 | display: inline-block\9; 111 | text-align: right; 112 | } 113 | 114 | .select__options-item-show-cursor { 115 | cursor: pointer; 116 | } 117 | 118 | .select__no-mouse { 119 | cursor: none; 120 | } 121 | 122 | .select__hover-active { 123 | background: #ff5a57; 124 | color: #fff; 125 | } 126 | 127 | .select__options-item.active { 128 | background: #d3d3d3; 129 | color: #fff; 130 | } 131 | 132 | .select__options-container-animate { 133 | opacity: 0; 134 | filter: alpha(opacity=0); 135 | position: absolute; 136 | left: 0; 137 | z-index: -1; 138 | box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.12), 0 0 10px 0 rgba(0, 0, 0, 0.08); 139 | transform: translateY(-10px); 140 | transition: all 0.4s; 141 | visibility: hidden; 142 | } 143 | 144 | .select__options-container-animate.show { 145 | transform: translate(-50%, 0) scale(1, 1); 146 | opacity: 1; 147 | z-index: 3; 148 | visibility: visible; 149 | } 150 | 151 | .select__options-container { 152 | opacity: 0; 153 | filter: alpha(opacity=0); 154 | position: absolute; 155 | left: 0; 156 | z-index: -1; 157 | box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.12), 0 0 10px 0 rgba(0, 0, 0, 0.08); 158 | transform: translateY(-10px); 159 | visibility: hidden; 160 | } 161 | 162 | .select__options-container.show { 163 | transform: translateY(0); 164 | opacity: 1; 165 | filter: alpha(opacity=100); 166 | z-index: 1000; 167 | visibility: visible; 168 | } 169 | 170 | .select__dropdown { 171 | display: flex; 172 | justify-content: space-between; 173 | align-items: center; 174 | height: 100%; 175 | } 176 | 177 | .select__dropdown-icon { 178 | position: relative; 179 | width: 2%\9; 180 | display: inline-block\9; 181 | vertical-align: middle\9; 182 | width: 10px; 183 | height: 20px; 184 | } 185 | 186 | .select__selector { 187 | padding: 0 10px; 188 | height: 100%; 189 | } 190 | 191 | .select__dropdown-flag { 192 | flex: 0 0 30%; 193 | width: 30%\9; 194 | display: inline-block\9; 195 | vertical-align: middle\9; 196 | height: 100%; 197 | } 198 | 199 | .select__dropdown-name { 200 | display: flex; 201 | justify-content: flex-end; 202 | align-items: center; 203 | flex: 0 0 70%; 204 | width: 68%\9; 205 | text-align: right\9; 206 | display: inline-block\9; 207 | vertical-align: middle\9; 208 | } 209 | 210 | .select__dropdown-name div { 211 | display: inline-block\9; 212 | vertical-align: middle\9; 213 | } 214 | 215 | .select__dropdown-icon.showArrow:before { 216 | content: ''; 217 | position: absolute; 218 | top: 50%; 219 | transform: translateY(-50%); 220 | border-style: solid; 221 | display: block; 222 | width: 0; 223 | border-width: 5px 5px 0; 224 | border-color: #e3e3e3 transparent; 225 | } 226 | 227 | .select__dropdown-icon-container { 228 | position: relative; 229 | } 230 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import 'raf/polyfill'; 2 | import 'core-js/stable'; 3 | import 'regenerator-runtime/runtime'; 4 | import React, { Component } from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import { Textbox } from 'react-inputs-validation'; 7 | import validator from 'validator'; 8 | import 'react-inputs-validation/lib/react-inputs-validation.min.css'; 9 | import ReactCustomFlagSelect from '../src/js/component/index.ts'; 10 | 11 | import STYLES from '../src/css/example.css'; 12 | 13 | const FLAG_SELECTOR_OPTION_LIST = [ 14 | { id: '1', name: 'US', displayText: 'US(1)', englishName: 'United States', locale: 'en-US', flag: require('../src/image/flags/us.svg') }, 15 | { id: '86', name: '中国', displayText: '中国(86)', englishName: 'China', locale: 'zh-CN', flag: require('../src/image/flags/cn.svg') }, 16 | ]; 17 | 18 | const DEFAULT_AREA_CODE = FLAG_SELECTOR_OPTION_LIST[0].id; 19 | 20 | const find = (arr, obj) => { 21 | const res = []; 22 | arr.forEach(o => { 23 | let match = false; 24 | Object.keys(obj).forEach(i => { 25 | if (obj[i] == o[i]) { 26 | match = true; 27 | } 28 | }); 29 | if (match) { 30 | res.push(o); 31 | } 32 | }); 33 | return res; 34 | }; 35 | 36 | class Index extends Component { 37 | constructor(props) { 38 | super(props); 39 | this.state = { areaCode: DEFAULT_AREA_CODE, phone: '', hasPhoneError: true, validate: false }; 40 | this.submit = this.submit.bind(this); 41 | } 42 | 43 | handlePhoneChange(res) { 44 | this.setState({ phone: res }); 45 | } 46 | 47 | toggleValidating(validate) { 48 | this.setState({ validate }); 49 | } 50 | 51 | submit(e) { 52 | e.preventDefault(); 53 | this.toggleValidating(true); 54 | const { hasPhoneError } = this.state; 55 | if (!hasPhoneError) { 56 | alert('You are good to go'); 57 | } 58 | } 59 | 60 | render() { 61 | const { areaCode, phone, validate } = this.state; 62 | const currentItem = find(FLAG_SELECTOR_OPTION_LIST, { id: areaCode })[0]; 63 | return ( 64 |
65 |
66 |
67 |
68 |
69 |
} //Optional.[Html].Default: none. The custom select options item html that will display in dropdown list. Use it if you think the default html is ugly. 83 | // selectHtml={
us
} //Optional.[Html].Default: none. The custom html that will display when user choose. Use it if you think the default html is ugly. 84 | classNameWrapper="" //Optional.[String].Default: "". 85 | classNameContainer="" //Optional.[String].Default: "". 86 | classNameOptionListContainer="" //Optional.[String].Default: "". 87 | classNameOptionListItem="" //Optional.[String].Default: "". 88 | classNameDropdownIconOptionListItem={''} //Optional.[String].Default: "". 89 | customStyleWrapper={{}} //Optional.[Object].Default: {}. 90 | customStyleContainer={{ border: 'none', fontSize: '12px' }} //Optional.[Object].Default: {}. 91 | customStyleSelect={{ width: '100px' }} //Optional.[Object].Default: {}. 92 | customStyleOptionListItem={{}} //Optional.[Object].Default: {}. 93 | customStyleOptionListContainer={{ maxHeight: '100px', overflow: 'auto', width: '100px' }} //Optional.[Object].Default: {}. 94 | onChange={areaCode => { 95 | this.setState({ areaCode: areaCode }, () => { 96 | this.handlePhoneChange(phone); 97 | }); 98 | }} 99 | // onBlur={() => {}} //Optional.[Func].Default: none. 100 | // onFocus={(e) => {console.log(e)}} //Optional.[Func].Default: none. 101 | // onClick={(e) => {console.log(e)}} //Optional.[Func].Default: none. 102 | /> 103 |
104 | 122 | this.setState({ 123 | hasPhoneError: res, 124 | validate: false, 125 | }) 126 | } 127 | onChange={res => { 128 | this.handlePhoneChange(res); 129 | }} 130 | onBlur={() => {}} 131 | validationOption={{ 132 | check: true, 133 | required: true, 134 | customFunc: phone => { 135 | if (validator.isMobilePhone(`${areaCode}${phone}`, currentItem.locale)) { 136 | return true; 137 | } else { 138 | return `Invalid phone format for ${currentItem.locale}`; 139 | } 140 | }, 141 | }} 142 | /> 143 |
144 |
145 |
146 | Submit 147 |
148 | 149 | 150 |
151 |
152 | ); 153 | } 154 | } 155 | 156 | ReactDOM.render(, document.getElementById('root')); 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-custom-flag-select 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) 4 | 5 | [![npm version](https://badge.fury.io/js/react-custom-flag-select.svg)](https://badge.fury.io/js/react-custom-flag-select) ![Cdnjs](https://img.shields.io/cdnjs/v/react-custom-flag-select) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) [![react-custom-flag-select](http://img.shields.io/npm/dm/react-custom-flag-select.svg)](https://www.npmjs.com/package/react-custom-flag-select) ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/react-custom-flag-select.svg) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/edwardfxiao/react-custom-flag-select/master/LICENSE) 6 | 7 | A react component for custom flag (country code) select. 8 | # 9 | 10 | # 11 | 12 | # Online Demo 13 | Live demo 14 | 15 | # Codesandbox Examples 16 | * Live demo playground 17 | 18 | ### :tada: For version >= 2.0.0, please update react and react-dom to at least ```16.8.6```, since it is rewrited with hooks. 19 | ```js 20 | "peerDependencies": { 21 | "react": ">= 16.8.6", 22 | "react-dom": ">= 16.8.6" 23 | } 24 | ``` 25 | 26 | # Thanks 27 | #### This project is inspired by ekwonye-richard/react-flags-select 28 | #### Flag images: https://github.com/ekwonye-richard/react-flags-select/tree/master/flags 29 | 30 | # Why another flag select? 31 | **Area Code is Area Code, Phone Number is Phone Number, Flag is Flag. Easy for you to handle when they are separated.** 32 | 33 | This component supports fully customized html. It focuses on the data you provided and handles the **country code** or **area code** only. Not like react-telephone-input validate whole value along with the phone number without separation from 'area code' and 'phone number', which sometimes could be really painful when you are trying to handle them in your own way. 34 | 35 | In case the **country code** or the **area code** or even the **flags** might be wrong inside a library, why don't provide them yourself? 36 | 37 | # Installation 38 | 39 | #### By NPM 40 | ```sh 41 | npm install react-custom-flag-select --save 42 | ``` 43 | #### By CDN (starting from v3.0.1) 44 | ```html 45 | 46 | ... 47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 61 | 62 | 63 | 64 | ``` 65 | # Donate 66 | Thanks for donating me a donut!  ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄ 67 | 68 | # Browser support 69 | Tested on IE9+ and Chrome and Safari(10.0.3) 70 | 71 | # Docs Link 72 | [ReactCustomFlagSelect](#ReactCustomFlagSelect) 73 | 74 | ### ReactCustomFlagSelect 75 | 76 | |Props | |Type |Description |Default | 77 | |--- |--- |--- |--- | --- | 78 | |attributesWrapper |**Req**| Obj |Modify wrapper general attributes. **If tabIndex not provided, the keydown may not working** **{
id: 'myWrapperId',
tabIndex: '1'
...
}**. | {} | 79 | |attributesButton | Opt | Obj |Modify button general attributes. **{
id: 'myButtonId'
...
}** | {} | 80 | |attributesInput | Opt | Obj |Modify hidden input general attributes. **{
id: 'myInputId'
name: 'myInputName'
...
}** | {} | 81 | |value | Opt | Str | | "" | 82 | |disabled | Opt | Bool | | false | 83 | |showSearch | Opt | Bool |Show a search box in order to find option quickly. | false | 84 | |fields | Opt | Array |Fields for search filtering. | ['name'] | 85 | |keyword | Opt | Str |Show a keyword for search box. | '' | 86 | |showArrow | Opt | Bool | | true | 87 | |animate | Opt | Bool | | false | 88 | |**optionList** |**Req**|**Array**|**[{id: "1", name: "United States", displayText: "US(1)", englishName: "United States", flag: "us.svg"}, {id: "86", name: "中国", displayText: "中国(86)", englishName: "China", flag: "cn.svg"}]** |**[]** | 89 | |classNameSelect | Opt | Str | | "" | 90 | |classNameWrapper | Opt | Str | | "" | 91 | |classNameContainer | Opt | Str | | "" | 92 | |classNameButton | Opt | Str | | "" | 93 | |classNameOptionListContainer | Opt | Str | | "" | 94 | |classNameOptionListItem | Opt | Str | | "" | 95 | |customStyleSelect | Opt | Obj | | {} | 96 | |customStyleButton | Opt | Obj | | {} | 97 | |customStyleWrapper | Opt | Obj | | {} | 98 | |customStyleContainer | Opt | Obj | | {} | 99 | |customStyleOptionListContainer | Opt | Obj | | {} | 100 | |customStyleOptionListItem | Opt | Obj | | {} | 101 | |**onChange** |**Req**|**Func**| |**(val, e) => {}**| 102 | |**onBlur** |**Opt**|**Func**| |**none** | 103 | |onFocus | Opt | Func | | none | 104 | |onClick | Opt | Func | | none | 105 | |**selectHtml** |**Opt**|**Html**|**The custom html that will display when user choose. Use it if you think the default html is ugly.**|**none** | 106 | |**selectOptionListItemHtml** |**Opt**|**Html**|**The custom select options item html that will display in dropdown list. Use it if you think the default html is ugly.**|**none** | 107 | 108 | ```js 109 | import ReactCustomFlagSelect from 'react-custom-flag-select'; 110 | import "react-custom-flag-select/lib/react-custom-flag-select.min.css"; 111 | 112 | const find = (arr, obj) => { 113 | const res = []; 114 | arr.forEach(o => { 115 | let match = false; 116 | Object.keys(obj).forEach(i => { 117 | if (obj[i] == o[i]) { 118 | match = true; 119 | } 120 | }); 121 | if (match) { 122 | res.push(o); 123 | } 124 | }); 125 | return res; 126 | }; 127 | 128 | const FLAG_SELECTOR_OPTION_LIST = [ 129 | { id: '1', name: 'US', displayText: 'US(1)', locale: 'en-US', englishName: 'United States', flag: require('../src/image/flags/us.svg') }, 130 | { id: '86', name: '中国', displayText: '中国(86)', locale: 'zh-CN', englishName: 'China', flag: require('../src/image/flags/cn.svg') } 131 | ]; 132 | 133 | const { areaCode, phone, validate } = this.state; 134 | const currentItem = find(FLAG_SELECTOR_OPTION_LIST, { id: areaCode })[0]; 135 | 136 |