@dofe/sso-hooks 0.1.5
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 +248 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/useAspectRatioSize.d.ts +18 -0
- package/dist/useAspectRatioSize.d.ts.map +1 -0
- package/dist/useAspectRatioSize.js +135 -0
- package/dist/useAspectRatioSize.js.map +1 -0
- package/dist/useDebouncedValue.d.ts +8 -0
- package/dist/useDebouncedValue.d.ts.map +1 -0
- package/dist/useDebouncedValue.js +21 -0
- package/dist/useDebouncedValue.js.map +1 -0
- package/dist/useErrorHandler.d.ts +31 -0
- package/dist/useErrorHandler.d.ts.map +1 -0
- package/dist/useErrorHandler.js +96 -0
- package/dist/useErrorHandler.js.map +1 -0
- package/dist/useHotkeys.d.ts +75 -0
- package/dist/useHotkeys.d.ts.map +1 -0
- package/dist/useHotkeys.js +172 -0
- package/dist/useHotkeys.js.map +1 -0
- package/dist/useI18nValidation.d.ts +80 -0
- package/dist/useI18nValidation.d.ts.map +1 -0
- package/dist/useI18nValidation.js +186 -0
- package/dist/useI18nValidation.js.map +1 -0
- package/dist/useOperationFeedback.d.ts +35 -0
- package/dist/useOperationFeedback.d.ts.map +1 -0
- package/dist/useOperationFeedback.js +78 -0
- package/dist/useOperationFeedback.js.map +1 -0
- package/dist/usePermissions.d.ts +31 -0
- package/dist/usePermissions.d.ts.map +1 -0
- package/dist/usePermissions.js +37 -0
- package/dist/usePermissions.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hotkey definition
|
|
3
|
+
*/
|
|
4
|
+
export interface HotkeyConfig {
|
|
5
|
+
/** Key combination (e.g., 'Space', 'Ctrl+S', 'Meta+Shift+P') */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Callback when hotkey is pressed */
|
|
8
|
+
handler: (event: KeyboardEvent) => void;
|
|
9
|
+
/** Whether to prevent default behavior (default: true) */
|
|
10
|
+
preventDefault?: boolean;
|
|
11
|
+
/** Whether to stop propagation (default: false) */
|
|
12
|
+
stopPropagation?: boolean;
|
|
13
|
+
/** Only trigger when these elements are NOT focused */
|
|
14
|
+
ignoreInputs?: boolean;
|
|
15
|
+
/** Description for accessibility */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Whether the hotkey is enabled (default: true) */
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Custom hook for keyboard shortcuts
|
|
22
|
+
*
|
|
23
|
+
* Features:
|
|
24
|
+
* - Support for key combinations (Ctrl, Alt, Shift, Meta)
|
|
25
|
+
* - Option to ignore when input elements are focused
|
|
26
|
+
* - Enable/disable hotkeys dynamically
|
|
27
|
+
* - Prevent default and stop propagation options
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* useHotkeys([
|
|
32
|
+
* { key: 'Space', handler: toggleRecording, description: 'Toggle recording' },
|
|
33
|
+
* { key: 'Escape', handler: cancelRecording, description: 'Cancel recording' },
|
|
34
|
+
* { key: 'Ctrl+S', handler: saveRecording, description: 'Save recording' },
|
|
35
|
+
* ]);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function useHotkeys(hotkeys: HotkeyConfig[], deps?: React.DependencyList): void;
|
|
39
|
+
/**
|
|
40
|
+
* Hook for a single hotkey
|
|
41
|
+
*/
|
|
42
|
+
export declare function useHotkey(key: string, handler: (event: KeyboardEvent) => void, options?: Omit<HotkeyConfig, 'key' | 'handler'>, deps?: React.DependencyList): void;
|
|
43
|
+
/**
|
|
44
|
+
* Recording-specific hotkeys preset
|
|
45
|
+
*/
|
|
46
|
+
export interface RecordingHotkeysConfig {
|
|
47
|
+
onToggleRecording?: () => void;
|
|
48
|
+
onPauseResume?: () => void;
|
|
49
|
+
onStop?: () => void;
|
|
50
|
+
onCancel?: () => void;
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Hook for recording-specific keyboard shortcuts
|
|
55
|
+
*
|
|
56
|
+
* Default shortcuts:
|
|
57
|
+
* - Space: Pause/Resume
|
|
58
|
+
* - Enter: Stop recording
|
|
59
|
+
* - Escape: Cancel recording
|
|
60
|
+
* - R: Start recording (when idle)
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* useRecordingHotkeys({
|
|
65
|
+
* onToggleRecording: handleToggle,
|
|
66
|
+
* onPauseResume: handlePauseResume,
|
|
67
|
+
* onStop: handleStop,
|
|
68
|
+
* onCancel: handleCancel,
|
|
69
|
+
* enabled: isRecordingActive,
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function useRecordingHotkeys(config: RecordingHotkeysConfig): void;
|
|
74
|
+
export default useHotkeys;
|
|
75
|
+
//# sourceMappingURL=useHotkeys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useHotkeys.d.ts","sourceRoot":"","sources":["../src/useHotkeys.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,0DAA0D;IAC1D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uDAAuD;IACvD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgDD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,YAAY,EAAE,EACvB,IAAI,GAAE,KAAK,CAAC,cAAmB,QAuDhC;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACvC,OAAO,GAAE,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,SAAS,CAAM,EACnD,IAAI,GAAE,KAAK,CAAC,cAAmB,QAGhC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,sBAAsB,QA0DjE;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Parse key combination string into parts
|
|
5
|
+
*/
|
|
6
|
+
function parseKey(key) {
|
|
7
|
+
const parts = key
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.split('+')
|
|
10
|
+
.map((p) => p.trim());
|
|
11
|
+
const mainKey = parts[parts.length - 1] ?? '';
|
|
12
|
+
return {
|
|
13
|
+
key: mainKey === 'space' ? ' ' : mainKey,
|
|
14
|
+
ctrl: parts.includes('ctrl') || parts.includes('control'),
|
|
15
|
+
alt: parts.includes('alt') || parts.includes('option'),
|
|
16
|
+
shift: parts.includes('shift'),
|
|
17
|
+
meta: parts.includes('meta') ||
|
|
18
|
+
parts.includes('cmd') ||
|
|
19
|
+
parts.includes('command'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if an element is an input element
|
|
24
|
+
*/
|
|
25
|
+
function isInputElement(element) {
|
|
26
|
+
if (!element)
|
|
27
|
+
return false;
|
|
28
|
+
const tagName = element.tagName.toLowerCase();
|
|
29
|
+
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (element.getAttribute('contenteditable') === 'true') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Custom hook for keyboard shortcuts
|
|
39
|
+
*
|
|
40
|
+
* Features:
|
|
41
|
+
* - Support for key combinations (Ctrl, Alt, Shift, Meta)
|
|
42
|
+
* - Option to ignore when input elements are focused
|
|
43
|
+
* - Enable/disable hotkeys dynamically
|
|
44
|
+
* - Prevent default and stop propagation options
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* useHotkeys([
|
|
49
|
+
* { key: 'Space', handler: toggleRecording, description: 'Toggle recording' },
|
|
50
|
+
* { key: 'Escape', handler: cancelRecording, description: 'Cancel recording' },
|
|
51
|
+
* { key: 'Ctrl+S', handler: saveRecording, description: 'Save recording' },
|
|
52
|
+
* ]);
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function useHotkeys(hotkeys, deps = []) {
|
|
56
|
+
const hotkeysRef = useRef(hotkeys);
|
|
57
|
+
// Update ref when hotkeys change
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
hotkeysRef.current = hotkeys;
|
|
60
|
+
}, [hotkeys]);
|
|
61
|
+
const handleKeyDown = useCallback((event) => {
|
|
62
|
+
const activeElement = document.activeElement;
|
|
63
|
+
for (const hotkey of hotkeysRef.current) {
|
|
64
|
+
// Skip if disabled
|
|
65
|
+
if (hotkey.enabled === false)
|
|
66
|
+
continue;
|
|
67
|
+
// Skip if focused on input and ignoreInputs is true
|
|
68
|
+
if (hotkey.ignoreInputs !== false && isInputElement(activeElement)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const parsed = parseKey(hotkey.key);
|
|
72
|
+
// Check if key matches
|
|
73
|
+
const keyMatches = event.key.toLowerCase() === parsed.key ||
|
|
74
|
+
event.code.toLowerCase() === parsed.key ||
|
|
75
|
+
event.code.toLowerCase() === `key${parsed.key}`;
|
|
76
|
+
// Check modifiers
|
|
77
|
+
const modifiersMatch = event.ctrlKey === parsed.ctrl &&
|
|
78
|
+
event.altKey === parsed.alt &&
|
|
79
|
+
event.shiftKey === parsed.shift &&
|
|
80
|
+
event.metaKey === parsed.meta;
|
|
81
|
+
if (keyMatches && modifiersMatch) {
|
|
82
|
+
if (hotkey.preventDefault !== false) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
}
|
|
85
|
+
if (hotkey.stopPropagation) {
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
}
|
|
88
|
+
hotkey.handler(event);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
95
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
96
|
+
}, [handleKeyDown, ...deps]);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Hook for a single hotkey
|
|
100
|
+
*/
|
|
101
|
+
export function useHotkey(key, handler, options = {}, deps = []) {
|
|
102
|
+
useHotkeys([{ key, handler, ...options }], deps);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Hook for recording-specific keyboard shortcuts
|
|
106
|
+
*
|
|
107
|
+
* Default shortcuts:
|
|
108
|
+
* - Space: Pause/Resume
|
|
109
|
+
* - Enter: Stop recording
|
|
110
|
+
* - Escape: Cancel recording
|
|
111
|
+
* - R: Start recording (when idle)
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```tsx
|
|
115
|
+
* useRecordingHotkeys({
|
|
116
|
+
* onToggleRecording: handleToggle,
|
|
117
|
+
* onPauseResume: handlePauseResume,
|
|
118
|
+
* onStop: handleStop,
|
|
119
|
+
* onCancel: handleCancel,
|
|
120
|
+
* enabled: isRecordingActive,
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function useRecordingHotkeys(config) {
|
|
125
|
+
const { onToggleRecording, onPauseResume, onStop, onCancel, enabled = true, } = config;
|
|
126
|
+
const hotkeys = [];
|
|
127
|
+
if (onPauseResume) {
|
|
128
|
+
hotkeys.push({
|
|
129
|
+
key: 'Space',
|
|
130
|
+
handler: onPauseResume,
|
|
131
|
+
description: 'Pause/Resume recording',
|
|
132
|
+
enabled,
|
|
133
|
+
ignoreInputs: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (onStop) {
|
|
137
|
+
hotkeys.push({
|
|
138
|
+
key: 'Enter',
|
|
139
|
+
handler: onStop,
|
|
140
|
+
description: 'Stop recording',
|
|
141
|
+
enabled,
|
|
142
|
+
ignoreInputs: true,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (onCancel) {
|
|
146
|
+
hotkeys.push({
|
|
147
|
+
key: 'Escape',
|
|
148
|
+
handler: onCancel,
|
|
149
|
+
description: 'Cancel recording',
|
|
150
|
+
enabled,
|
|
151
|
+
ignoreInputs: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (onToggleRecording) {
|
|
155
|
+
hotkeys.push({
|
|
156
|
+
key: 'r',
|
|
157
|
+
handler: onToggleRecording,
|
|
158
|
+
description: 'Toggle recording',
|
|
159
|
+
enabled,
|
|
160
|
+
ignoreInputs: true,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
useHotkeys(hotkeys, [
|
|
164
|
+
enabled,
|
|
165
|
+
onToggleRecording,
|
|
166
|
+
onPauseResume,
|
|
167
|
+
onStop,
|
|
168
|
+
onCancel,
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
export default useHotkeys;
|
|
172
|
+
//# sourceMappingURL=useHotkeys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useHotkeys.js","sourceRoot":"","sources":["../src/useHotkeys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
type FieldKey = 'username' | 'email' | 'password' | 'confirmPassword' | 'currentPassword' | 'newPassword' | 'phone' | 'name' | 'title' | 'content' | 'description' | 'code' | 'roleName' | 'departmentName' | 'link';
|
|
3
|
+
/**
|
|
4
|
+
* 国际化表单验证 Hook
|
|
5
|
+
*
|
|
6
|
+
* 使用示例:
|
|
7
|
+
* ```tsx
|
|
8
|
+
* const v = useI18nValidation();
|
|
9
|
+
*
|
|
10
|
+
* // 使用预定义验证器
|
|
11
|
+
* const schema = z.object({
|
|
12
|
+
* email: v.email(),
|
|
13
|
+
* phone: v.phone(),
|
|
14
|
+
* password: v.password({ min: 6 }),
|
|
15
|
+
* username: v.required('username'),
|
|
16
|
+
* bio: v.string('description', { max: 200 }),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // 自定义验证消息
|
|
20
|
+
* const customSchema = z.object({
|
|
21
|
+
* title: z.string().min(1, { message: v.getMessage('required', { field: 'title' }) }),
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function useI18nValidation(): {
|
|
26
|
+
getFieldName: (fieldKey: FieldKey | string) => string;
|
|
27
|
+
getMessage: (messageKey: string, params?: {
|
|
28
|
+
field?: FieldKey | string;
|
|
29
|
+
min?: number;
|
|
30
|
+
max?: number;
|
|
31
|
+
}) => string;
|
|
32
|
+
required: (fieldKey: FieldKey | string) => z.ZodString;
|
|
33
|
+
string: (fieldKey: FieldKey | string, options?: {
|
|
34
|
+
min?: number;
|
|
35
|
+
max?: number;
|
|
36
|
+
}) => z.ZodString;
|
|
37
|
+
email: () => z.ZodString;
|
|
38
|
+
phone: () => z.ZodString;
|
|
39
|
+
url: () => z.ZodString;
|
|
40
|
+
password: (options?: {
|
|
41
|
+
min?: number;
|
|
42
|
+
requireStrong?: boolean;
|
|
43
|
+
}) => z.ZodString;
|
|
44
|
+
number: (fieldKey: FieldKey | string, options?: {
|
|
45
|
+
min?: number;
|
|
46
|
+
max?: number;
|
|
47
|
+
}) => z.ZodNumber;
|
|
48
|
+
passwordMismatchMessage: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* 创建带有密码确认的表单 schema 辅助函数
|
|
52
|
+
*
|
|
53
|
+
* 使用示例:
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const v = useI18nValidation();
|
|
56
|
+
* const schema = createPasswordConfirmSchema(v, {
|
|
57
|
+
* passwordField: 'password',
|
|
58
|
+
* confirmField: 'confirmPassword',
|
|
59
|
+
* minLength: 8,
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createPasswordConfirmSchema(v: ReturnType<typeof useI18nValidation>, options?: {
|
|
64
|
+
passwordField?: string;
|
|
65
|
+
confirmField?: string;
|
|
66
|
+
minLength?: number;
|
|
67
|
+
requireStrong?: boolean;
|
|
68
|
+
}): z.ZodEffects<z.ZodObject<{
|
|
69
|
+
[x: string]: z.ZodString;
|
|
70
|
+
}, "strip", z.ZodTypeAny, {
|
|
71
|
+
[x: string]: string;
|
|
72
|
+
}, {
|
|
73
|
+
[x: string]: string;
|
|
74
|
+
}>, {
|
|
75
|
+
[x: string]: string;
|
|
76
|
+
}, {
|
|
77
|
+
[x: string]: string;
|
|
78
|
+
}>;
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=useI18nValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useI18nValidation.d.ts","sourceRoot":"","sources":["../src/useI18nValidation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,KAAK,QAAQ,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,aAAa,GACb,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,aAAa,GACb,MAAM,GACN,UAAU,GACV,gBAAgB,GAChB,MAAM,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB;6BAOlB,QAAQ,GAAG,MAAM,KAAG,MAAM;6BAgBvB,MAAM,WACT;QAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,KACjE,MAAM;yBAmBE,QAAQ,GAAG,MAAM;uBAYjB,QAAQ,GAAG,MAAM,YAAY;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;;yBAqD3D;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE;uBAsBzC,QAAQ,GAAG,MAAM,YAAY;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;EA6CzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,CAAC,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,EACvC,OAAO,GAAE;IACP,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACpB;;;;;;;;;;GAkBP"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
/**
|
|
6
|
+
* 国际化表单验证 Hook
|
|
7
|
+
*
|
|
8
|
+
* 使用示例:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const v = useI18nValidation();
|
|
11
|
+
*
|
|
12
|
+
* // 使用预定义验证器
|
|
13
|
+
* const schema = z.object({
|
|
14
|
+
* email: v.email(),
|
|
15
|
+
* phone: v.phone(),
|
|
16
|
+
* password: v.password({ min: 6 }),
|
|
17
|
+
* username: v.required('username'),
|
|
18
|
+
* bio: v.string('description', { max: 200 }),
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // 自定义验证消息
|
|
22
|
+
* const customSchema = z.object({
|
|
23
|
+
* title: z.string().min(1, { message: v.getMessage('required', { field: 'title' }) }),
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function useI18nValidation() {
|
|
28
|
+
const t = useTranslations('validation');
|
|
29
|
+
/**
|
|
30
|
+
* 获取字段显示名称
|
|
31
|
+
*/
|
|
32
|
+
const getFieldName = useCallback((fieldKey) => {
|
|
33
|
+
try {
|
|
34
|
+
return t(`fields.${fieldKey}`);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// 如果没有翻译,返回原始 key
|
|
38
|
+
return fieldKey;
|
|
39
|
+
}
|
|
40
|
+
}, [t]);
|
|
41
|
+
/**
|
|
42
|
+
* 获取验证消息
|
|
43
|
+
*/
|
|
44
|
+
const getMessage = useCallback((messageKey, params) => {
|
|
45
|
+
const fieldName = params?.field ? getFieldName(params.field) : '';
|
|
46
|
+
try {
|
|
47
|
+
return t(messageKey, {
|
|
48
|
+
field: fieldName,
|
|
49
|
+
min: params?.min ?? 0,
|
|
50
|
+
max: params?.max ?? 0,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return messageKey;
|
|
55
|
+
}
|
|
56
|
+
}, [t, getFieldName]);
|
|
57
|
+
/**
|
|
58
|
+
* 必填字符串
|
|
59
|
+
*/
|
|
60
|
+
const required = useCallback((fieldKey) => {
|
|
61
|
+
return z
|
|
62
|
+
.string()
|
|
63
|
+
.min(1, { message: getMessage('required', { field: fieldKey }) });
|
|
64
|
+
}, [getMessage]);
|
|
65
|
+
/**
|
|
66
|
+
* 字符串(可选长度限制)
|
|
67
|
+
*/
|
|
68
|
+
const string = useCallback((fieldKey, options) => {
|
|
69
|
+
let schema = z.string();
|
|
70
|
+
if (options?.min !== undefined) {
|
|
71
|
+
schema = schema.min(options.min, {
|
|
72
|
+
message: getMessage('minLength', {
|
|
73
|
+
field: fieldKey,
|
|
74
|
+
min: options.min,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (options?.max !== undefined) {
|
|
79
|
+
schema = schema.max(options.max, {
|
|
80
|
+
message: getMessage('maxLength', {
|
|
81
|
+
field: fieldKey,
|
|
82
|
+
max: options.max,
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return schema;
|
|
87
|
+
}, [getMessage]);
|
|
88
|
+
/**
|
|
89
|
+
* 邮箱验证
|
|
90
|
+
*/
|
|
91
|
+
const email = useCallback(() => {
|
|
92
|
+
return z.string().email({ message: getMessage('invalidEmail') });
|
|
93
|
+
}, [getMessage]);
|
|
94
|
+
/**
|
|
95
|
+
* 手机号验证(中国大陆格式)
|
|
96
|
+
*/
|
|
97
|
+
const phone = useCallback(() => {
|
|
98
|
+
return z
|
|
99
|
+
.string()
|
|
100
|
+
.regex(/^1[3-9]\d{9}$/, { message: getMessage('invalidPhone') });
|
|
101
|
+
}, [getMessage]);
|
|
102
|
+
/**
|
|
103
|
+
* URL 验证
|
|
104
|
+
*/
|
|
105
|
+
const url = useCallback(() => {
|
|
106
|
+
return z.string().url({ message: getMessage('invalidUrl') });
|
|
107
|
+
}, [getMessage]);
|
|
108
|
+
/**
|
|
109
|
+
* 密码验证
|
|
110
|
+
*/
|
|
111
|
+
const password = useCallback((options) => {
|
|
112
|
+
const minLength = options?.min ?? 6;
|
|
113
|
+
let schema = z.string().min(minLength, {
|
|
114
|
+
message: getMessage('passwordTooShort', { min: minLength }),
|
|
115
|
+
});
|
|
116
|
+
if (options?.requireStrong) {
|
|
117
|
+
// 要求包含字母和数字
|
|
118
|
+
schema = schema.regex(/^(?=.*[A-Za-z])(?=.*\d)/, {
|
|
119
|
+
message: getMessage('passwordTooWeak'),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return schema;
|
|
123
|
+
}, [getMessage]);
|
|
124
|
+
/**
|
|
125
|
+
* 数字验证(可选范围限制)
|
|
126
|
+
*/
|
|
127
|
+
const number = useCallback((fieldKey, options) => {
|
|
128
|
+
let schema = z.number();
|
|
129
|
+
if (options?.min !== undefined) {
|
|
130
|
+
schema = schema.min(options.min, {
|
|
131
|
+
message: getMessage('min', { field: fieldKey, min: options.min }),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (options?.max !== undefined) {
|
|
135
|
+
schema = schema.max(options.max, {
|
|
136
|
+
message: getMessage('max', { field: fieldKey, max: options.max }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return schema;
|
|
140
|
+
}, [getMessage]);
|
|
141
|
+
/**
|
|
142
|
+
* 确认密码验证(需要在 refine 中使用)
|
|
143
|
+
*/
|
|
144
|
+
const passwordMismatchMessage = useMemo(() => getMessage('passwordMismatch'), [getMessage]);
|
|
145
|
+
return {
|
|
146
|
+
// 获取方法
|
|
147
|
+
getFieldName,
|
|
148
|
+
getMessage,
|
|
149
|
+
// 验证器
|
|
150
|
+
required,
|
|
151
|
+
string,
|
|
152
|
+
email,
|
|
153
|
+
phone,
|
|
154
|
+
url,
|
|
155
|
+
password,
|
|
156
|
+
number,
|
|
157
|
+
// 预定义消息
|
|
158
|
+
passwordMismatchMessage,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 创建带有密码确认的表单 schema 辅助函数
|
|
163
|
+
*
|
|
164
|
+
* 使用示例:
|
|
165
|
+
* ```tsx
|
|
166
|
+
* const v = useI18nValidation();
|
|
167
|
+
* const schema = createPasswordConfirmSchema(v, {
|
|
168
|
+
* passwordField: 'password',
|
|
169
|
+
* confirmField: 'confirmPassword',
|
|
170
|
+
* minLength: 8,
|
|
171
|
+
* });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export function createPasswordConfirmSchema(v, options = {}) {
|
|
175
|
+
const { passwordField = 'password', confirmField = 'confirmPassword', minLength = 6, requireStrong = false, } = options;
|
|
176
|
+
return z
|
|
177
|
+
.object({
|
|
178
|
+
[passwordField]: v.password({ min: minLength, requireStrong }),
|
|
179
|
+
[confirmField]: v.required('confirmPassword'),
|
|
180
|
+
})
|
|
181
|
+
.refine((data) => data[passwordField] === data[confirmField], {
|
|
182
|
+
message: v.passwordMismatchMessage,
|
|
183
|
+
path: [confirmField],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=useI18nValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useI18nValidation.js","sourceRoot":"","sources":["../src/useI18nValidation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface ToastOptions {
|
|
2
|
+
duration?: number;
|
|
3
|
+
position?: 'top-left' | 'top-right' | 'top-center' | 'bottom-left' | 'bottom-right' | 'bottom-center';
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* 统一的操作反馈 Hook
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { success, error, warning, info } = useOperationFeedback();
|
|
11
|
+
*
|
|
12
|
+
* success('操作成功');
|
|
13
|
+
* error('操作失败');
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function useOperationFeedback(): {
|
|
17
|
+
success: (message: string, options?: ToastOptions) => void;
|
|
18
|
+
error: (message: string, options?: ToastOptions) => void;
|
|
19
|
+
warning: (message: string, options?: ToastOptions) => void;
|
|
20
|
+
info: (message: string, options?: ToastOptions) => void;
|
|
21
|
+
loading: (message: string, options?: ToastOptions) => string | number;
|
|
22
|
+
promise: <T>(promise: Promise<T>, messages: {
|
|
23
|
+
loading: string;
|
|
24
|
+
success: string | ((data: T) => string);
|
|
25
|
+
error: string | ((error: unknown) => string);
|
|
26
|
+
}, options?: ToastOptions) => (string & {
|
|
27
|
+
unwrap: () => Promise<T>;
|
|
28
|
+
}) | (number & {
|
|
29
|
+
unwrap: () => Promise<T>;
|
|
30
|
+
}) | {
|
|
31
|
+
unwrap: () => Promise<T>;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=useOperationFeedback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOperationFeedback.d.ts","sourceRoot":"","sources":["../src/useOperationFeedback.ts"],"names":[],"mappings":"AAWA,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EACL,UAAU,GACV,WAAW,GACX,YAAY,GACZ,aAAa,GACb,cAAc,GACd,eAAe,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB;uBAQI,MAAM,YAAY,YAAY;qBAQhC,MAAM,YAAY,YAAY;uBAQ5B,MAAM,YAAY,YAAY;oBAQjC,MAAM,YAAY,YAAY;uBAQ3B,MAAM,YAAY,YAAY;cAQjE,CAAC,WACS,OAAO,CAAC,CAAC,CAAC,YACT;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;QACxC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC,CAAC;KAC9C,YACS,YAAY;;;;;;;EAqB3B"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* useOperationFeedback - 统一的操作反馈 Hook
|
|
4
|
+
* 提供一致的成功/错误/警告提示
|
|
5
|
+
*/
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
import { toast } from 'sonner';
|
|
8
|
+
import { useTranslations } from 'next-intl';
|
|
9
|
+
/**
|
|
10
|
+
* 统一的操作反馈 Hook
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { success, error, warning, info } = useOperationFeedback();
|
|
15
|
+
*
|
|
16
|
+
* success('操作成功');
|
|
17
|
+
* error('操作失败');
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function useOperationFeedback() {
|
|
21
|
+
const t = useTranslations('common');
|
|
22
|
+
const defaultOptions = {
|
|
23
|
+
duration: 3000,
|
|
24
|
+
position: 'top-right',
|
|
25
|
+
};
|
|
26
|
+
const success = useCallback((message, options) => {
|
|
27
|
+
toast.success(message, {
|
|
28
|
+
...defaultOptions,
|
|
29
|
+
...options,
|
|
30
|
+
duration: options?.duration ?? 2000, // 成功提示稍短
|
|
31
|
+
});
|
|
32
|
+
}, []);
|
|
33
|
+
const error = useCallback((message, options) => {
|
|
34
|
+
toast.error(message, {
|
|
35
|
+
...defaultOptions,
|
|
36
|
+
...options,
|
|
37
|
+
duration: options?.duration ?? 4000, // 错误提示稍长
|
|
38
|
+
});
|
|
39
|
+
}, []);
|
|
40
|
+
const warning = useCallback((message, options) => {
|
|
41
|
+
toast.warning(message, {
|
|
42
|
+
...defaultOptions,
|
|
43
|
+
...options,
|
|
44
|
+
duration: options?.duration ?? 3000,
|
|
45
|
+
});
|
|
46
|
+
}, []);
|
|
47
|
+
const info = useCallback((message, options) => {
|
|
48
|
+
toast.info(message, {
|
|
49
|
+
...defaultOptions,
|
|
50
|
+
...options,
|
|
51
|
+
duration: options?.duration ?? 3000,
|
|
52
|
+
});
|
|
53
|
+
}, []);
|
|
54
|
+
const loading = useCallback((message, options) => {
|
|
55
|
+
return toast.loading(message, {
|
|
56
|
+
...defaultOptions,
|
|
57
|
+
...options,
|
|
58
|
+
});
|
|
59
|
+
}, []);
|
|
60
|
+
const promise = useCallback((promise, messages, options) => {
|
|
61
|
+
return toast.promise(promise, {
|
|
62
|
+
loading: messages.loading,
|
|
63
|
+
success: messages.success,
|
|
64
|
+
error: messages.error,
|
|
65
|
+
...defaultOptions,
|
|
66
|
+
...options,
|
|
67
|
+
});
|
|
68
|
+
}, []);
|
|
69
|
+
return {
|
|
70
|
+
success,
|
|
71
|
+
error,
|
|
72
|
+
warning,
|
|
73
|
+
info,
|
|
74
|
+
loading,
|
|
75
|
+
promise,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=useOperationFeedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOperationFeedback.js","sourceRoot":"","sources":["../src/useOperationFeedback.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAa5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEpC,MAAM,cAAc,GAAiB;QACnC,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,WAAW;KACtB,CAAC;IAEF,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,OAAsB,EAAE,EAAE;QACtE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE;YACrB,GAAG,cAAc;YACjB,GAAG,OAAO;YACV,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,SAAS;SAC/C,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,OAAsB,EAAE,EAAE;QACpE,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE;YACnB,GAAG,cAAc;YACjB,GAAG,OAAO;YACV,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,SAAS;SAC/C,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,OAAsB,EAAE,EAAE;QACtE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE;YACrB,GAAG,cAAc;YACjB,GAAG,OAAO;YACV,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI;SACpC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,OAAsB,EAAE,EAAE;QACnE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE;YAClB,GAAG,cAAc;YACjB,GAAG,OAAO;YACV,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI;SACpC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,OAAsB,EAAE,EAAE;QACtE,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE;YAC5B,GAAG,cAAc;YACjB,GAAG,OAAO;SACX,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CACzB,CACE,OAAmB,EACnB,QAIC,EACD,OAAsB,EACtB,EAAE;QACF,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE;YAC5B,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,GAAG,cAAc;YACjB,GAAG,OAAO;SACX,CAAC,CAAC;IACL,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO;QACL,OAAO;QACP,KAAK;QACL,OAAO;QACP,IAAI;QACJ,OAAO;QACP,OAAO;KACR,CAAC;AACJ,CAAC"}
|