├── .circleci
└── config.yml
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── LICENSE
├── README.md
├── jest.config.js
├── package-lock.json
├── package.json
├── src
└── index.tsx
├── test
├── __snapshots__
│ └── index.test.tsx.snap
└── index.test.tsx
├── tsconfig.json
├── tsconfig.test.json
└── tslint.json
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | defaults: &defaults
4 | working_directory: ~/repo
5 | docker:
6 | - image: circleci/node:10.13.0
7 |
8 | jobs:
9 | install:
10 | <<: *defaults
11 | steps:
12 | - checkout
13 |
14 | - restore_cache:
15 | keys:
16 | - v1-dependencies-{{ checksum "package.json" }}
17 | # fallback to using the latest cache if no exact match is found
18 | - v1-dependencies-
19 |
20 | - run: npm install
21 | - save_cache:
22 | paths:
23 | - node_modules
24 | key: v1-dependencies-{{ checksum "package.json" }}
25 |
26 | - persist_to_workspace:
27 | root: ~/repo
28 | paths: .
29 | lint:
30 | <<: *defaults
31 | steps:
32 | - attach_workspace:
33 | at: ~/repo
34 |
35 | - run:
36 | name: Run lint
37 | command: npm run lint
38 | test:
39 | <<: *defaults
40 | steps:
41 | - attach_workspace:
42 | at: ~/repo
43 |
44 | - run:
45 | name: Run tests
46 | command: npm test
47 | build:
48 | <<: *defaults
49 | steps:
50 | - attach_workspace:
51 | at: ~/repo
52 |
53 | - run:
54 | name: Build project
55 | command: npm run build
56 | publish:
57 | # Only runs when the branch is tagged (using git tag v...)
58 | <<: *defaults
59 | steps:
60 | - attach_workspace:
61 | at: ~/repo
62 | - run:
63 | name: Build project
64 | command: npm run build
65 | - run:
66 | name: Authenticate with registry
67 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
68 | - run:
69 | name: Publish package
70 | command: npm publish
71 |
72 | workflows:
73 | version: 2
74 | lint_test_publish:
75 | jobs:
76 | - install
77 | - lint:
78 | requires:
79 | - install
80 | - test:
81 | requires:
82 | - install
83 | - build:
84 | requires:
85 | - install
86 | - publish:
87 | requires:
88 | - test
89 | filters:
90 | tags:
91 | only: /^v.*/
92 | branches:
93 | ignore: /.*/
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | lib/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Fabian Schliski
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://npmjs.org/package/antd-password-input-strength)
2 | [](https://github.com/Kombustor/antd-password-input-strength/issues)
3 | [](https://github.com/Kombustor/antd-password-input-strength/blob/master/LICENSE)
4 | [](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2FKombustor%2Fantd-password-input-strength) [](https://greenkeeper.io/)
5 |
6 | # antd-password-input-strength
7 |
8 | > Antd Input Component with password-strength indicator.
9 |
10 | 
11 |
12 | ## Features
13 |
14 | - Drop-in replacement for antd's Input component
15 | - Customizable
16 | - Uses [tai-password-strength](https://www.npmjs.com/package/tai-password-strength) for password strength estimation
17 |
18 | _Note: tai-password-strength is a rather large library. Use code splitting to only load the library when necessary._
19 |
20 | ## Install
21 |
22 | ```bash
23 | npm install --save antd-password-input-strength
24 | ```
25 |
26 | or
27 |
28 | ```bash
29 | yarn add --save antd-password-input-strength
30 | ```
31 |
32 | _Note: antd and react/react-dom are peer dependencies. You should only use this library in a React/AntD project._
33 |
34 | ## Usage
35 |
36 | Use as a drop-in replacement for antd's [Input](https://ant.design/components/input/):
37 |
38 | ```tsx
39 |
41 |
42 |
43 |
44 | ```
45 |
46 | With ```Form.create()```:
47 |
48 | ```tsx
49 |
51 | {this.props.form.getFieldDecorator("password", {
52 | rules: [{
53 | required: true,
54 | message: "Please enter your password"
55 | }]
56 | })()}
57 |
58 |
59 | ```
60 |
61 | With custom settings:
62 |
63 | ```tsx
64 |
66 | console.log("Changed")}
73 | size="large"
74 | />
75 |
76 |
77 | ```
78 |
79 | With validation:
80 |
81 | ```tsx
82 | function ValidationExample() {
83 | const [level, setLevel] = useState(0)
84 |
85 | const minLevel = 1;
86 | const errorMessage = 'Password is too weak';
87 |
88 | return (
89 | {
94 | return level >= minLevel ? Promise.resolve() : Promise.reject(errorMessage);
95 | },
96 | message: errorMessage
97 | }]}
98 | >
99 |
100 |
101 |
102 | );
103 | }
104 | ```
105 |
106 | ## API
107 |
108 | ### PasswordInput
109 |
110 | | props | type | description |
111 | | -- | -- | -- |
112 | | settings | PasswordInputSettings | Strength indicator display settings |
113 | | onLevelChange | (newLevel: 0 | 1 | 2 | 3 | 4) => void | Called when the input level changes |
114 | | ...props | [InputProps](https://ant.design/components/input/#Input) | Pass additional properties to the underlying [Input](https://ant.design/components/input/) component
115 |
116 | ### PasswordInputSettings
117 |
118 | | props | type | description |
119 | | -- | -- | -- |
120 | | colorScheme | ColorScheme | Modify the indicator's color scheme |
121 | | height | number | Change indicator bar height (in px) |
122 | | alwaysVisible | boolean | If false, the bar only appears if the input field isn't empty |
123 |
124 | > Default:
125 |
126 | ```jsx
127 | {
128 | colorScheme: [...],
129 | height: 3,
130 | alwaysVisible: false
131 | }
132 | ```
133 |
134 | ### ColorScheme
135 |
136 | | props | type | description |
137 | | -- | -- | -- |
138 | | levels | string[] | Array of CSS color codes for the different strength levels:
`levels[0] = weakest`, `levels[4] = strongest` |
139 | | noLevel| string | CSS color code for non-colored strength indicator bars. |
140 |
141 | > Default:
142 |
143 | ```jsx
144 | {
145 | levels: ["#ff4033", "#fe940d", "#ffd908", "#cbe11d", "#6ecc3a"],
146 | noLevel: "lightgrey"
147 | }
148 | ```
149 |
150 | ## License
151 |
152 | MIT
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'jsdom',
4 | "globals": {
5 | "ts-jest": {
6 | "tsconfig": "./tsconfig.test.json"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "antd-password-input-strength",
3 | "author": "Fabian Schliski",
4 | "license": "MIT",
5 | "version": "2.0.1",
6 | "description": "antd Input component with password-strength indicator.",
7 | "main": "lib/index.js",
8 | "scripts": {
9 | "test": "jest",
10 | "coverage": "npm test -- --coverage",
11 | "lint": "tslint \"src/**/*.(ts|tsx)\" --project tsconfig.json --project tsconfig.json",
12 | "build": "npm run clean && tsc",
13 | "clean": "rimraf lib"
14 | },
15 | "peerDependencies": {
16 | "antd": ">=4",
17 | "react": ">=16"
18 | },
19 | "files": [
20 | "lib",
21 | "LICENSE"
22 | ],
23 | "types": "lib/index.d.ts",
24 | "keywords": [
25 | "antd",
26 | "ant-design",
27 | "css",
28 | "password",
29 | "strength",
30 | "input"
31 | ],
32 | "bugs": {
33 | "url": "https://github.com/Kombustor/antd-password-input-strength/issues"
34 | },
35 | "homepage": "https://github.com/Kombustor/antd-password-input-strength#readme",
36 | "repository": {
37 | "type": "git",
38 | "url": "git+https://github.com/Kombustor/antd-password-input-strength.git"
39 | },
40 | "dependencies": {
41 | "tai-password-strength": "^1.1.3"
42 | },
43 | "devDependencies": {
44 | "@types/jest": "^24.0.15",
45 | "@types/node": "^12.6.6",
46 | "@types/react": "^16.8.23",
47 | "@types/react-test-renderer": "^16.8.2",
48 | "@types/zxcvbn": "^4.4.0",
49 | "antd": "4.19.5",
50 | "jest": "^27.5.1",
51 | "react": "^16.8.6",
52 | "react-dom": "^16.8.6",
53 | "react-test-renderer": "^16.8.6",
54 | "rimraf": "^2.6.3",
55 | "ts-jest": "^27.1.4",
56 | "tslint": "^5.18.0",
57 | "typescript": "^4.6.3"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Input, InputProps, InputRef } from "antd";
2 | import React, { useEffect, useState } from "react";
3 | import { PasswordStrength } from 'tai-password-strength';
4 |
5 | const PASSWORD_STRENGTH = new PasswordStrength();
6 | const PASSWORD_STRENGTH_CODE = ['VERY_WEAK', 'WEAK', 'REASONABLE', 'STRONG', 'VERY_STRONG'];
7 | export const defaultSettings = {
8 | colorScheme: {
9 | levels: ["#ff4033", "#fe940d", "#ffd908", "#cbe11d", "#6ecc3a"],
10 | noLevel: "lightgrey"
11 | },
12 | height: 3,
13 | alwaysVisible: false
14 | }
15 |
16 | interface LevelChangeProps {
17 | onLevelChange?: (newLevel: number) => void;
18 | }
19 |
20 | export interface PasswordInputProps extends LevelChangeProps {
21 | settings?: PasswordInputSettings;
22 | }
23 |
24 | export const PasswordInput = React.forwardRef(({
25 | settings = defaultSettings,
26 | onLevelChange,
27 | ...props
28 | }: PasswordInputProps & Partial, ref: React.Ref) => {
29 | const [input, setInput] = useState('')
30 |
31 | return (
32 | <>
33 | { setInput(e.target.value); props?.onChange?.(e) }}
38 | />
39 |
44 | >
45 | );
46 | })
47 |
48 | interface PasswordStrengthIndicatorProps extends LevelChangeProps {
49 | input: string;
50 | settings: PasswordInputSettings;
51 | }
52 |
53 | export const PasswordStrengthIndicator = ({ input, settings, onLevelChange }: PasswordStrengthIndicatorProps) => {
54 | // Calculate level
55 | const level = React.useMemo(() => {
56 | return input.length == 0 ? -1 : PASSWORD_STRENGTH_CODE.indexOf(PASSWORD_STRENGTH.check(input).strengthCode);
57 | }, [input])
58 |
59 | // Calculate indicators
60 | const indicators: React.ReactElement[] = React.useMemo(() => {
61 | const ind = [];
62 | for (let i = 0; i < 5; i++) {
63 | const color =
64 | i <= level
65 | ? settings.colorScheme.levels[level]
66 | : settings.colorScheme.noLevel;
67 | ind.push();
68 | }
69 |
70 | return ind;
71 | }, [level, settings])
72 |
73 | useEffect(() => {
74 | onLevelChange?.(level);
75 | }, [level])
76 |
77 | if (!settings.alwaysVisible && level < 0) {
78 | return null;
79 | }
80 |
81 | return {indicators}
;
82 | };
83 |
84 | function getWrapperStyle(height: number) {
85 | return {
86 | lineHeight: height + "px"
87 | };
88 | }
89 |
90 | function getIndicatorStyle(color: string, height: number) {
91 | return {
92 | display: "inline-block",
93 | width: "20%",
94 | backgroundColor: color,
95 | height: height + "px",
96 | borderRadius: "2px"
97 | };
98 | }
99 |
100 | export interface PasswordInputSettings {
101 | colorScheme: ColorScheme;
102 | height: number;
103 | alwaysVisible: boolean;
104 | }
105 |
106 | export interface ColorScheme {
107 | levels: string[],
108 | noLevel: string;
109 | }
110 |
--------------------------------------------------------------------------------
/test/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PasswordInput should render correctly 1`] = `
4 |
8 |
20 |
23 |
32 |
48 |
49 |
50 |
51 | `;
52 |
--------------------------------------------------------------------------------
/test/index.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import * as renderer from 'react-test-renderer'
3 | import { PasswordInput, PasswordInputSettings } from '../src'
4 |
5 | describe('PasswordInput', () => {
6 |
7 | /** Snapshot testing */
8 | it('should render correctly', () => {
9 | const settings: PasswordInputSettings = {
10 | colorScheme: {
11 | levels: ["#ff4033", "#fe940d", "#ffd908", "#cbe11d", "#6ecc3a"],
12 | noLevel: "lightgrey"
13 | },
14 | height: 3,
15 | alwaysVisible: false
16 | };
17 | const component = renderer.create(
18 |
21 | );
22 | let tree = component.toJSON();
23 |
24 | expect(tree).toMatchSnapshot()
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "jsx": "react",
6 | "moduleResolution": "node",
7 | "esModuleInterop": true,
8 | "declaration": true,
9 | "outDir": "lib",
10 | "lib": ["es6", "es7", "dom"],
11 | "baseUrl": "src",
12 | "skipLibCheck": true
13 | },
14 | "include": [
15 | "src"
16 | ],
17 | "exclude": ["lib","node_modules"]
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "esModuleInterop": true
5 | }
6 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [ "tslint-react" ]
3 | }
4 |
--------------------------------------------------------------------------------