@mohammadsalman/storybook-custom-ui 1.0.0 → 1.0.2
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 +60 -176
- package/dist/assets/index-BWMuc5b_.js +9 -0
- package/dist/assets/index-COcDBgFa.css +1 -0
- package/dist/assets/react-CHdo91hT.svg +1 -0
- package/dist/index.html +14 -0
- package/dist/vite.svg +1 -0
- package/package.json +50 -39
- package/src/components/Button/Button.stories.tsx +0 -109
- package/src/components/Button/Button.tsx +0 -144
- package/src/components/Button/index.ts +0 -1
- package/src/components/index.ts +0 -1
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useFormInput.stories.tsx +0 -126
- package/src/hooks/useFormInput.ts +0 -114
- package/src/index.ts +0 -0
- package/src/theme/ThemeProvider.tsx +0 -34
- package/src/theme/defaultTheme.ts +0 -182
- package/src/theme/index.ts +0 -3
- package/src/theme/types.ts +0 -106
- package/src/utils/styles.ts +0 -15
package/package.json
CHANGED
|
@@ -1,53 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohammadsalman/storybook-custom-ui",
|
|
3
|
-
"version": "1.0.0",
|
|
4
3
|
"description": "A minimal React TypeScript component library with Button component and useFormInput hook",
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
],
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"components",
|
|
15
|
-
"hooks",
|
|
16
|
-
"storybook",
|
|
17
|
-
"button",
|
|
18
|
-
"form-input"
|
|
19
|
-
],
|
|
20
|
-
"author": "Your Name <your.email@example.com>",
|
|
4
|
+
|
|
5
|
+
"version": "1.0.2",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.esm.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": ["dist"],
|
|
10
|
+
|
|
11
|
+
"keywords": ["react", "typescript", "components", "storybook"],
|
|
12
|
+
"author": "Mohammad Salman <mohammadsalman71993@gmail.com>",
|
|
21
13
|
"license": "MIT",
|
|
22
14
|
"repository": {
|
|
23
15
|
"type": "git",
|
|
24
16
|
"url": "https://github.com/MohSalman/creative-ui-react-ui-library.git"
|
|
25
17
|
},
|
|
26
|
-
"
|
|
27
|
-
"react": "^18.2.0",
|
|
28
|
-
"react-dom": "^18.2.0"
|
|
29
|
-
},
|
|
18
|
+
"type": "module",
|
|
30
19
|
"scripts": {
|
|
20
|
+
"dev": "vite",
|
|
21
|
+
"build": "tsc -b && vite build",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"preview": "vite preview",
|
|
31
24
|
"storybook": "storybook dev -p 6006",
|
|
32
|
-
"build-storybook": "storybook build"
|
|
33
|
-
|
|
25
|
+
"build-storybook": "storybook build"
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^19.2.0",
|
|
30
|
+
"react-dom": "^19.2.0"
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"react": "^19.2.0",
|
|
35
|
+
"react-dom": "^19.2.0"
|
|
34
36
|
},
|
|
35
|
-
|
|
37
|
+
|
|
36
38
|
"devDependencies": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"storybook": "^
|
|
51
|
-
"
|
|
39
|
+
"@eslint/js": "^9.39.1",
|
|
40
|
+
"@types/node": "^24.10.1",
|
|
41
|
+
"@types/react": "^19.2.5",
|
|
42
|
+
"@types/react-dom": "^19.2.3",
|
|
43
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
44
|
+
"eslint": "^9.39.1",
|
|
45
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
46
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
47
|
+
"globals": "^16.5.0",
|
|
48
|
+
"typescript": "~5.9.3",
|
|
49
|
+
"typescript-eslint": "^8.46.4",
|
|
50
|
+
"vite": "^7.2.4",
|
|
51
|
+
"storybook": "^10.1.11",
|
|
52
|
+
"@storybook/react-vite": "^10.1.11",
|
|
53
|
+
"@chromatic-com/storybook": "^5.0.0",
|
|
54
|
+
"@storybook/addon-vitest": "^10.1.11",
|
|
55
|
+
"@storybook/addon-a11y": "^10.1.11",
|
|
56
|
+
"@storybook/addon-docs": "^10.1.11",
|
|
57
|
+
"@storybook/addon-onboarding": "^10.1.11",
|
|
58
|
+
"eslint-plugin-storybook": "^10.1.11",
|
|
59
|
+
"vitest": "^4.0.17",
|
|
60
|
+
"playwright": "^1.57.0",
|
|
61
|
+
"@vitest/browser-playwright": "^4.0.17",
|
|
62
|
+
"@vitest/coverage-v8": "^4.0.17"
|
|
52
63
|
}
|
|
53
64
|
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { Button } from './Button';
|
|
3
|
-
|
|
4
|
-
const meta: Meta<typeof Button> = {
|
|
5
|
-
title: 'Components/Button',
|
|
6
|
-
component: Button,
|
|
7
|
-
tags: ['autodocs'],
|
|
8
|
-
argTypes: {
|
|
9
|
-
variant: {
|
|
10
|
-
control: { type: 'select' },
|
|
11
|
-
options: ['contained', 'outlined', 'text'],
|
|
12
|
-
},
|
|
13
|
-
color: {
|
|
14
|
-
control: { type: 'select' },
|
|
15
|
-
options: ['primary', 'secondary', 'success', 'error', 'warning', 'info'],
|
|
16
|
-
},
|
|
17
|
-
size: {
|
|
18
|
-
control: { type: 'select' },
|
|
19
|
-
options: ['small', 'medium', 'large'],
|
|
20
|
-
},
|
|
21
|
-
fullWidth: {
|
|
22
|
-
control: { type: 'boolean' },
|
|
23
|
-
},
|
|
24
|
-
disabled: {
|
|
25
|
-
control: { type: 'boolean' },
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export default meta;
|
|
31
|
-
type Story = StoryObj<typeof Button>;
|
|
32
|
-
|
|
33
|
-
export const Contained: Story = {
|
|
34
|
-
args: {
|
|
35
|
-
children: 'Button',
|
|
36
|
-
variant: 'contained',
|
|
37
|
-
color: 'primary',
|
|
38
|
-
size: 'medium',
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const Outlined: Story = {
|
|
43
|
-
args: {
|
|
44
|
-
children: 'Button',
|
|
45
|
-
variant: 'outlined',
|
|
46
|
-
color: 'primary',
|
|
47
|
-
size: 'medium',
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export const Text: Story = {
|
|
52
|
-
args: {
|
|
53
|
-
children: 'Button',
|
|
54
|
-
variant: 'text',
|
|
55
|
-
color: 'primary',
|
|
56
|
-
size: 'medium',
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const Colors: Story = {
|
|
61
|
-
render: () => (
|
|
62
|
-
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
63
|
-
<Button color="primary">Primary</Button>
|
|
64
|
-
<Button color="secondary">Secondary</Button>
|
|
65
|
-
<Button color="success">Success</Button>
|
|
66
|
-
<Button color="error">Error</Button>
|
|
67
|
-
<Button color="warning">Warning</Button>
|
|
68
|
-
<Button color="info">Info</Button>
|
|
69
|
-
</div>
|
|
70
|
-
),
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const Sizes: Story = {
|
|
74
|
-
render: () => (
|
|
75
|
-
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
76
|
-
<Button size="small">Small</Button>
|
|
77
|
-
<Button size="medium">Medium</Button>
|
|
78
|
-
<Button size="large">Large</Button>
|
|
79
|
-
</div>
|
|
80
|
-
),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export const Variants: Story = {
|
|
84
|
-
render: () => (
|
|
85
|
-
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
86
|
-
<Button variant="contained">Contained</Button>
|
|
87
|
-
<Button variant="outlined">Outlined</Button>
|
|
88
|
-
<Button variant="text">Text</Button>
|
|
89
|
-
</div>
|
|
90
|
-
),
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export const FullWidth: Story = {
|
|
94
|
-
args: {
|
|
95
|
-
children: 'Full Width Button',
|
|
96
|
-
fullWidth: true,
|
|
97
|
-
variant: 'contained',
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export const Disabled: Story = {
|
|
102
|
-
render: () => (
|
|
103
|
-
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
104
|
-
<Button variant="contained" disabled>Disabled</Button>
|
|
105
|
-
<Button variant="outlined" disabled>Disabled</Button>
|
|
106
|
-
<Button variant="text" disabled>Disabled</Button>
|
|
107
|
-
</div>
|
|
108
|
-
),
|
|
109
|
-
};
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useTheme } from '../../theme';
|
|
3
|
-
import { spacing } from '../../utils/styles';
|
|
4
|
-
|
|
5
|
-
export type ButtonVariant = 'contained' | 'outlined' | 'text';
|
|
6
|
-
export type ButtonColor = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
|
|
7
|
-
export type ButtonSize = 'small' | 'medium' | 'large';
|
|
8
|
-
|
|
9
|
-
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
10
|
-
variant?: ButtonVariant;
|
|
11
|
-
color?: ButtonColor;
|
|
12
|
-
size?: ButtonSize;
|
|
13
|
-
fullWidth?: boolean;
|
|
14
|
-
disabled?: boolean;
|
|
15
|
-
children: React.ReactNode;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const Button: React.FC<ButtonProps> = ({
|
|
19
|
-
variant = 'contained',
|
|
20
|
-
color = 'primary',
|
|
21
|
-
size = 'medium',
|
|
22
|
-
fullWidth = false,
|
|
23
|
-
disabled = false,
|
|
24
|
-
children,
|
|
25
|
-
style,
|
|
26
|
-
...props
|
|
27
|
-
}) => {
|
|
28
|
-
const theme = useTheme();
|
|
29
|
-
|
|
30
|
-
const getVariantStyles = (): React.CSSProperties => {
|
|
31
|
-
const colorValue = theme.palette[color][500];
|
|
32
|
-
const textColor = variant === 'contained' ? '#ffffff' : colorValue;
|
|
33
|
-
|
|
34
|
-
switch (variant) {
|
|
35
|
-
case 'contained':
|
|
36
|
-
return {
|
|
37
|
-
backgroundColor: colorValue,
|
|
38
|
-
color: textColor,
|
|
39
|
-
border: 'none',
|
|
40
|
-
boxShadow: theme.shadows[2],
|
|
41
|
-
};
|
|
42
|
-
case 'outlined':
|
|
43
|
-
return {
|
|
44
|
-
backgroundColor: 'transparent',
|
|
45
|
-
color: colorValue,
|
|
46
|
-
border: `1px solid ${colorValue}`,
|
|
47
|
-
boxShadow: 'none',
|
|
48
|
-
};
|
|
49
|
-
case 'text':
|
|
50
|
-
return {
|
|
51
|
-
backgroundColor: 'transparent',
|
|
52
|
-
color: colorValue,
|
|
53
|
-
border: 'none',
|
|
54
|
-
boxShadow: 'none',
|
|
55
|
-
};
|
|
56
|
-
default:
|
|
57
|
-
return {};
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getSizeStyles = (): React.CSSProperties => {
|
|
62
|
-
switch (size) {
|
|
63
|
-
case 'small':
|
|
64
|
-
return {
|
|
65
|
-
padding: spacing(theme, 0.5, 1.5),
|
|
66
|
-
fontSize: theme.typography.button.fontSize,
|
|
67
|
-
minWidth: '64px',
|
|
68
|
-
};
|
|
69
|
-
case 'medium':
|
|
70
|
-
return {
|
|
71
|
-
padding: spacing(theme, 1, 2),
|
|
72
|
-
fontSize: theme.typography.button.fontSize,
|
|
73
|
-
minWidth: '64px',
|
|
74
|
-
};
|
|
75
|
-
case 'large':
|
|
76
|
-
return {
|
|
77
|
-
padding: spacing(theme, 1.5, 3),
|
|
78
|
-
fontSize: '1rem',
|
|
79
|
-
minWidth: '64px',
|
|
80
|
-
};
|
|
81
|
-
default:
|
|
82
|
-
return {};
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const baseStyles: React.CSSProperties = {
|
|
87
|
-
fontFamily: theme.typography.button.fontFamily,
|
|
88
|
-
fontWeight: theme.typography.button.fontWeight,
|
|
89
|
-
lineHeight: theme.typography.button.lineHeight,
|
|
90
|
-
letterSpacing: theme.typography.button.letterSpacing,
|
|
91
|
-
textTransform: 'uppercase',
|
|
92
|
-
borderRadius: `${theme.shape.borderRadius}px`,
|
|
93
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
94
|
-
outline: 'none',
|
|
95
|
-
transition: 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
|
|
96
|
-
width: fullWidth ? '100%' : 'auto',
|
|
97
|
-
opacity: disabled ? 0.6 : 1,
|
|
98
|
-
...getVariantStyles(),
|
|
99
|
-
...getSizeStyles(),
|
|
100
|
-
...style,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
104
|
-
if (!disabled && props.onMouseEnter) {
|
|
105
|
-
props.onMouseEnter(e);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
110
|
-
if (!disabled && props.onMouseLeave) {
|
|
111
|
-
props.onMouseLeave(e);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<button
|
|
117
|
-
{...props}
|
|
118
|
-
disabled={disabled}
|
|
119
|
-
style={baseStyles}
|
|
120
|
-
onMouseEnter={handleMouseEnter}
|
|
121
|
-
onMouseLeave={handleMouseLeave}
|
|
122
|
-
onMouseOver={(e) => {
|
|
123
|
-
if (!disabled) {
|
|
124
|
-
e.currentTarget.style.backgroundColor =
|
|
125
|
-
variant === 'contained'
|
|
126
|
-
? theme.palette[color][700]
|
|
127
|
-
: variant === 'outlined'
|
|
128
|
-
? theme.palette.action.hover
|
|
129
|
-
: theme.palette.action.hover;
|
|
130
|
-
}
|
|
131
|
-
}}
|
|
132
|
-
onMouseOut={(e) => {
|
|
133
|
-
if (!disabled) {
|
|
134
|
-
e.currentTarget.style.backgroundColor =
|
|
135
|
-
variant === 'contained'
|
|
136
|
-
? theme.palette[color][500]
|
|
137
|
-
: 'transparent';
|
|
138
|
-
}
|
|
139
|
-
}}
|
|
140
|
-
>
|
|
141
|
-
{children}
|
|
142
|
-
</button>
|
|
143
|
-
);
|
|
144
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './Button';
|
package/src/components/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './Button';
|
package/src/hooks/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './useFormInput';
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { useFormInput } from './useFormInput';
|
|
3
|
-
import { Button } from '../components/Button';
|
|
4
|
-
|
|
5
|
-
// Demo component to showcase the hook
|
|
6
|
-
const FormInputDemo = ({
|
|
7
|
-
initialValue,
|
|
8
|
-
validate,
|
|
9
|
-
label
|
|
10
|
-
}: {
|
|
11
|
-
initialValue?: string;
|
|
12
|
-
validate?: (value: string) => string | null;
|
|
13
|
-
label?: string;
|
|
14
|
-
}) => {
|
|
15
|
-
const input = useFormInput({
|
|
16
|
-
initialValue,
|
|
17
|
-
validate,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div style={{ maxWidth: '400px', padding: '20px' }}>
|
|
22
|
-
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>
|
|
23
|
-
{label || 'Input'}
|
|
24
|
-
</label>
|
|
25
|
-
<input
|
|
26
|
-
type="text"
|
|
27
|
-
value={input.value}
|
|
28
|
-
onChange={input.onChange}
|
|
29
|
-
onBlur={input.onBlur}
|
|
30
|
-
style={{
|
|
31
|
-
width: '100%',
|
|
32
|
-
padding: '8px 12px',
|
|
33
|
-
border: `1px solid ${input.error ? '#d32f2f' : '#ccc'}`,
|
|
34
|
-
borderRadius: '4px',
|
|
35
|
-
fontSize: '14px',
|
|
36
|
-
outline: 'none',
|
|
37
|
-
}}
|
|
38
|
-
placeholder="Type something..."
|
|
39
|
-
/>
|
|
40
|
-
{input.error && (
|
|
41
|
-
<div style={{ color: '#d32f2f', fontSize: '12px', marginTop: '4px' }}>
|
|
42
|
-
{input.error}
|
|
43
|
-
</div>
|
|
44
|
-
)}
|
|
45
|
-
<div style={{ marginTop: '12px', display: 'flex', gap: '8px' }}>
|
|
46
|
-
<Button size="small" onClick={input.reset}>
|
|
47
|
-
Reset
|
|
48
|
-
</Button>
|
|
49
|
-
<div style={{
|
|
50
|
-
padding: '8px',
|
|
51
|
-
fontSize: '12px',
|
|
52
|
-
color: input.isValid ? '#2e7d32' : '#666'
|
|
53
|
-
}}>
|
|
54
|
-
Valid: {input.isValid ? 'Yes' : 'No'}
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const meta: Meta<typeof FormInputDemo> = {
|
|
62
|
-
title: 'Hooks/useFormInput',
|
|
63
|
-
component: FormInputDemo,
|
|
64
|
-
tags: ['autodocs'],
|
|
65
|
-
parameters: {
|
|
66
|
-
docs: {
|
|
67
|
-
description: {
|
|
68
|
-
component: 'A custom hook for managing form input state, validation, and error handling.',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export default meta;
|
|
75
|
-
type Story = StoryObj<typeof FormInputDemo>;
|
|
76
|
-
|
|
77
|
-
export const Basic: Story = {
|
|
78
|
-
args: {
|
|
79
|
-
label: 'Basic Input',
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export const WithInitialValue: Story = {
|
|
84
|
-
args: {
|
|
85
|
-
label: 'Input with Initial Value',
|
|
86
|
-
initialValue: 'Hello World',
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export const WithValidation: Story = {
|
|
91
|
-
args: {
|
|
92
|
-
label: 'Email Input (with validation)',
|
|
93
|
-
initialValue: '',
|
|
94
|
-
validate: (value) => {
|
|
95
|
-
if (!value) return 'Email is required';
|
|
96
|
-
if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format';
|
|
97
|
-
return null;
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export const PasswordValidation: Story = {
|
|
103
|
-
args: {
|
|
104
|
-
label: 'Password (min 8 characters)',
|
|
105
|
-
initialValue: '',
|
|
106
|
-
validate: (value) => {
|
|
107
|
-
if (!value) return 'Password is required';
|
|
108
|
-
if (value.length < 8) return 'Password must be at least 8 characters';
|
|
109
|
-
return null;
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export const NumberValidation: Story = {
|
|
115
|
-
args: {
|
|
116
|
-
label: 'Age (must be 18+)',
|
|
117
|
-
initialValue: '',
|
|
118
|
-
validate: (value) => {
|
|
119
|
-
if (!value) return 'Age is required';
|
|
120
|
-
const num = parseInt(value, 10);
|
|
121
|
-
if (isNaN(num)) return 'Must be a number';
|
|
122
|
-
if (num < 18) return 'Must be 18 or older';
|
|
123
|
-
return null;
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
};
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, ChangeEvent } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface UseFormInputOptions {
|
|
4
|
-
initialValue?: string;
|
|
5
|
-
validate?: (value: string) => string | null;
|
|
6
|
-
onChange?: (value: string) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface UseFormInputReturn {
|
|
10
|
-
value: string;
|
|
11
|
-
error: string | null;
|
|
12
|
-
onChange: (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
13
|
-
onBlur: () => void;
|
|
14
|
-
setValue: (value: string) => void;
|
|
15
|
-
setError: (error: string | null) => void;
|
|
16
|
-
reset: () => void;
|
|
17
|
-
isValid: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Custom hook for managing form input state, validation, and error handling
|
|
22
|
-
*
|
|
23
|
-
* @param options - Configuration options for the hook
|
|
24
|
-
* @param options.initialValue - Initial value for the input (default: '')
|
|
25
|
-
* @param options.validate - Validation function that returns error message or null
|
|
26
|
-
* @param options.onChange - Optional callback when value changes
|
|
27
|
-
* @returns Object containing input state and handlers
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```tsx
|
|
31
|
-
* const emailInput = useFormInput({
|
|
32
|
-
* initialValue: '',
|
|
33
|
-
* validate: (value) => {
|
|
34
|
-
* if (!value) return 'Email is required';
|
|
35
|
-
* if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format';
|
|
36
|
-
* return null;
|
|
37
|
-
* }
|
|
38
|
-
* });
|
|
39
|
-
*
|
|
40
|
-
* <input
|
|
41
|
-
* value={emailInput.value}
|
|
42
|
-
* onChange={emailInput.onChange}
|
|
43
|
-
* onBlur={emailInput.onBlur}
|
|
44
|
-
* />
|
|
45
|
-
* {emailInput.error && <span>{emailInput.error}</span>}
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export const useFormInput = ({
|
|
49
|
-
initialValue = '',
|
|
50
|
-
validate,
|
|
51
|
-
onChange: onChangeCallback,
|
|
52
|
-
}: UseFormInputOptions = {}): UseFormInputReturn => {
|
|
53
|
-
const [value, setValueState] = useState<string>(initialValue);
|
|
54
|
-
const [error, setErrorState] = useState<string | null>(null);
|
|
55
|
-
const [touched, setTouched] = useState<boolean>(false);
|
|
56
|
-
|
|
57
|
-
const handleChange = useCallback(
|
|
58
|
-
(e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
59
|
-
const newValue = e.target.value;
|
|
60
|
-
setValueState(newValue);
|
|
61
|
-
|
|
62
|
-
// Clear error when user starts typing
|
|
63
|
-
if (error && touched) {
|
|
64
|
-
const validationError = validate ? validate(newValue) : null;
|
|
65
|
-
setErrorState(validationError);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Call optional onChange callback
|
|
69
|
-
if (onChangeCallback) {
|
|
70
|
-
onChangeCallback(newValue);
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
[error, touched, validate, onChangeCallback]
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
const handleBlur = useCallback(() => {
|
|
77
|
-
setTouched(true);
|
|
78
|
-
if (validate) {
|
|
79
|
-
const validationError = validate(value);
|
|
80
|
-
setErrorState(validationError);
|
|
81
|
-
}
|
|
82
|
-
}, [value, validate]);
|
|
83
|
-
|
|
84
|
-
const setValue = useCallback((newValue: string) => {
|
|
85
|
-
setValueState(newValue);
|
|
86
|
-
if (touched && validate) {
|
|
87
|
-
const validationError = validate(newValue);
|
|
88
|
-
setErrorState(validationError);
|
|
89
|
-
}
|
|
90
|
-
}, [touched, validate]);
|
|
91
|
-
|
|
92
|
-
const setError = useCallback((errorMessage: string | null) => {
|
|
93
|
-
setErrorState(errorMessage);
|
|
94
|
-
}, []);
|
|
95
|
-
|
|
96
|
-
const reset = useCallback(() => {
|
|
97
|
-
setValueState(initialValue);
|
|
98
|
-
setErrorState(null);
|
|
99
|
-
setTouched(false);
|
|
100
|
-
}, [initialValue]);
|
|
101
|
-
|
|
102
|
-
const isValid = error === null;
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
value,
|
|
106
|
-
error,
|
|
107
|
-
onChange: handleChange,
|
|
108
|
-
onBlur: handleBlur,
|
|
109
|
-
setValue,
|
|
110
|
-
setError,
|
|
111
|
-
reset,
|
|
112
|
-
isValid,
|
|
113
|
-
};
|
|
114
|
-
};
|
package/src/index.ts
DELETED
|
File without changes
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, ReactNode } from 'react';
|
|
2
|
-
import { Theme } from './types';
|
|
3
|
-
|
|
4
|
-
const ThemeContext = createContext<Theme | undefined>(undefined);
|
|
5
|
-
|
|
6
|
-
export const useTheme = (): Theme => {
|
|
7
|
-
const theme = useContext(ThemeContext);
|
|
8
|
-
if (!theme) {
|
|
9
|
-
throw new Error('useTheme must be used within a ThemeProvider');
|
|
10
|
-
}
|
|
11
|
-
return theme;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
interface ThemeProviderProps {
|
|
15
|
-
theme: Theme;
|
|
16
|
-
children: ReactNode;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme, children }) => {
|
|
20
|
-
return (
|
|
21
|
-
<ThemeContext.Provider value={theme}>
|
|
22
|
-
<div
|
|
23
|
-
style={{
|
|
24
|
-
color: theme.palette.text.primary,
|
|
25
|
-
backgroundColor: theme.palette.background.default,
|
|
26
|
-
fontFamily: theme.typography.fontFamily,
|
|
27
|
-
fontSize: theme.typography.fontSize,
|
|
28
|
-
}}
|
|
29
|
-
>
|
|
30
|
-
{children}
|
|
31
|
-
</div>
|
|
32
|
-
</ThemeContext.Provider>
|
|
33
|
-
);
|
|
34
|
-
};
|