use-password-policy 1.0.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,80 +1,204 @@
1
- ## usePasswordPolicy Hook
1
+ # use-password-policy
2
2
 
3
- This React hook simplifies password policy enforcement in your application. It provides a flexible and customizable way to validate passwords against various criteria, ensuring strong password security.
3
+ [![npm version](https://img.shields.io/npm/v/use-password-policy.svg)](https://www.npmjs.com/package/use-password-policy)
4
+ [![npm downloads](https://img.shields.io/npm/dm/use-password-policy.svg)](https://www.npmjs.com/package/use-password-policy)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
4
7
 
5
- Features:
6
- - Built-in Policies: Enforces common password complexity requirements like minimum length, case sensitivity, digit inclusion, and special characters.
8
+ A powerful, lightweight, and fully customizable solution for real-time password strength validation in React. Comes with a flexible hook and a zero-config, all-in-one UI component.
7
9
 
8
- - Custom Policies: Define your own validation checks using regular expressions or custom functions for even more granular control.
10
+ ---
9
11
 
10
- - Configurable Defaults: Specify a default configuration for common policies, or override them with custom settings.
12
+ ### [➡️ View Live Demo & Playground](https://rahulpatwa1303.github.io/use-password-policy/)
11
13
 
12
- Installation:
14
+ *(Replace this with your final GitHub Pages URL)*
13
15
 
14
- ```
16
+
17
+ *(**Action Needed:** Record a GIF of your awesome demo and replace this link!)*
18
+
19
+ ---
20
+
21
+ ## ✨ Why `use-password-policy`?
22
+
23
+ - **🚀 Two Ways to Use:** Get full control with the `usePasswordPolicy` hook, or get running in seconds with the drop-in `<PasswordPolicyInput />` component.
24
+ - **🔧 Fully Customizable:** Easily configure policies like min-length, character requirements, and even add your own complex rules with custom functions or regex.
25
+ - **💅 Zero-Config Styling:** The UI component works out-of-the-box with self-contained styles, but is easily overridable.
26
+ - **✅ Rich & Reactive Feedback:** Provides a simple `isValid` boolean, a detailed `policyState` object, and a `strengthScore` to easily build any UI you can imagine.
27
+ - **♿ Accessibility First:** The component is designed with accessibility in mind, ready to be paired with a `<label>`.
28
+ - **📦 Tiny & Performant:** Zero dependencies and built with performance in mind, using `useMemo` to prevent unnecessary recalculations.
29
+
30
+ ## 💾 Installation
31
+
32
+ ```bash
15
33
  npm install use-password-policy
34
+ # or
35
+ yarn add use-password-policy
36
+ ```
37
+
38
+ ## 🚀 Usage
39
+
40
+ You have two great ways to implement password validation.
41
+
42
+ ### 1. The Easy Way: `<PasswordPolicyInput />` Component
43
+
44
+ For maximum speed, drop the component directly into your form. It includes the input, strength meter, and requirements list all-in-one.
45
+
46
+ ```tsx
47
+ import { PasswordPolicyInput } from 'use-password-policy';
48
+
49
+ function MyForm() {
50
+ const [isValid, setIsValid] = useState(false);
51
+
52
+ return (
53
+ <form>
54
+ <label htmlFor="signup-password">Create a Password</label>
55
+ <PasswordPolicyInput
56
+ id="signup-password"
57
+ name="password"
58
+ placeholder="Enter a secure password..."
59
+ onPasswordChange={(_, validation) => {
60
+ setIsValid(validation.isValid);
61
+ }}
62
+ policyOptions={{ minLength: 8, numberCheck: true, specialCharCheck: true }}
63
+ />
64
+ <button type="submit" disabled={!isValid}>
65
+ Sign Up
66
+ </button>
67
+ </form>
68
+ );
69
+ }
16
70
  ```
17
71
 
18
- Usage:
19
-
20
- ```javascript
21
-
22
- import { usePasswordPolicy } from './use-password-policy';
23
- function MyComponent() {
24
- const [password, setPassword] = useState('');
25
- const policy = usePasswordPolicy({
26
- password,
27
- config: { minLength: 12 },
28
- customPolicies: [{ name: 'noRepeatedChars', regex: /^(?!.*(.)\1)/ },],});
29
- const isPasswordValid = Object.values(policy).every(check => check);
30
-
31
- return (
32
- <form>
33
- <input
34
- type="password"
35
- value={password}
36
- onChange={e => setPassword(e.target.value)}
37
- />
38
- {isPasswordValid ? (
39
- <p>Password is strong!</p>
40
- ) : (
41
- <ul>
42
- {Object.entries(policy).map(([key, value]) => (
43
- <li key={key}>{!value && key.replace(/([A-Z])/g, ' $1')}</li>
44
- ))}
45
- </ul>
46
- )}
47
- </form>
48
- );
72
+ ### 2. The Powerful Way: `usePasswordPolicy` Hook
73
+
74
+ For complete control over your UI, use the hook and build your own components.
75
+
76
+ ```tsx
77
+ import { usePasswordPolicy } from 'use-password-policy';
78
+
79
+ function MyCustomForm() {
80
+ const [password, setPassword] = useState('');
81
+ const { isValid, strengthLabel, policyState } = usePasswordPolicy({
82
+ password: password,
83
+ minLength: 10,
84
+ uppercaseCheck: true,
85
+ customRules: [{ name: 'noSpaces', test: (p) => !/\\s/.test(p) }],
86
+ });
87
+
88
+ return (
89
+ <form>
90
+ <input
91
+ type="password"
92
+ value={password}
93
+ onChange={(e) => setPassword(e.target.value)}
94
+ />
95
+ <div>Strength: {strengthLabel}</div>
96
+ <ul>
97
+ {Object.entries(policyState).map(([rule, passed]) => (
98
+ <li key={rule} style={{ color: passed ? 'green' : 'red' }}>
99
+ {rule}
100
+ </li>
101
+ ))}
102
+ </ul>
103
+ <button type="submit" disabled={!isValid}>
104
+ Submit
105
+ </button>
106
+ </form>
107
+ );
49
108
  }
50
109
  ```
51
110
 
52
- **Props:**
53
-
54
- | Prop Name | Props Values | Type | Description | Default Value |
55
- | ---------------- | ---------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------- |
56
- | `password` | | string | The password to be validated. | Required |
57
- | `config` | | object (optional) | An object overriding default configuration for built-in policies. | |
58
- | | - `minLength` | number | Minimum password length | 8 |
59
- | | - `uppercaseCharRegex` | RegExp | Regular expression for uppercase characters | /[A-Z]/ |
60
- | | - `digitRegex` | RegExp | Regular expression for digits | /\d/ |
61
- | | - `specialCharRegex` | RegExp | Regular expression for special characters | /[!@#$%^&*()_+-=[]{};':"\| |
62
- | | - `caseCheck` | boolean | This flag determines the availability of a feature or functionality within your application's state | true |
63
- | | - `lengthCheck` | boolean | This flag determines the availability of a feature or functionality within your application's state | true |
64
- | | - `digitCheck` | boolean | This flag determines the availability of a feature or functionality within your application's state | true |
65
- | | - `specialCharCheck` | boolean | This flag determines the availability of a feature or functionality within your application's state | true |
66
- | `customPolicies` | | array of objects (optional) | An array of custom policies to enforce. | |
67
- | | - `name` | string | Name of the custom policy for clarity in feedback. | |
68
- | | - `regex` | RegExp (optional) | Regular expression for custom validation. | |
69
- | | - `checkFunction` | function (optional) | A function taking the password as an argument and returning true/false for a custom check. | |
70
- | useDefaultConfig | | boolean | This flag controls whether to use the default configuration for validations or checks in your application. | true |
71
-
72
- **Return Value:**
73
-
74
- An object containing boolean values for each policy check (built-in and custom). Use `Object.values(policy).every(check => check)` to determine if all policies are satisfied.
75
-
76
- **Benefits:**
77
-
78
- - **Improved Security:** Enforces strong passwords, reducing the risk of brute-force attacks and data breaches.
79
- - **Enhanced User Experience:** Provides clear feedback to users on password strength, guiding them towards creating secure passwords.
80
- - **Customization:** Adapts to your specific security requirements through configurable defaults and custom policies.
111
+ ## 📖 API Reference
112
+
113
+ ### `<PasswordPolicyInput />` Props
114
+
115
+ | Prop | Type | Default | Description |
116
+ | ---------------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------------------------------- |
117
+ | `policyOptions` | `PasswordPolicyOptions` | `{}` | Same options as the `usePasswordPolicy` hook to control validation logic. |
118
+ | `onPasswordChange` | `(password: string, validation: HookReturnValue) => void` | `null` | Callback fired on change, providing the password and full validation state. |
119
+ | `showStrengthMeter` | `boolean` | `true` | Toggles the visibility of the strength meter bar. |
120
+ | `showRequirementsList` | `boolean` | `true` | Toggles the visibility of the pass/fail requirements list. |
121
+ | `showToggleButton` | `boolean` | `true` | Toggles the visibility of the show/hide password button. |
122
+ | `...restInputProps` | `React.InputHTMLAttributes` | | All other standard input props (`id`, `name`, `placeholder`, etc.) are passed to the `<input>`. |
123
+
124
+ <br/>
125
+
126
+ ### `usePasswordPolicy` Hook
127
+
128
+ #### Options (`PasswordPolicyOptions`)
129
+
130
+ | Prop | Type | Default | Description |
131
+ | ---------------------- | -------------- | ---------- | ----------------------------------------------------------- |
132
+ | `password` | `string` | `''` | The password string to validate. |
133
+ | `minLength` | `number` | `8` | Minimum password length. |
134
+ | `lowercaseCheck` | `boolean` | `true` | Requires at least one lowercase letter. |
135
+ | `uppercaseCheck` | `boolean` | `true` | Requires at least one uppercase letter. |
136
+ | `numberCheck` | `boolean` | `true` | Requires at least one number. |
137
+ | `specialCharCheck` | `boolean` | `true` | Requires at least one special character. |
138
+ | `customRules` | `PolicyRule[]` | `[]` | An array of custom validation rules. |
139
+
140
+ #### Return Value (`HookReturnValue`)
141
+
142
+ | Key | Type | Description |
143
+ | --------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------- |
144
+ | `isValid` | `boolean` | `true` only if all active policies are met. |
145
+ | `strengthScore` | `number` | The number of policies that have passed. |
146
+ | `strengthLabel` | `'Very Weak' \| 'Weak' \| 'Medium' \| 'Strong' \| 'Very Strong'` | A human-readable strength label. |
147
+ | `policyState` | `object` | An object with boolean flags for each active policy (`{ minLength: true, uppercase: false, ... }`). |
148
+
149
+ ## 🎨 Customizing Styles
150
+
151
+ The `<PasswordPolicyInput />` component is built with `styled-components` for complete style isolation and easy customization. You have two primary ways to apply your own styles:
152
+
153
+ ### 1. Theming with `styled()`
154
+
155
+ For deep customization, wrap the component with `styled()` from `styled-components`. You can easily change the theme by overriding the internal CSS variables, or target any internal element for specific changes.
156
+
157
+ ```jsx
158
+ import styled from 'styled-components';
159
+ import { PasswordPolicyInput } from 'use-password-policy';
160
+
161
+ const MyStyledInput = styled(PasswordPolicyInput)`
162
+ /* Override theme variables */
163
+ --rpp-accent: #ff6347; // Use a tomato red accent
164
+
165
+ /* Override specific elements */
166
+ input {
167
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
168
+ }
169
+ `;
170
+
171
+ // Then use <MyStyledInput /> in your app!
172
+ ```
173
+
174
+ **Available CSS Variables for Theming:**
175
+
176
+ | Variable | Default | Description |
177
+ | ----------------- | ---------- | ---------------------------------- |
178
+ | `--rpp-accent` | `#646cff` | Accent color for focus, buttons. |
179
+ | `--rpp-success` | `#27ae60` | Color for passed requirements. |
180
+ | `--rpp-danger` | `#c0392b` | Color for failed requirements. |
181
+ | `--rpp-weak` | `#f39c12` | Strength meter color for weak. |
182
+ | `--rpp-medium` | `#d35400` | Strength meter color for medium. |
183
+ | `--rpp-bg` | `#f9f9f9` | Component's background color. |
184
+ | `--rpp-border` | `#e0e0e0` | Component's border color. |
185
+ | `--rpp-text` | `#333` | Component's main text color. |
186
+
187
+ ### 2. Applying Custom Class Names
188
+
189
+ To apply layout styles (like margins or flex properties), simply pass a `className`. This works perfectly with utility-class frameworks like Tailwind CSS.
190
+
191
+ ```jsx
192
+ import { PasswordPolicyInput } from 'use-password-policy';
193
+
194
+ // Example with Tailwind CSS or a custom utility class
195
+ <PasswordPolicyInput className="mb-4 w-full" />
196
+ ```
197
+
198
+ ## ❤️ Contributing
199
+
200
+ Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/rahulpatwa1303/use-password-policy/issues).
201
+
202
+ ## 📄 License
203
+
204
+ This project is [MIT](https://github.com/rahulpatwa1303/use-password-policy/blob/main/LICENSE) licensed.
@@ -0,0 +1,43 @@
1
+ import React, { FC } from 'react';
2
+
3
+ interface PolicyRule {
4
+ name: string;
5
+ optionsKey?: keyof PasswordPolicyOptions;
6
+ test: (password: string, options: Required<PasswordPolicyOptions>) => boolean;
7
+ }
8
+ interface PasswordPolicyOptions {
9
+ password?: string;
10
+ minLength?: number;
11
+ lowercaseCheck?: boolean;
12
+ uppercaseCheck?: boolean;
13
+ numberCheck?: boolean;
14
+ specialCharCheck?: boolean;
15
+ customRules?: PolicyRule[];
16
+ lowercaseRegex?: RegExp;
17
+ uppercaseRegex?: RegExp;
18
+ numberRegex?: RegExp;
19
+ specialCharRegex?: RegExp;
20
+ }
21
+ interface PasswordPolicyState {
22
+ [key: string]: boolean;
23
+ }
24
+ interface HookReturnValue {
25
+ password?: string;
26
+ isValid: boolean;
27
+ strengthScore: number;
28
+ strengthLabel: 'Very Weak' | 'Weak' | 'Medium' | 'Strong' | 'Very Strong';
29
+ policyState: PasswordPolicyState;
30
+ }
31
+
32
+ declare const usePasswordPolicy: (options?: PasswordPolicyOptions) => HookReturnValue;
33
+
34
+ interface PasswordPolicyInputProps extends React.ComponentPropsWithoutRef<'input'> {
35
+ policyOptions?: PasswordPolicyOptions;
36
+ onPasswordChange?: (password: string, validation: HookReturnValue) => void;
37
+ showStrengthMeter?: boolean;
38
+ showRequirementsList?: boolean;
39
+ showToggleButton?: boolean;
40
+ }
41
+ declare const PasswordPolicyInput: FC<PasswordPolicyInputProps>;
42
+
43
+ export { type HookReturnValue, PasswordPolicyInput, type PasswordPolicyInputProps, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyRule, usePasswordPolicy };
@@ -0,0 +1,43 @@
1
+ import React, { FC } from 'react';
2
+
3
+ interface PolicyRule {
4
+ name: string;
5
+ optionsKey?: keyof PasswordPolicyOptions;
6
+ test: (password: string, options: Required<PasswordPolicyOptions>) => boolean;
7
+ }
8
+ interface PasswordPolicyOptions {
9
+ password?: string;
10
+ minLength?: number;
11
+ lowercaseCheck?: boolean;
12
+ uppercaseCheck?: boolean;
13
+ numberCheck?: boolean;
14
+ specialCharCheck?: boolean;
15
+ customRules?: PolicyRule[];
16
+ lowercaseRegex?: RegExp;
17
+ uppercaseRegex?: RegExp;
18
+ numberRegex?: RegExp;
19
+ specialCharRegex?: RegExp;
20
+ }
21
+ interface PasswordPolicyState {
22
+ [key: string]: boolean;
23
+ }
24
+ interface HookReturnValue {
25
+ password?: string;
26
+ isValid: boolean;
27
+ strengthScore: number;
28
+ strengthLabel: 'Very Weak' | 'Weak' | 'Medium' | 'Strong' | 'Very Strong';
29
+ policyState: PasswordPolicyState;
30
+ }
31
+
32
+ declare const usePasswordPolicy: (options?: PasswordPolicyOptions) => HookReturnValue;
33
+
34
+ interface PasswordPolicyInputProps extends React.ComponentPropsWithoutRef<'input'> {
35
+ policyOptions?: PasswordPolicyOptions;
36
+ onPasswordChange?: (password: string, validation: HookReturnValue) => void;
37
+ showStrengthMeter?: boolean;
38
+ showRequirementsList?: boolean;
39
+ showToggleButton?: boolean;
40
+ }
41
+ declare const PasswordPolicyInput: FC<PasswordPolicyInputProps>;
42
+
43
+ export { type HookReturnValue, PasswordPolicyInput, type PasswordPolicyInputProps, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyRule, usePasswordPolicy };
package/dist/index.js ADDED
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ PasswordPolicyInput: () => PasswordPolicyInput,
34
+ usePasswordPolicy: () => usePasswordPolicy
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/use-password-policy.ts
39
+ var import_react = require("react");
40
+ var DEFAULT_POLICIES = [
41
+ {
42
+ name: "minLength",
43
+ optionsKey: "minLength",
44
+ test: (password, options) => password.length >= options.minLength
45
+ },
46
+ {
47
+ name: "uppercase",
48
+ optionsKey: "uppercaseCheck",
49
+ test: (password, options) => options.uppercaseRegex.test(password)
50
+ },
51
+ {
52
+ name: "lowercase",
53
+ optionsKey: "lowercaseCheck",
54
+ test: (password, options) => options.lowercaseRegex.test(password)
55
+ },
56
+ {
57
+ name: "number",
58
+ optionsKey: "numberCheck",
59
+ test: (password, options) => options.numberRegex.test(password)
60
+ },
61
+ {
62
+ name: "specialChar",
63
+ optionsKey: "specialCharCheck",
64
+ test: (password, options) => options.specialCharRegex.test(password)
65
+ }
66
+ ];
67
+ var DEFAULT_OPTIONS = {
68
+ minLength: 8,
69
+ lowercaseCheck: true,
70
+ uppercaseCheck: true,
71
+ numberCheck: true,
72
+ specialCharCheck: true,
73
+ customRules: [],
74
+ lowercaseRegex: /[a-z]/,
75
+ uppercaseRegex: /[A-Z]/,
76
+ numberRegex: /\d/,
77
+ specialCharRegex: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/
78
+ };
79
+ var usePasswordPolicy = (options = {}) => {
80
+ const { password = "" } = options;
81
+ const mergedOptions = (0, import_react.useMemo)(
82
+ () => ({ ...DEFAULT_OPTIONS, ...options }),
83
+ [options]
84
+ );
85
+ const activePolicies = (0, import_react.useMemo)(() => {
86
+ const activeDefaultPolicies = DEFAULT_POLICIES.filter(
87
+ (policy) => policy.optionsKey && mergedOptions[policy.optionsKey]
88
+ );
89
+ return [...activeDefaultPolicies, ...mergedOptions.customRules];
90
+ }, [mergedOptions]);
91
+ const policyState = (0, import_react.useMemo)(() => {
92
+ const state = {};
93
+ for (const policy of activePolicies) {
94
+ state[policy.name] = policy.test(password, mergedOptions);
95
+ }
96
+ return state;
97
+ }, [password, activePolicies, mergedOptions]);
98
+ const { score, label, isValid } = (0, import_react.useMemo)(() => {
99
+ const passedPolicies = Object.values(policyState).filter(Boolean);
100
+ const score2 = passedPolicies.length;
101
+ const totalPolicies = activePolicies.length;
102
+ const strengthPercentage = totalPolicies > 0 ? score2 / totalPolicies : 0;
103
+ let label2 = "Very Weak";
104
+ if (strengthPercentage >= 1) {
105
+ label2 = "Very Strong";
106
+ } else if (strengthPercentage >= 0.75) {
107
+ label2 = "Strong";
108
+ } else if (strengthPercentage >= 0.5) {
109
+ label2 = "Medium";
110
+ } else if (strengthPercentage > 0) {
111
+ label2 = "Weak";
112
+ }
113
+ return {
114
+ score: score2,
115
+ label: label2,
116
+ isValid: score2 === totalPolicies && totalPolicies > 0
117
+ };
118
+ }, [policyState, activePolicies]);
119
+ return {
120
+ password,
121
+ policyState,
122
+ isValid,
123
+ strengthScore: score,
124
+ strengthLabel: label
125
+ };
126
+ };
127
+
128
+ // src/PasswordPolicyInput.tsx
129
+ var import_react2 = require("react");
130
+ var import_styled_components = __toESM(require("styled-components"));
131
+ var import_jsx_runtime = require("react/jsx-runtime");
132
+ var STRENGTH_COLOR_MAP = {
133
+ "Very Weak": "var(--rpp-weak)",
134
+ "Weak": "var(--rpp-weak)",
135
+ "Medium": "var(--rpp-medium)",
136
+ "Strong": "var(--rpp-success)",
137
+ "Very Strong": "var(--rpp-success)"
138
+ };
139
+ var Container = import_styled_components.default.div`
140
+ /* CSS variables define the component's internal theme */
141
+ --rpp-accent: #646cff;
142
+ --rpp-success: #27ae60;
143
+ --rpp-danger: #c0392b;
144
+ --rpp-weak: #f39c12;
145
+ --rpp-medium: #d35400;
146
+ --rpp-bg: #f9f9f9;
147
+ --rpp-border: #e0e0e0;
148
+ --rpp-text: #333;
149
+
150
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 0.75rem;
154
+ `;
155
+ var InputWrapper = import_styled_components.default.div`
156
+ position: relative;
157
+ `;
158
+ var Input = import_styled_components.default.input`
159
+ box-sizing: border-box;
160
+ width: 100%;
161
+ padding: 0.75rem;
162
+ border: 1px solid var(--rpp-border);
163
+ border-radius: 6px;
164
+ font-size: 1rem;
165
+
166
+ &:focus {
167
+ border-color: var(--rpp-accent);
168
+ outline: none;
169
+ }
170
+ `;
171
+ var ToggleButton = import_styled_components.default.button`
172
+ position: absolute;
173
+ top: 50%;
174
+ right: 0.5rem;
175
+ transform: translateY(-50%);
176
+ background: none;
177
+ border: none;
178
+ cursor: pointer;
179
+ padding: 0.5rem;
180
+ color: #888;
181
+ display: flex;
182
+ align-items: center;
183
+ `;
184
+ var StrengthMeter = import_styled_components.default.div`
185
+ display: flex;
186
+ gap: 0.25rem;
187
+ height: 6px;
188
+ `;
189
+ var StrengthMeterSegment = import_styled_components.default.div`
190
+ flex: 1;
191
+ background-color: var(--rpp-border);
192
+ border-radius: 3px;
193
+ transition: background-color 0.3s;
194
+
195
+ ${({ isFilled, strengthLabel }) => (
196
+ // FIX: Explicitly type props
197
+ isFilled && import_styled_components.css`
198
+ background-color: ${STRENGTH_COLOR_MAP[strengthLabel]};
199
+ `
200
+ )}
201
+ `;
202
+ var RequirementsList = import_styled_components.default.ul`
203
+ list-style: none;
204
+ padding: 0;
205
+ margin: 0;
206
+ display: flex;
207
+ flex-direction: column;
208
+ gap: 0.5rem;
209
+ font-size: 0.875rem;
210
+ `;
211
+ var RequirementItem = import_styled_components.default.li`
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 0.5rem;
215
+ transition: color 0.2s;
216
+ /* FIX: Explicitly type props */
217
+ color: ${({ passed }) => passed ? "var(--rpp-success)" : "var(--rpp-danger)"};
218
+ `;
219
+ var EyeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "3" })
222
+ ] });
223
+ var EyeOffIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" }),
225
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
226
+ ] });
227
+ var PasswordPolicyInput = ({
228
+ policyOptions,
229
+ onPasswordChange,
230
+ showStrengthMeter = true,
231
+ showRequirementsList = true,
232
+ showToggleButton = true,
233
+ className,
234
+ ...restInputProps
235
+ }) => {
236
+ const [password, setPassword] = (0, import_react2.useState)("");
237
+ const [showPassword, setShowPassword] = (0, import_react2.useState)(false);
238
+ const validation = usePasswordPolicy({ ...policyOptions, password });
239
+ const { policyState, strengthScore, strengthLabel } = validation;
240
+ (0, import_react2.useEffect)(() => {
241
+ onPasswordChange?.(password, validation);
242
+ }, [password, validation, onPasswordChange]);
243
+ const formatPolicyName = (name) => name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
244
+ const handlePasswordChange = (e) => {
245
+ setPassword(e.target.value);
246
+ };
247
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Container, { className, children: [
248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InputWrapper, { children: [
249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
250
+ Input,
251
+ {
252
+ type: showPassword ? "text" : "password",
253
+ value: password,
254
+ onChange: handlePasswordChange,
255
+ ...restInputProps
256
+ }
257
+ ),
258
+ showToggleButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleButton, { type: "button", onClick: () => setShowPassword(!showPassword), children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EyeIcon, {}) })
259
+ ] }),
260
+ showStrengthMeter && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StrengthMeter, { children: Array.from({ length: 5 }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
261
+ StrengthMeterSegment,
262
+ {
263
+ isFilled: strengthScore > index,
264
+ strengthLabel
265
+ },
266
+ index
267
+ )) }),
268
+ showRequirementsList && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequirementsList, { children: Object.entries(policyState).map(([name, passed]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RequirementItem, { passed, children: [
269
+ passed ? "\u2713" : "\u2717",
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatPolicyName(name) })
271
+ ] }, name)) })
272
+ ] });
273
+ };
274
+ // Annotate the CommonJS export names for ESM import in node:
275
+ 0 && (module.exports = {
276
+ PasswordPolicyInput,
277
+ usePasswordPolicy
278
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,240 @@
1
+ // src/use-password-policy.ts
2
+ import { useMemo } from "react";
3
+ var DEFAULT_POLICIES = [
4
+ {
5
+ name: "minLength",
6
+ optionsKey: "minLength",
7
+ test: (password, options) => password.length >= options.minLength
8
+ },
9
+ {
10
+ name: "uppercase",
11
+ optionsKey: "uppercaseCheck",
12
+ test: (password, options) => options.uppercaseRegex.test(password)
13
+ },
14
+ {
15
+ name: "lowercase",
16
+ optionsKey: "lowercaseCheck",
17
+ test: (password, options) => options.lowercaseRegex.test(password)
18
+ },
19
+ {
20
+ name: "number",
21
+ optionsKey: "numberCheck",
22
+ test: (password, options) => options.numberRegex.test(password)
23
+ },
24
+ {
25
+ name: "specialChar",
26
+ optionsKey: "specialCharCheck",
27
+ test: (password, options) => options.specialCharRegex.test(password)
28
+ }
29
+ ];
30
+ var DEFAULT_OPTIONS = {
31
+ minLength: 8,
32
+ lowercaseCheck: true,
33
+ uppercaseCheck: true,
34
+ numberCheck: true,
35
+ specialCharCheck: true,
36
+ customRules: [],
37
+ lowercaseRegex: /[a-z]/,
38
+ uppercaseRegex: /[A-Z]/,
39
+ numberRegex: /\d/,
40
+ specialCharRegex: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/
41
+ };
42
+ var usePasswordPolicy = (options = {}) => {
43
+ const { password = "" } = options;
44
+ const mergedOptions = useMemo(
45
+ () => ({ ...DEFAULT_OPTIONS, ...options }),
46
+ [options]
47
+ );
48
+ const activePolicies = useMemo(() => {
49
+ const activeDefaultPolicies = DEFAULT_POLICIES.filter(
50
+ (policy) => policy.optionsKey && mergedOptions[policy.optionsKey]
51
+ );
52
+ return [...activeDefaultPolicies, ...mergedOptions.customRules];
53
+ }, [mergedOptions]);
54
+ const policyState = useMemo(() => {
55
+ const state = {};
56
+ for (const policy of activePolicies) {
57
+ state[policy.name] = policy.test(password, mergedOptions);
58
+ }
59
+ return state;
60
+ }, [password, activePolicies, mergedOptions]);
61
+ const { score, label, isValid } = useMemo(() => {
62
+ const passedPolicies = Object.values(policyState).filter(Boolean);
63
+ const score2 = passedPolicies.length;
64
+ const totalPolicies = activePolicies.length;
65
+ const strengthPercentage = totalPolicies > 0 ? score2 / totalPolicies : 0;
66
+ let label2 = "Very Weak";
67
+ if (strengthPercentage >= 1) {
68
+ label2 = "Very Strong";
69
+ } else if (strengthPercentage >= 0.75) {
70
+ label2 = "Strong";
71
+ } else if (strengthPercentage >= 0.5) {
72
+ label2 = "Medium";
73
+ } else if (strengthPercentage > 0) {
74
+ label2 = "Weak";
75
+ }
76
+ return {
77
+ score: score2,
78
+ label: label2,
79
+ isValid: score2 === totalPolicies && totalPolicies > 0
80
+ };
81
+ }, [policyState, activePolicies]);
82
+ return {
83
+ password,
84
+ policyState,
85
+ isValid,
86
+ strengthScore: score,
87
+ strengthLabel: label
88
+ };
89
+ };
90
+
91
+ // src/PasswordPolicyInput.tsx
92
+ import { useState, useEffect } from "react";
93
+ import styled, { css } from "styled-components";
94
+ import { jsx, jsxs } from "react/jsx-runtime";
95
+ var STRENGTH_COLOR_MAP = {
96
+ "Very Weak": "var(--rpp-weak)",
97
+ "Weak": "var(--rpp-weak)",
98
+ "Medium": "var(--rpp-medium)",
99
+ "Strong": "var(--rpp-success)",
100
+ "Very Strong": "var(--rpp-success)"
101
+ };
102
+ var Container = styled.div`
103
+ /* CSS variables define the component's internal theme */
104
+ --rpp-accent: #646cff;
105
+ --rpp-success: #27ae60;
106
+ --rpp-danger: #c0392b;
107
+ --rpp-weak: #f39c12;
108
+ --rpp-medium: #d35400;
109
+ --rpp-bg: #f9f9f9;
110
+ --rpp-border: #e0e0e0;
111
+ --rpp-text: #333;
112
+
113
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
114
+ display: flex;
115
+ flex-direction: column;
116
+ gap: 0.75rem;
117
+ `;
118
+ var InputWrapper = styled.div`
119
+ position: relative;
120
+ `;
121
+ var Input = styled.input`
122
+ box-sizing: border-box;
123
+ width: 100%;
124
+ padding: 0.75rem;
125
+ border: 1px solid var(--rpp-border);
126
+ border-radius: 6px;
127
+ font-size: 1rem;
128
+
129
+ &:focus {
130
+ border-color: var(--rpp-accent);
131
+ outline: none;
132
+ }
133
+ `;
134
+ var ToggleButton = styled.button`
135
+ position: absolute;
136
+ top: 50%;
137
+ right: 0.5rem;
138
+ transform: translateY(-50%);
139
+ background: none;
140
+ border: none;
141
+ cursor: pointer;
142
+ padding: 0.5rem;
143
+ color: #888;
144
+ display: flex;
145
+ align-items: center;
146
+ `;
147
+ var StrengthMeter = styled.div`
148
+ display: flex;
149
+ gap: 0.25rem;
150
+ height: 6px;
151
+ `;
152
+ var StrengthMeterSegment = styled.div`
153
+ flex: 1;
154
+ background-color: var(--rpp-border);
155
+ border-radius: 3px;
156
+ transition: background-color 0.3s;
157
+
158
+ ${({ isFilled, strengthLabel }) => (
159
+ // FIX: Explicitly type props
160
+ isFilled && css`
161
+ background-color: ${STRENGTH_COLOR_MAP[strengthLabel]};
162
+ `
163
+ )}
164
+ `;
165
+ var RequirementsList = styled.ul`
166
+ list-style: none;
167
+ padding: 0;
168
+ margin: 0;
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: 0.5rem;
172
+ font-size: 0.875rem;
173
+ `;
174
+ var RequirementItem = styled.li`
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 0.5rem;
178
+ transition: color 0.2s;
179
+ /* FIX: Explicitly type props */
180
+ color: ${({ passed }) => passed ? "var(--rpp-success)" : "var(--rpp-danger)"};
181
+ `;
182
+ var EyeIcon = () => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
183
+ /* @__PURE__ */ jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
184
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
185
+ ] });
186
+ var EyeOffIcon = () => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
187
+ /* @__PURE__ */ jsx("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" }),
188
+ /* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
189
+ ] });
190
+ var PasswordPolicyInput = ({
191
+ policyOptions,
192
+ onPasswordChange,
193
+ showStrengthMeter = true,
194
+ showRequirementsList = true,
195
+ showToggleButton = true,
196
+ className,
197
+ ...restInputProps
198
+ }) => {
199
+ const [password, setPassword] = useState("");
200
+ const [showPassword, setShowPassword] = useState(false);
201
+ const validation = usePasswordPolicy({ ...policyOptions, password });
202
+ const { policyState, strengthScore, strengthLabel } = validation;
203
+ useEffect(() => {
204
+ onPasswordChange?.(password, validation);
205
+ }, [password, validation, onPasswordChange]);
206
+ const formatPolicyName = (name) => name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
207
+ const handlePasswordChange = (e) => {
208
+ setPassword(e.target.value);
209
+ };
210
+ return /* @__PURE__ */ jsxs(Container, { className, children: [
211
+ /* @__PURE__ */ jsxs(InputWrapper, { children: [
212
+ /* @__PURE__ */ jsx(
213
+ Input,
214
+ {
215
+ type: showPassword ? "text" : "password",
216
+ value: password,
217
+ onChange: handlePasswordChange,
218
+ ...restInputProps
219
+ }
220
+ ),
221
+ showToggleButton && /* @__PURE__ */ jsx(ToggleButton, { type: "button", onClick: () => setShowPassword(!showPassword), children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {}) })
222
+ ] }),
223
+ showStrengthMeter && /* @__PURE__ */ jsx(StrengthMeter, { children: Array.from({ length: 5 }).map((_, index) => /* @__PURE__ */ jsx(
224
+ StrengthMeterSegment,
225
+ {
226
+ isFilled: strengthScore > index,
227
+ strengthLabel
228
+ },
229
+ index
230
+ )) }),
231
+ showRequirementsList && /* @__PURE__ */ jsx(RequirementsList, { children: Object.entries(policyState).map(([name, passed]) => /* @__PURE__ */ jsxs(RequirementItem, { passed, children: [
232
+ passed ? "\u2713" : "\u2717",
233
+ /* @__PURE__ */ jsx("span", { children: formatPolicyName(name) })
234
+ ] }, name)) })
235
+ ] });
236
+ };
237
+ export {
238
+ PasswordPolicyInput,
239
+ usePasswordPolicy
240
+ };
package/package.json CHANGED
@@ -1,29 +1,46 @@
1
1
  {
2
2
  "name": "use-password-policy",
3
- "version": "1.0.7",
4
- "description": "",
5
- "main": "use-password-policy.ts",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
3
+ "version": "2.0.0",
4
+ "private": false,
5
+ "description": "A simple, lightweight, and customizable React hook for real-time password strength validation.",
6
+ "license": "MIT",
7
+ "author": "Rahul Patwa <rahul.y2j@gmail.com>",
9
8
  "repository": {
10
9
  "type": "git",
11
10
  "url": "git+https://github.com/rahulpatwa1303/use-password-policy.git"
12
11
  },
13
12
  "keywords": [
14
- "password",
15
- "password-policy",
16
- "hooks",
17
- "react"
13
+ "react", "hook", "password", "password-policy", "validation", "form",
14
+ "typescript", "password-strength", "component", "react-component"
15
+ ],
16
+ "homepage": "https://rahulpatwa1303.github.io/use-password-policy/",
17
+ "bugs": { "url": "https://github.com/rahulpatwa1303/use-password-policy/issues" },
18
+
19
+ "workspaces": [
20
+ "demo"
18
21
  ],
19
- "author": "",
20
- "license": "ISC",
21
- "bugs": {
22
- "url": "https://github.com/rahulpatwa1303/use-password-policy/issues"
22
+
23
+ "main": "dist/index.js",
24
+ "module": "dist/index.mjs",
25
+ "types": "dist/index.d.ts",
26
+ "files": [ "dist" ],
27
+ "scripts": {
28
+ "clean": "rm -rf dist",
29
+ "build": "npm run clean && npx tsup src/index.ts --format cjs,esm --dts",
30
+ "dev": "npm run build -- --watch",
31
+ "prepublishOnly": "npm run build",
32
+ "deploy:demo": "npm run build && cd demo && npm install && npm run deploy"
23
33
  },
24
- "homepage": "https://github.com/rahulpatwa1303/use-password-policy#readme",
25
34
  "peerDependencies": {
26
- "react": "^18.3.1",
27
- "react-dom": "^18.3.1"
35
+ "react": ">=16.8.0",
36
+ "styled-components": ">=5"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^18.2.66",
40
+ "@types/styled-components": "^5.1.34",
41
+ "react": "^18.2.0",
42
+ "styled-components": "^6.1.11",
43
+ "tsup": "^8.0.2",
44
+ "typescript": "^5.4.5"
28
45
  }
29
- }
46
+ }
package/Type.ts DELETED
@@ -1,14 +0,0 @@
1
- export interface CustomPolicy {
2
- name: string; // Name of the policy (e.g., "SymbolCheck")
3
- regex?: RegExp; // Regular expression for the check (optional)
4
- checkFunction?: (password: string) => boolean; // Custom check function (optional)
5
- message?: string; // Error message if the check fails (optional)
6
- }
7
-
8
- export interface StateObjects {
9
- [key: string]: boolean;
10
- }
11
-
12
- export interface DefaultConfig {
13
- [key: string]: boolean | RegExp | string | number;
14
- }
@@ -1,112 +0,0 @@
1
- import { useState, useEffect, useMemo } from "react";
2
- import { CustomPolicy, StateObjects, DefaultConfig } from "./Type";
3
-
4
- export const usePasswordPolicy = ({
5
- password,
6
- config,
7
- customPolicies = [],
8
- useDefaultConfig = true,
9
- }: {
10
- password: string;
11
- config?: {};
12
- customPolicies?: CustomPolicy[];
13
- useDefaultConfig?: boolean;
14
- }) => {
15
- const defaultConfig: DefaultConfig = {
16
- caseCheck: true,
17
- lengthCheck: true,
18
- digitCheck: true,
19
- specialCharCheck: true,
20
- minLength: 8,
21
- uppercaseCharRegex: /[A-Z]/,
22
- digitRegex: /\d/,
23
- specialCharRegex: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/,
24
- };
25
-
26
- const merged = useMemo(() => mergedConfig(defaultConfig, config), []);
27
-
28
- function mergedConfig(defaultConfig: {}, config: any) {
29
- if (useDefaultConfig) {
30
- return { ...defaultConfig, ...config };
31
- } else {
32
- return { ...config };
33
- }
34
- }
35
-
36
- const [policy, setPolicy] = useState<StateObjects>(() => {
37
- if (useDefaultConfig) {
38
- const defaultStates: StateObjects = {
39
- caseCheck: false,
40
- lengthCheck: false,
41
- digitCheck: false,
42
- specialCharCheck: false,
43
- };
44
- const safeConfig: StateObjects = config || {};
45
-
46
- Object.entries(defaultStates).map(([key]) => {
47
- if (safeConfig.hasOwnProperty(key) && safeConfig[key] === false) {
48
- delete defaultStates[key];
49
- delete defaultConfig[key];
50
- }
51
- });
52
- return {
53
- ...defaultStates,
54
- ...customPolicies.reduce(
55
- (acc, policy: { name: string }) => ({
56
- ...acc,
57
- [policy?.name]: false,
58
- }),
59
- {}
60
- ),
61
- };
62
- } else
63
- return {
64
- ...customPolicies.reduce(
65
- (acc, policy: { name: string }) => ({
66
- ...acc,
67
- [policy?.name]: false,
68
- }),
69
- {}
70
- ),
71
- };
72
- });
73
-
74
- const evaluateChecks = () => {
75
- const newPolicy: StateObjects = { ...policy };
76
- const policyKeys = Object.keys(policy);
77
-
78
- if (useDefaultConfig) {
79
- policyKeys.forEach((key) => {
80
- if (merged.hasOwnProperty(key) && policy.hasOwnProperty(key)) {
81
- const checkFunction = {
82
- caseCheck: () => RegExp(merged.uppercaseCharRegex).test(password),
83
- lengthCheck: () => password.length >= merged.minLength,
84
- digitCheck: () => RegExp(merged.digitRegex).test(password),
85
- specialCharCheck: () =>
86
- RegExp(merged.specialCharRegex).test(password),
87
- }[key];
88
-
89
- if (checkFunction) {
90
- newPolicy[key] = merged[key] && checkFunction();
91
- }
92
- }
93
- });
94
- }
95
-
96
- customPolicies.forEach((customPolicy: CustomPolicy) => {
97
- const name: string = customPolicy?.name;
98
- if (customPolicy?.regex) {
99
- newPolicy[name] = customPolicy.regex.test(password);
100
- } else if (customPolicy?.checkFunction) {
101
- newPolicy[name] = customPolicy.checkFunction(password);
102
- }
103
- });
104
-
105
- setPolicy(newPolicy);
106
- };
107
-
108
- useEffect(() => {
109
- evaluateChecks();
110
- }, [password]);
111
- return policy;
112
- };