@douglasneuroinformatics/libui 2.7.0 → 2.8.1

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.
@@ -1,6 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { StringFormField } from '@douglasneuroinformatics/libui-form-types';
3
3
  import type { BaseFieldComponentProps } from '../types.js';
4
- export type StringFieldPasswordProps = BaseFieldComponentProps<string> & StringFormField;
5
- export declare const StringFieldPassword: ({ description, error, label, name, readOnly, setValue, value }: StringFieldPasswordProps) => React.JSX.Element;
4
+ export type PasswordStrengthValue = 0 | 1 | 2 | 3 | 4;
5
+ export type StringFieldPasswordProps = BaseFieldComponentProps<string> & Extract<StringFormField, {
6
+ variant: 'password';
7
+ }>;
8
+ export declare const StringFieldPassword: ({ calculateStrength, description, error, label, name, readOnly, setValue, value }: StringFieldPasswordProps) => React.JSX.Element;
6
9
  //# sourceMappingURL=StringFieldPassword.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StringFieldPassword.d.ts","sourceRoot":"","sources":["../../../../src/components/Form/StringField/StringFieldPassword.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAQjF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC;AAEzF,eAAO,MAAM,mBAAmB,mEAQ7B,wBAAwB,sBA+B1B,CAAC"}
1
+ {"version":3,"file":"StringFieldPassword.d.ts","sourceRoot":"","sources":["../../../../src/components/Form/StringField/StringFieldPassword.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AASjF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEtD,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,CAAC,MAAM,CAAC,GACpE,OAAO,CAAC,eAAe,EAAE;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAEpD,eAAO,MAAM,mBAAmB,sFAS7B,wBAAwB,sBAuD1B,CAAC"}
@@ -1,11 +1,18 @@
1
- import React, { useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
+ import { motion } from 'framer-motion';
2
3
  import { EyeIcon, EyeOffIcon } from 'lucide-react';
3
4
  import { cn } from '../../../utils.js';
4
5
  import { Input } from '../../Input/Input.js';
5
6
  import { Label } from '../../Label/Label.js';
6
7
  import { FieldGroup } from '../FieldGroup/FieldGroup.js';
7
- export const StringFieldPassword = ({ description, error, label, name, readOnly, setValue, value }) => {
8
+ export const StringFieldPassword = ({ calculateStrength, description, error, label, name, readOnly, setValue, value }) => {
9
+ const [strength, setStrength] = useState(calculateStrength ? 0 : null);
8
10
  const [show, setShow] = useState(false);
11
+ useEffect(() => {
12
+ if (calculateStrength) {
13
+ setStrength(value ? calculateStrength(value) : 0);
14
+ }
15
+ }, [value]);
9
16
  return (React.createElement(FieldGroup, null,
10
17
  React.createElement(FieldGroup.Row, null,
11
18
  React.createElement(Label, { htmlFor: name }, label),
@@ -15,5 +22,7 @@ export const StringFieldPassword = ({ description, error, label, name, readOnly,
15
22
  React.createElement("button", { className: "absolute right-0 flex h-full w-8 items-center justify-center text-muted-foreground", disabled: readOnly, tabIndex: -1, type: "button", onClick: () => setShow(!show) },
16
23
  React.createElement(EyeIcon, { className: cn('absolute transition-all', show ? '-rotate-90 scale-0' : 'rotate-0 scale-100') }),
17
24
  React.createElement(EyeOffIcon, { className: cn('absolute transition-all', !show ? 'rotate-90 scale-0' : 'rotate-0 scale-100') }))),
25
+ strength !== null && (React.createElement(motion.div, { animate: { width: `${Math.max(strength, value?.length ? 1 : 0) * 25}%` }, className: "h-1 w-full overflow-hidden rounded", initial: { width: '0%' }, transition: { duration: 0.5 } },
26
+ React.createElement("div", { className: cn('h-full w-full bg-destructive transition-colors duration-500', strength === 2 && 'bg-yellow-500 dark:bg-yellow-700', strength === 3 && 'bg-sky-500 dark:bg-sky-700', strength === 4 && 'bg-green-500 dark:bg-green-700') }))),
18
27
  React.createElement(FieldGroup.Error, { error: error })));
19
28
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@douglasneuroinformatics/libui",
3
3
  "type": "module",
4
- "version": "2.7.0",
4
+ "version": "2.8.1",
5
5
  "packageManager": "pnpm@9.3.0",
6
6
  "description": "Generic UI components for DNP projects, built using React and TailwindCSS",
7
7
  "author": {
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@douglasneuroinformatics/libjs": "^0.3.1",
70
- "@douglasneuroinformatics/libui-form-types": "^0.8.0",
70
+ "@douglasneuroinformatics/libui-form-types": "^0.9.0",
71
71
  "@headlessui/tailwindcss": "^0.2.1",
72
72
  "@heroicons/react": "^2.1.3",
73
73
  "@radix-ui/react-accordion": "^1.1.2",
@@ -4,6 +4,7 @@ import React from 'react';
4
4
 
5
5
  import type { FormFields } from '@douglasneuroinformatics/libui-form-types';
6
6
  import type { Meta, StoryObj } from '@storybook/react';
7
+ import type { IntRange } from 'type-fest';
7
8
  import { z } from 'zod';
8
9
 
9
10
  import { Heading } from '../Heading/Heading.js';
@@ -165,7 +166,10 @@ const stringFields: FormFields<
165
166
  stringPassword: {
166
167
  kind: 'string',
167
168
  label: 'Password',
168
- variant: 'password'
169
+ variant: 'password',
170
+ calculateStrength: (password: string) => {
171
+ return Math.min(password.length, 4) as IntRange<0, 5>;
172
+ }
169
173
  },
170
174
  stringInput: {
171
175
  description: 'This is a string field',
@@ -4,6 +4,8 @@ import type { Meta, StoryObj } from '@storybook/react';
4
4
 
5
5
  import { StringField } from './StringField.js';
6
6
 
7
+ import type { PasswordStrengthValue } from './StringFieldPassword.js';
8
+
7
9
  type Story = StoryObj<typeof StringField>;
8
10
 
9
11
  export default { component: StringField } as Meta<typeof StringField>;
@@ -68,6 +70,29 @@ export const Password: Story = {
68
70
  ]
69
71
  };
70
72
 
73
+ export const PasswordWithStrength: Story = {
74
+ decorators: [
75
+ (Story) => {
76
+ const [value, setValue] = useState<string | undefined>();
77
+ return (
78
+ <Story
79
+ args={{
80
+ calculateStrength: (password: string) => {
81
+ return Math.min(password.length, 4) as PasswordStrengthValue;
82
+ },
83
+ description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
84
+ label: 'Password',
85
+ name: 'text',
86
+ setValue,
87
+ value,
88
+ variant: 'password'
89
+ }}
90
+ />
91
+ );
92
+ }
93
+ ]
94
+ };
95
+
71
96
  export const Select: Story = {
72
97
  decorators: [
73
98
  (Story) => {
@@ -1,6 +1,7 @@
1
- import React, { useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
 
3
3
  import type { StringFormField } from '@douglasneuroinformatics/libui-form-types';
4
+ import { motion } from 'framer-motion';
4
5
  import { EyeIcon, EyeOffIcon } from 'lucide-react';
5
6
 
6
7
  import { cn } from '../../../utils.js';
@@ -10,9 +11,13 @@ import { FieldGroup } from '../FieldGroup/FieldGroup.js';
10
11
 
11
12
  import type { BaseFieldComponentProps } from '../types.js';
12
13
 
13
- export type StringFieldPasswordProps = BaseFieldComponentProps<string> & StringFormField;
14
+ export type PasswordStrengthValue = 0 | 1 | 2 | 3 | 4;
15
+
16
+ export type StringFieldPasswordProps = BaseFieldComponentProps<string> &
17
+ Extract<StringFormField, { variant: 'password' }>;
14
18
 
15
19
  export const StringFieldPassword = ({
20
+ calculateStrength,
16
21
  description,
17
22
  error,
18
23
  label,
@@ -21,7 +26,14 @@ export const StringFieldPassword = ({
21
26
  setValue,
22
27
  value
23
28
  }: StringFieldPasswordProps) => {
29
+ const [strength, setStrength] = useState<PasswordStrengthValue | null>(calculateStrength ? 0 : null);
24
30
  const [show, setShow] = useState(false);
31
+ useEffect(() => {
32
+ if (calculateStrength) {
33
+ setStrength(value ? calculateStrength(value) : 0);
34
+ }
35
+ }, [value]);
36
+
25
37
  return (
26
38
  <FieldGroup>
27
39
  <FieldGroup.Row>
@@ -48,6 +60,23 @@ export const StringFieldPassword = ({
48
60
  <EyeOffIcon className={cn('absolute transition-all', !show ? 'rotate-90 scale-0' : 'rotate-0 scale-100')} />
49
61
  </button>
50
62
  </FieldGroup.Row>
63
+ {strength !== null && (
64
+ <motion.div
65
+ animate={{ width: `${Math.max(strength, value?.length ? 1 : 0) * 25}%` }}
66
+ className="h-1 w-full overflow-hidden rounded"
67
+ initial={{ width: '0%' }}
68
+ transition={{ duration: 0.5 }}
69
+ >
70
+ <div
71
+ className={cn(
72
+ 'h-full w-full bg-destructive transition-colors duration-500',
73
+ strength === 2 && 'bg-yellow-500 dark:bg-yellow-700',
74
+ strength === 3 && 'bg-sky-500 dark:bg-sky-700',
75
+ strength === 4 && 'bg-green-500 dark:bg-green-700'
76
+ )}
77
+ />
78
+ </motion.div>
79
+ )}
51
80
  <FieldGroup.Error error={error} />
52
81
  </FieldGroup>
53
82
  );