Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit efa6b1d

Browse files
committedNov 18, 2024
version 1.0.0
1 parent c0df4d7 commit efa6b1d

23 files changed

+16248
-45
lines changed
 

‎.babelrc.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const output = process.env.BABEL_OUTPUT;
4+
const modules = output == null ? false : output;
5+
const options = {
6+
presets: [['@babel/env', {loose: true, modules}], '@babel/react'],
7+
plugins: ['@babel/plugin-transform-react-jsx'],
8+
env: {
9+
test: {
10+
// extra configuration for process.env.NODE_ENV === 'test'
11+
presets: ['@babel/env'], // overwrite env-config from above with transpiled module syntax
12+
},
13+
},
14+
};
15+
16+
module.exports = options;

‎.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ide
2+
3+
# dependencies
4+
/node_modules
5+
example/node_modules
6+
/.pnp
7+
.pnp.js
8+
9+
# testing
10+
/coverage
11+
/sandbox
12+
13+
# production
14+
/build
15+
/lib
16+
/dist
17+
18+
# demo
19+
/demo
20+
21+
# misc
22+
.DS_Store
23+
.env.local
24+
.env.development.local
25+
.env.test.local
26+
.env.production.local
27+
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*

‎.npmignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#ide
2+
/.vscode
3+
4+
# dependencies
5+
/node_modules
6+
/.pnp
7+
.pnp.js
8+
9+
# production
10+
webpack*
11+
rollup*
12+
.babelrc.js
13+
14+
#development
15+
/sandbox
16+
17+
#demo
18+
/example
19+
index.html
20+
/demo
21+
22+
# testing
23+
/coverage
24+
25+
# misc
26+
.DS_Store
27+
.env.local
28+
.env.development.local
29+
.env.test.local
30+
.env.production.local
31+
32+
npm-debug.log*
33+
yarn-debug.log*
34+
yarn-error.log*

‎.prettierignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ide
2+
/.vscode
3+
4+
# dependencies
5+
/node_modules
6+
example/node_modules
7+
/.pnp
8+
.pnp.js
9+
10+
# testing
11+
/coverage
12+
13+
# production
14+
/build
15+
/lib
16+
/dist
17+
18+
# demo
19+
/demo
20+
21+
# misc
22+
.DS_Store
23+
.env.local
24+
.env.development.local
25+
.env.test.local
26+
.env.production.local
27+
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*

‎.prettierrc.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"arrowParens": "always",
3+
"bracketSpacing": false,
4+
"jsxBracketSameLine": true,
5+
"printWidth": 120,
6+
"semi": true,
7+
"singleQuote": true,
8+
"tabWidth": 2,
9+
"trailingComma": "all",
10+
"useTabs": false,
11+
"proseWrap": "never"
12+
}

‎.vscode/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"[javascript]": {
4+
"editor.formatOnPaste": true,
5+
"editor.formatOnSave": true,
6+
},
7+
"editor.formatOnPaste": true,
8+
"editor.formatOnSave": true,
9+
}

‎CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

‎CODE_OF_CONDUCT.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Contributing
2+
3+
## Semantic Versioning
4+
5+
`react-custom-search-list` follows semantic versioning. We release patch versions for critical bugfixes, minor versions for new features or non-essential changes, and major versions for any breaking changes.
6+
7+
## Proposing a Change
8+
9+
Patches for bugfixes are always welcome. Please accompany pull requests for bugfixes with a test case that is fixed by the PR. If you want to implement a new feature, It is advised to open an issue first in the GitHub.
10+
11+
## Before submitting a pull request, please make sure the following is done:
12+
13+
- Fork the repository and create your branch from `main`.
14+
- If you’ve fixed a bug or added code that should be tested, add tests.
15+
- Ensure the test suite passes : `$ npm run test`
16+
- Format your code with prettier.
17+
- Make sure you don't check-in any ESLint violations : `$ npm run lint`
18+
- Update README with appropriate docs.
19+
- Commit and PR

‎README.md

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Whether you want to showcase a constant list of options or dynamically adapt to
1313
- Clear button
1414

1515
- `Rtl` support
16-
16+
1717
- Flexible style
1818

1919
## Installation
@@ -27,79 +27,79 @@ or
2727
If you need to directly include script in your html, use the following links :
2828

2929
```js
30-
<script src="https://unpkg.com/react-custom-search-list@latest/dist/react-custom-search-list.min.js"></script>
30+
<script src="https://unpkg.com/react-custom-search-list@latest/dist/react-custom-search-list.umd.min.js"></script>
3131
```
3232

3333
## Minimal Usage
3434

3535
```js
3636
import {useState} from 'react';
3737
import ReactCustomSearchList from 'react-custom-search-list';
38-
function App(){
39-
const [searchValue, setSearchValue] = useState('');
40-
return (
41-
<ReactCustomSearchList fullWidth value={searchValue} setValue={setSearchValue}>
42-
/**Render your suggestions list here */
43-
<ul>
44-
<li>Option A</li>
45-
<li>Option B</li>
46-
<li>Option C</li>
47-
</ul>
48-
</ReactCustomSearchList>
49-
);
38+
function App() {
39+
const [searchValue, setSearchValue] = useState('');
40+
return (
41+
<ReactCustomSearchList fullWidth value={searchValue} setValue={setSearchValue}>
42+
/**Render your suggestions list here */
43+
<ul>
44+
<li>Option A</li>
45+
<li>Option B</li>
46+
<li>Option C</li>
47+
</ul>
48+
</ReactCustomSearchList>
49+
);
5050
}
5151
```
5252

5353
## Props
5454

5555
- value
56-
- type : `String`
57-
- description : input value
56+
- type : `String`
57+
- description : input value
5858
- setValue
59-
- type : `Func`
60-
- description : setState function for input value
59+
- type : `Func`
60+
- description : setState function for input value
6161
- children
62-
- type : `ReactNode`
63-
- description : suggestions list
62+
- type : `ReactNode`
63+
- description : suggestions list
6464
- rootStyle?
65-
- type : `Object`
66-
- description : style object of the `root` element
65+
- type : `Object`
66+
- description : style object of the `root` element
6767
- inputStyle?
68-
- type : `Object`
69-
- description : style object of the `input` element
68+
- type : `Object`
69+
- description : style object of the `input` element
7070
- placeholder?
71-
- type : `String`
72-
- default : `"search"`
71+
- type : `String`
72+
- default : `"search"`
7373
- iconsColor?
74-
- type : `String`
75-
- description : svg icon's color
76-
- default : `"gray"`
74+
- type : `String`
75+
- description : svg icon's color
76+
- default : `"gray"`
7777
- searchIconStyle?
78-
- type : `Object`
79-
- description : style object of the magnifying icon
78+
- type : `Object`
79+
- description : style object of the magnifying icon
8080
- clearIconStyle?
81-
- type : `Object`
82-
- description : style object of the clear icon
81+
- type : `Object`
82+
- description : style object of the clear icon
8383
- popperStyle?
84-
- type : `Object`
85-
- description : style object of the popper container
84+
- type : `Object`
85+
- description : style object of the popper container
8686
- onKeyDown?
87-
- type : `Func`
88-
- description : keydown event for input
87+
- type : `Func`
88+
- description : keydown event for input
8989
- onBlur?
90-
- type : `Func`
91-
- description : blur event for input
90+
- type : `Func`
91+
- description : blur event for input
9292
- fullWidth?
93-
- type : `Boolean`
94-
- description : set popper width same as input
95-
- placement?
96-
- type : `'auto'| 'auto-start'| 'auto-end'| 'top'| 'top-start'| 'top-end'| 'bottom'| 'bottom-start'| 'bottom-end'| 'right'| 'right-start'| 'right-end'| 'left'| 'left-start'| 'left-end'`
97-
- description : popper's placement
93+
- type : `Boolean`
94+
- description : set popper width same as input
95+
- placement?
96+
- type : `'auto'| 'auto-start'| 'auto-end'| 'top'| 'top-start'| 'top-end'| 'bottom'| 'bottom-start'| 'bottom-end'| 'right'| 'right-start'| 'right-end'| 'left'| 'left-start'| 'left-end'`
97+
- description : popper's placement
9898

9999
## Test
100100

101101
> $ npm run test
102102
103103
## License
104104

105-
MIT
105+
MIT

‎eslint.config.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const prettier = require('eslint-plugin-prettier');
2+
const trunOffPrettier = require('eslint-config-prettier');
3+
const globals = require('globals');
4+
const babelParser = require('@babel/eslint-parser');
5+
const path = require('path');
6+
const {fileURLToPath} = require('url');
7+
const js = require('@eslint/js');
8+
const {FlatCompat} = require('@eslint/eslintrc');
9+
10+
//const __filename = fileURLToPath(import.meta.url);
11+
//const __dirname = path.dirname(__filename);
12+
const compat = new FlatCompat({
13+
baseDirectory: __dirname,
14+
recommendedConfig: js.configs.recommended,
15+
allConfig: js.configs.all,
16+
});
17+
// Fix TypeError Key "languageOptions": Key "globals": Global "AudioWorkletGlobalScope " has leading or trailing whitespace.
18+
const GLOBALS_BROWSER_FIX = Object.assign({}, globals.browser, {
19+
AudioWorkletGlobalScope: globals.browser['AudioWorkletGlobalScope '],
20+
});
21+
delete GLOBALS_BROWSER_FIX['AudioWorkletGlobalScope '];
22+
23+
module.exports = [
24+
...compat.extends('eslint:recommended', 'plugin:prettier/recommended', 'prettier'),
25+
{
26+
plugins: {
27+
prettier,
28+
trunOffPrettier,
29+
},
30+
31+
languageOptions: {
32+
globals: {
33+
...GLOBALS_BROWSER_FIX,
34+
...globals.node,
35+
...globals.jest,
36+
},
37+
38+
parser: babelParser,
39+
ecmaVersion: 12,
40+
sourceType: 'module',
41+
parserOptions: {
42+
ecmaFeatures: {
43+
//jsx: true,
44+
},
45+
46+
allowImportExportEverywhere: false,
47+
},
48+
},
49+
50+
rules: {
51+
'prettier/prettier': [
52+
'error',
53+
{
54+
endOfLine: 'auto',
55+
},
56+
],
57+
},
58+
},
59+
];

‎package-lock.json

Lines changed: 15540 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
{
2+
"name": "react-custom-search-list",
3+
"version": "1.0.0",
4+
"private": false,
5+
"description": "A customizable React search input component with dynamic popup suggestions, allowing developers to create unique search experiences.",
6+
"keywords": [
7+
"react",
8+
"search",
9+
"search input",
10+
"autocomplete",
11+
"suggestions",
12+
"customizable",
13+
"responsive",
14+
"UI component",
15+
"react component",
16+
"dynamic suggestions",
17+
"react search",
18+
"popup suggestions",
19+
"user experience",
20+
"interactive search",
21+
"frontend"
22+
],
23+
"author": {
24+
"name": "dev-javascript",
25+
"email": "javascript.code.dev@gmail.com"
26+
},
27+
"main": "lib/cjs/index.js",
28+
"module": "lib/esm/index.js",
29+
"repository": {
30+
"type": "git",
31+
"url": "git+https://github.com/dev-javascript/react-custom-search-list.git"
32+
},
33+
"homepage": "https://github.com/dev-javascript/react-custom-search-list#readme",
34+
"bugs": {
35+
"url": "https://github.com/dev-javascript/react-custom-search-list/issues"
36+
},
37+
"scripts": {
38+
"watch": "set NODE_OPTIONS=--openssl-legacy-provider & run-p watch:*",
39+
"watch:js": "cross-env WATCH=true npm run build:dist:dev",
40+
"build": "set NODE_OPTIONS=--openssl-legacy-provider & npm-run-all --parallel clean:* build:lib:* --parallel build:dist:*",
41+
"build:dist:prod": "rollup -c",
42+
"build:dist:dev": "webpack --config webpack.config.js --env=development",
43+
"build:lib:cjs": "cross-env BABEL_OUTPUT=cjs babel src/ --out-dir lib/cjs/ --ignore **/__tests__,**/__mocks__,**/*.test.js,**/*.js.snap",
44+
"build:lib:esm": "babel src/ --out-dir lib/esm/ --ignore **/__tests__,**/__mocks__,**/*.test.js,**/*.js.snap",
45+
"clean:lib": "rimraf lib",
46+
"clean:dist": "rimraf dist",
47+
"prepublishOnly": "npm run build",
48+
"test": "jest",
49+
"lint": "eslint src",
50+
"deploy": "gh-pages -d demo",
51+
"precommit": "lint-staged"
52+
},
53+
"peerDependencies": {
54+
"react": ">=16.8.0",
55+
"react-dom": ">=16.8.0"
56+
},
57+
"devDependencies": {
58+
"@babel/cli": "^7.24.7",
59+
"@babel/core": "^7.24.7",
60+
"@babel/eslint-parser": "^7.24.7",
61+
"@babel/plugin-transform-runtime": "^7.24.7",
62+
"@babel/preset-env": "^7.24.7",
63+
"@babel/runtime-corejs3": "^7.24.7",
64+
"@rollup/plugin-commonjs": "^26.0.1",
65+
"@rollup/plugin-node-resolve": "^15.2.3",
66+
"@rollup/plugin-terser": "^0.4.4",
67+
"babel-loader": "^9.1.3",
68+
"cross-env": "^7.0.3",
69+
"eslint": "^8.0.0",
70+
"eslint-config-prettier": "^9.1.0",
71+
"eslint-plugin-prettier": "^5.1.3",
72+
"gh-pages": "^6.1.1",
73+
"jest": "^29.7.0",
74+
"jest-environment-jsdom": "^29.7.0",
75+
"jest-extended": "^4.0.2",
76+
"lint-staged": "^15.2.7",
77+
"npm-run-all": "^4.1.5",
78+
"prettier": "3.3.2",
79+
"rollup": "^4.18.0",
80+
"webpack": "^5.92.1",
81+
"webpack-cli": "^5.1.4",
82+
"@babel/plugin-transform-react-jsx": "^7.24.7",
83+
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
84+
"@babel/preset-react": "^7.24.7",
85+
"react-styleguidist": "^12.0.1",
86+
"eslint-plugin-react": "^7.34.3",
87+
"react": "16.9.0",
88+
"react-dom": "16.9.0",
89+
"react-test-renderer": "16.9.0"
90+
},
91+
"dependencies": {
92+
"@popperjs/core": "^2.11.8"
93+
},
94+
"files": [
95+
"dist",
96+
"lib",
97+
"src",
98+
"index.d.ts",
99+
"__tests__",
100+
"CHANGELOG.md"
101+
],
102+
"license": "MIT",
103+
"directories": {
104+
"lib": "lib"
105+
},
106+
"jest": {
107+
"testEnvironment": "jsdom",
108+
"setupFilesAfterEnv": [
109+
"jest-extended"
110+
],
111+
"collectCoverage": true,
112+
"modulePathIgnorePatterns": [
113+
"./src/__mock__",
114+
"./src/__sandbox__"
115+
]
116+
},
117+
"lint-staged": {
118+
"*.js": "eslint"
119+
}
120+
}

‎rollup.config.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const terser = require('@rollup/plugin-terser');
2+
const commonjs = require('@rollup/plugin-commonjs');
3+
const nodeResolve = require('@rollup/plugin-node-resolve');
4+
const pkg = require('./package.json');
5+
const Config = ({
6+
en,
7+
inputPath = '',
8+
outputFile = 'react-custom-search-list',
9+
outputName = 'ReactCustomSearchList',
10+
pf = false,
11+
}) => {
12+
var pfName = pf ? '.including-polyfills' : '';
13+
return {
14+
input: `lib/${pf ? 'esm-including-polyfills' : 'esm'}/${inputPath}index.js`,
15+
output: {
16+
file: `dist/${outputFile}${pfName}.umd${en === 'dev' ? '' : '.min'}.js`,
17+
format: 'umd',
18+
name: outputName,
19+
globals: {
20+
'react-dom': 'ReactDOM',
21+
react: 'React',
22+
},
23+
sourcemap: true,
24+
banner:
25+
'' +
26+
`/**
27+
* ${pkg.name} - ${pkg.description}
28+
*
29+
* @version v${pkg.version}
30+
* @homepage ${pkg.homepage}
31+
* @author ${pkg.author.name} ${pkg.author.email}
32+
* @license ${pkg.license}
33+
*/`,
34+
},
35+
plugins: (function () {
36+
const _plugins = [nodeResolve({preferBuiltins: false}), commonjs()];
37+
if (en === 'prod') {
38+
_plugins.push(terser());
39+
}
40+
return _plugins;
41+
})(),
42+
external: function (id) {
43+
return /prop-types$|react$|react-dom$|.test.js$|.js.snap$|.css$/g.test(id);
44+
},
45+
};
46+
},
47+
ConfigFactory = (op) => [Config({en: 'dev', ...op}), Config({en: 'prod', ...op})];
48+
module.exports = ConfigFactory();

‎src/defaultOptions.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**default options */
2+
const deop = {
3+
rootStyle: {
4+
display: 'inline-flex',
5+
alignItems: 'center',
6+
borderBottom: '1px solid lightgray',
7+
padding: '0.5em',
8+
position: 'relative',
9+
},
10+
inputStyle: {
11+
border: 'none',
12+
backgroundColor: 'white',
13+
outline: 'none',
14+
flex: 1,
15+
},
16+
placeholder: 'search',
17+
iconsColor: 'gray',
18+
searchIconStyle: {
19+
backgroundColor: 'white',
20+
},
21+
clearIconStyle: {
22+
backgroundColor: 'white',
23+
cursor: 'pointer',
24+
},
25+
popperStyle: {
26+
zIndex: 2,
27+
background: 'white',
28+
border: '1px solid lightgray',
29+
height: 'auto',
30+
overflowY: 'auto',
31+
overflowX: 'auto',
32+
position: 'fixed',
33+
boxShadow: `rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px`,
34+
},
35+
onKeyDown: () => {},
36+
onBlur: () => {},
37+
fullWidth: false,
38+
placement: 'bottom',
39+
};
40+
export default deop;

‎src/icons/close.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function (props) {
2+
return (
3+
<svg width="20" height="20" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}>
4+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.58 12 5 17.58 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
5+
</svg>
6+
);
7+
}

‎src/icons/search.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default function (props) {
2+
return (
3+
<svg
4+
class="sc-bcXHqe hVRzLL search-icon"
5+
width="20"
6+
height="20"
7+
focusable="false"
8+
xmlns="http://www.w3.org/2000/svg"
9+
viewBox="0 0 24 24"
10+
{...props}>
11+
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>
12+
</svg>
13+
);
14+
}

‎src/index.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React, {useRef, useState} from 'react';
2+
import Popper from './popper';
3+
import SearchIcon from './icons/search.js';
4+
import CloseIcon from './icons/close.js';
5+
import deop from './defaultOptions.js';
6+
/**
7+
*
8+
* @param {Object} props
9+
* @param {value} props.value - input value
10+
* @param {Function} props.setValue - setState function for input value
11+
* @param {React.ReactNode} [props.children] - will be shown in popup
12+
* @param {Object} [props.rootStyle] - style object of the root element
13+
* @param {Object} [props.inputStyle] - style object of the input element
14+
* @param {String} [props.placeholder="search"]
15+
* @param {String} [props.iconsColor="gray"] - svg icon's color
16+
* @param {Object} [props.searchIconStyle] - style object for magnifying icon
17+
* @param {Object} [props.clearIconStyle] - style object for clear icon
18+
* @param {Object} [props.popperStyle] - style object for popper container
19+
* @param {Function} [props.onKeyDown] - keydown event for input
20+
* @param {Function} [props.onBlur] - blur event for the input
21+
* @param {Boolean} [props.fullWidth=false] - set popper width same as input
22+
* @param {'auto'| 'auto-start'| 'auto-end'| 'top'| 'top-start'| 'top-end'| 'bottom'| 'bottom-start'| 'bottom-end'| 'right'| 'right-start'| 'right-end'| 'left'| 'left-start'| 'left-end'} [props.placement="bottom"] - popper's placement
23+
*/
24+
function ReactCustomSearchList(props) {
25+
const {
26+
children,
27+
value,
28+
setValue,
29+
rootStyle,
30+
inputStyle,
31+
placeholder,
32+
iconsColor,
33+
searchIconStyle,
34+
clearIconStyle,
35+
onKeyDown,
36+
onBlur,
37+
popperStyle,
38+
fullWidth,
39+
placement,
40+
} = {
41+
...deop,
42+
...props,
43+
};
44+
const [open, setOpen] = useState(false);
45+
const rootRef = useRef();
46+
const onFocus = (e) => {
47+
setOpen(true);
48+
};
49+
const onBlurHandle = (e) => {
50+
setOpen(false);
51+
onBlur(e);
52+
};
53+
54+
return (
55+
<div className="rc-search-suggestions" ref={rootRef} style={{...deop.rootStyle, ...rootStyle}}>
56+
<SearchIcon
57+
className="rc-search-suggestions-magnifying"
58+
fill={iconsColor}
59+
style={{...deop.searchIconStyle, ...searchIconStyle}}
60+
/>
61+
<input
62+
onFocus={onFocus}
63+
onBlur={onBlurHandle}
64+
value={value}
65+
onChange={(e) => setValue(e.target.value)}
66+
placeholder={placeholder}
67+
className="rc-search-suggestions-input"
68+
style={{...deop.inputStyle, ...inputStyle}}
69+
onKeyDown={onKeyDown}
70+
/>
71+
<CloseIcon
72+
className="rc-search-suggestions-close"
73+
fill={iconsColor}
74+
style={{opacity: value.length ? 1 : 0, ...deop.clearIconStyle, ...clearIconStyle}}
75+
onClick={() => {
76+
setValue('');
77+
}}
78+
/>
79+
{open ? (
80+
<Popper
81+
rootRef={rootRef}
82+
style={{...deop.popperStyle, ...popperStyle}}
83+
fullWidth={fullWidth}
84+
placement={placement}>
85+
{children}
86+
</Popper>
87+
) : null}
88+
</div>
89+
);
90+
}
91+
export default ReactCustomSearchList;

‎src/popper/createPopper.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {createPopper} from '@popperjs/core/lib/popper-lite';
2+
import flip from '@popperjs/core/lib/modifiers/flip';
3+
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow';
4+
import computeStyles from '@popperjs/core/lib/modifiers/computeStyles';
5+
preventOverflow.options = {padding: 13, boundariesElement: 'viewport'};
6+
export default function (ref, popper, fullWidth, placement) {
7+
const op = {placement, strategy: 'fixed', modifiers: [flip, preventOverflow]};
8+
if (fullWidth == true) {
9+
op.placement = 'bottom-start';
10+
op.modifiers.push({
11+
name: 'sameWidth',
12+
enabled: true,
13+
fn: ({state}) => {
14+
state.styles.popper.width = `${state.rects.reference.width}px`;
15+
},
16+
phase: 'beforeWrite',
17+
requires: [computeStyles],
18+
});
19+
}
20+
return createPopper(ref, popper, op);
21+
}

‎src/popper/getPopperMaxHeight.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
*
3+
* @param {Object} btnEl
4+
* @param {Number} margin
5+
*/
6+
export default function (btnEl, margin) {
7+
let {top, bottom} = btnEl.getBoundingClientRect();
8+
bottom = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) - bottom;
9+
return Math.max(top, bottom) - margin;
10+
}

‎src/popper/getPopperMaxWidth.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
*
3+
* @param {Object} popperEl
4+
* @param {Number} marginRight
5+
* @return {Number}
6+
*/
7+
export default function (popperEl, marginRight) {
8+
const {left} = popperEl.getBoundingClientRect();
9+
const maxWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0) - left - marginRight;
10+
return maxWidth;
11+
}

‎src/popper/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import createPopper from './createPopper.js';
2+
import getPopperMaxHeight from './getPopperMaxHeight.js';
3+
import getPopperMaxWidth from './getPopperMaxWidth.js';
4+
import Popper from './popper.js';
5+
6+
const getDeps = () => ({
7+
createPopper,
8+
getPopperMaxHeight,
9+
getPopperMaxWidth,
10+
});
11+
export default Popper.bind(null, getDeps);

‎src/popper/popper.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, {useLayoutEffect, useRef} from 'react';
2+
export default function Popper(getDeps, props) {
3+
const {createPopper, getPopperMaxHeight, getPopperMaxWidth} = getDeps();
4+
const {rootRef, children, style, fullWidth, placement} = props;
5+
const popperRef = useRef();
6+
const ref = useRef();
7+
ref.current = ref.current || {};
8+
useLayoutEffect(() => {
9+
popperRef.current.style.maxHeight = getPopperMaxHeight(rootRef.current, 8) + 'px';
10+
const popperIns = createPopper(rootRef.current, popperRef.current, fullWidth, placement);
11+
ref.current.popperIns = popperIns;
12+
return () => {
13+
popperIns.destroy();
14+
};
15+
}, []);
16+
useLayoutEffect(() => {
17+
ref.current &&
18+
ref.current.popperIns &&
19+
ref.current.popperIns.update().then(() => {
20+
popperRef.current.style.maxWidth = getPopperMaxWidth(popperRef.current, 8) + 'px';
21+
});
22+
}, [children]);
23+
return (
24+
<>
25+
<div ref={popperRef} className="rc-search-suggestions-popper" style={style}>
26+
{children}
27+
</div>
28+
</>
29+
);
30+
}

‎webpack.config.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const path = require('path');
2+
const pkg = require('./package.json');
3+
module.exports = (env) => {
4+
const isProduction = env === 'production';
5+
return {
6+
entry: {
7+
'react-custom-search-list': './src/index.js',
8+
},
9+
output: {
10+
filename: `[name].umd${isProduction ? '.min' : ''}.js`,
11+
path: path.resolve(__dirname, 'build'),
12+
library: pkg.name,
13+
libraryTarget: 'umd',
14+
publicPath: '/build/',
15+
umdNamedDefine: true,
16+
},
17+
devtool: isProduction ? 'source-map' : 'inline-source-map',
18+
mode: 'development',
19+
module: {
20+
rules: [
21+
{
22+
test: /\.m?js$/,
23+
exclude: /(node_modules|bower_components)/,
24+
use: {
25+
loader: 'babel-loader',
26+
},
27+
},
28+
],
29+
},
30+
resolve: {
31+
alias: {
32+
assets: path.resolve(__dirname, 'assets'),
33+
},
34+
},
35+
externals: {
36+
react: {
37+
commonjs: 'react',
38+
commonjs2: 'react',
39+
amd: 'React',
40+
root: 'React',
41+
},
42+
'react-dom': {
43+
commonjs: 'react-dom',
44+
commonjs2: 'react-dom',
45+
amd: 'ReactDOM',
46+
root: 'ReactDOM',
47+
},
48+
},
49+
};
50+
};

0 commit comments

Comments
 (0)
Please sign in to comment.