@ug666/ui-react 0.1.0 → 0.2.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.
Files changed (54) hide show
  1. package/dist/blocks/index.cjs +238 -0
  2. package/dist/blocks/index.cjs.map +1 -0
  3. package/dist/blocks/index.d.cts +86 -0
  4. package/dist/blocks/index.d.ts +86 -0
  5. package/dist/blocks/index.js +153 -0
  6. package/dist/blocks/index.js.map +1 -0
  7. package/dist/button-CaLZig8j.d.cts +22 -0
  8. package/dist/button-CaLZig8j.d.ts +22 -0
  9. package/dist/chunk-2IVRUJKO.js +377 -0
  10. package/dist/chunk-2IVRUJKO.js.map +1 -0
  11. package/dist/chunk-73WQAE3E.js +3003 -0
  12. package/dist/chunk-73WQAE3E.js.map +1 -0
  13. package/dist/chunk-RUDEZA5Q.js +62 -0
  14. package/dist/chunk-RUDEZA5Q.js.map +1 -0
  15. package/dist/chunk-S45GP6IB.js +254 -0
  16. package/dist/chunk-S45GP6IB.js.map +1 -0
  17. package/dist/components/index.cjs +3993 -0
  18. package/dist/components/index.cjs.map +1 -0
  19. package/dist/components/index.d.cts +1097 -0
  20. package/dist/components/index.d.ts +1097 -0
  21. package/dist/components/index.js +330 -0
  22. package/dist/components/index.js.map +1 -0
  23. package/dist/hooks/index.cjs +1 -0
  24. package/dist/hooks/index.cjs.map +1 -1
  25. package/dist/hooks/index.js +1 -0
  26. package/dist/index.cjs +1410 -710
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +274 -1340
  29. package/dist/index.d.ts +274 -1340
  30. package/dist/index.js +385 -3229
  31. package/dist/index.js.map +1 -1
  32. package/dist/labs/index.cjs +34 -0
  33. package/dist/labs/index.cjs.map +1 -0
  34. package/dist/labs/index.d.cts +12 -0
  35. package/dist/labs/index.d.ts +12 -0
  36. package/dist/labs/index.js +9 -0
  37. package/dist/labs/index.js.map +1 -0
  38. package/dist/patterns/index.cjs +758 -0
  39. package/dist/patterns/index.cjs.map +1 -0
  40. package/dist/patterns/index.d.cts +158 -0
  41. package/dist/patterns/index.d.ts +158 -0
  42. package/dist/patterns/index.js +320 -0
  43. package/dist/patterns/index.js.map +1 -0
  44. package/dist/primitives/index.cjs +384 -0
  45. package/dist/primitives/index.cjs.map +1 -0
  46. package/dist/primitives/index.d.cts +137 -0
  47. package/dist/primitives/index.d.ts +137 -0
  48. package/dist/primitives/index.js +56 -0
  49. package/dist/primitives/index.js.map +1 -0
  50. package/dist/sidebar-vl00Z2o-.d.cts +93 -0
  51. package/dist/sidebar-vl00Z2o-.d.ts +93 -0
  52. package/dist/styles.css +2499 -0
  53. package/dist/tokens.css +86 -9
  54. package/package.json +36 -6
@@ -0,0 +1,330 @@
1
+ "use client";
2
+ import {
3
+ Accordion,
4
+ AccordionContent,
5
+ AccordionItem,
6
+ AccordionTrigger,
7
+ Alert,
8
+ AlertDescription,
9
+ AlertTitle,
10
+ Avatar,
11
+ AvatarFallback,
12
+ AvatarGroup,
13
+ AvatarImage,
14
+ Breadcrumb,
15
+ BreadcrumbItem,
16
+ BreadcrumbLink,
17
+ BreadcrumbSeparator,
18
+ Checkbox,
19
+ ContextMenu,
20
+ ContextMenuContent,
21
+ ContextMenuItem,
22
+ ContextMenuSeparator,
23
+ ContextMenuTrigger,
24
+ Descriptions,
25
+ DescriptionsItem,
26
+ Dialog,
27
+ Drawer,
28
+ DrawerClose,
29
+ DrawerContent,
30
+ DrawerDescription,
31
+ DrawerFooter,
32
+ DrawerHeader,
33
+ DrawerTitle,
34
+ DropdownContent,
35
+ DropdownItem,
36
+ DropdownMenu,
37
+ DropdownSeparator,
38
+ DropdownTrigger,
39
+ EmptyState,
40
+ Form,
41
+ FormControl,
42
+ FormDescription,
43
+ FormField,
44
+ FormItem,
45
+ FormLabel,
46
+ FormMessage,
47
+ NavigationMenu,
48
+ NavigationMenuContent,
49
+ NavigationMenuItem,
50
+ NavigationMenuLink,
51
+ NavigationMenuTrigger,
52
+ NumberInput,
53
+ OTPInput,
54
+ Pagination,
55
+ Popover,
56
+ PopoverContent,
57
+ PopoverTrigger,
58
+ Progress,
59
+ Radio,
60
+ RadioGroup,
61
+ Select,
62
+ Sheet,
63
+ Skeleton,
64
+ Slider,
65
+ Statistic,
66
+ Steps,
67
+ Switch,
68
+ Table,
69
+ TableBody,
70
+ TableCell,
71
+ TableHead,
72
+ TableHeader,
73
+ TableRow,
74
+ Tabs,
75
+ TabsContent,
76
+ TabsList,
77
+ TabsTrigger,
78
+ Textarea,
79
+ Toaster,
80
+ Tooltip,
81
+ TooltipContent,
82
+ TooltipTrigger,
83
+ checkboxVariants,
84
+ numberInputVariants,
85
+ toast
86
+ } from "../chunk-73WQAE3E.js";
87
+ import {
88
+ Modal,
89
+ ModalCloseButton,
90
+ ModalContent,
91
+ ModalFooter,
92
+ ModalHeader,
93
+ ModalTitle,
94
+ Sidebar
95
+ } from "../chunk-2IVRUJKO.js";
96
+ import {
97
+ Badge,
98
+ Card,
99
+ CardContent,
100
+ CardDescription,
101
+ CardFooter,
102
+ CardHeader,
103
+ CardTitle,
104
+ Input,
105
+ Label,
106
+ Separator,
107
+ Spinner,
108
+ Tag,
109
+ badgeVariants,
110
+ tagVariants
111
+ } from "../chunk-S45GP6IB.js";
112
+ import {
113
+ Button,
114
+ buttonVariants
115
+ } from "../chunk-RUDEZA5Q.js";
116
+ export {
117
+ Accordion,
118
+ AccordionContent,
119
+ AccordionItem,
120
+ AccordionTrigger,
121
+ Alert,
122
+ AlertDescription,
123
+ AlertTitle,
124
+ Avatar,
125
+ AvatarFallback,
126
+ AvatarGroup,
127
+ AvatarImage,
128
+ Badge,
129
+ Breadcrumb,
130
+ BreadcrumbItem,
131
+ BreadcrumbLink,
132
+ BreadcrumbSeparator,
133
+ Button,
134
+ Card,
135
+ CardContent,
136
+ CardDescription,
137
+ CardFooter,
138
+ CardHeader,
139
+ CardTitle,
140
+ Checkbox,
141
+ ContextMenu,
142
+ ContextMenuContent,
143
+ ContextMenuItem,
144
+ ContextMenuSeparator,
145
+ ContextMenuTrigger,
146
+ Descriptions,
147
+ DescriptionsItem,
148
+ Dialog,
149
+ Drawer,
150
+ DrawerClose,
151
+ DrawerContent,
152
+ DrawerDescription,
153
+ DrawerFooter,
154
+ DrawerHeader,
155
+ DrawerTitle,
156
+ DropdownContent,
157
+ DropdownItem,
158
+ DropdownMenu,
159
+ DropdownSeparator,
160
+ DropdownTrigger,
161
+ EmptyState,
162
+ Form,
163
+ FormControl,
164
+ FormDescription,
165
+ FormField,
166
+ FormItem,
167
+ FormLabel,
168
+ FormMessage,
169
+ Input,
170
+ Label,
171
+ Modal,
172
+ ModalCloseButton,
173
+ ModalContent,
174
+ ModalFooter,
175
+ ModalHeader,
176
+ ModalTitle,
177
+ NavigationMenu,
178
+ NavigationMenuContent,
179
+ NavigationMenuItem,
180
+ NavigationMenuLink,
181
+ NavigationMenuTrigger,
182
+ NumberInput,
183
+ OTPInput,
184
+ Pagination,
185
+ Popover,
186
+ PopoverContent,
187
+ PopoverTrigger,
188
+ Progress,
189
+ Radio,
190
+ RadioGroup,
191
+ Select,
192
+ Separator,
193
+ Sheet,
194
+ Sidebar,
195
+ Skeleton,
196
+ Slider,
197
+ Spinner,
198
+ Statistic,
199
+ Steps,
200
+ Switch,
201
+ Table,
202
+ TableBody,
203
+ TableCell,
204
+ TableHead,
205
+ TableHeader,
206
+ TableRow,
207
+ Tabs,
208
+ TabsContent,
209
+ TabsList,
210
+ TabsTrigger,
211
+ Tag,
212
+ Textarea,
213
+ Toaster,
214
+ Tooltip,
215
+ TooltipContent,
216
+ TooltipTrigger,
217
+ Accordion as UGAccordion,
218
+ AccordionContent as UGAccordionContent,
219
+ AccordionItem as UGAccordionItem,
220
+ AccordionTrigger as UGAccordionTrigger,
221
+ Alert as UGAlert,
222
+ AlertDescription as UGAlertDescription,
223
+ AlertTitle as UGAlertTitle,
224
+ Avatar as UGAvatar,
225
+ AvatarFallback as UGAvatarFallback,
226
+ AvatarGroup as UGAvatarGroup,
227
+ AvatarImage as UGAvatarImage,
228
+ Badge as UGBadge,
229
+ Breadcrumb as UGBreadcrumb,
230
+ BreadcrumbItem as UGBreadcrumbItem,
231
+ BreadcrumbLink as UGBreadcrumbLink,
232
+ BreadcrumbSeparator as UGBreadcrumbSeparator,
233
+ Button as UGButton,
234
+ Card as UGCard,
235
+ CardContent as UGCardContent,
236
+ CardDescription as UGCardDescription,
237
+ CardFooter as UGCardFooter,
238
+ CardHeader as UGCardHeader,
239
+ CardTitle as UGCardTitle,
240
+ Checkbox as UGCheckbox,
241
+ ContextMenu as UGContextMenu,
242
+ ContextMenuContent as UGContextMenuContent,
243
+ ContextMenuItem as UGContextMenuItem,
244
+ ContextMenuSeparator as UGContextMenuSeparator,
245
+ ContextMenuTrigger as UGContextMenuTrigger,
246
+ Descriptions as UGDescriptions,
247
+ DescriptionsItem as UGDescriptionsItem,
248
+ Dialog as UGDialog,
249
+ Drawer as UGDrawer,
250
+ DrawerClose as UGDrawerClose,
251
+ DrawerContent as UGDrawerContent,
252
+ DrawerDescription as UGDrawerDescription,
253
+ DrawerFooter as UGDrawerFooter,
254
+ DrawerHeader as UGDrawerHeader,
255
+ DrawerTitle as UGDrawerTitle,
256
+ DropdownMenu as UGDropdown,
257
+ DropdownContent as UGDropdownContent,
258
+ DropdownItem as UGDropdownItem,
259
+ DropdownMenu as UGDropdownMenu,
260
+ DropdownSeparator as UGDropdownSeparator,
261
+ DropdownTrigger as UGDropdownTrigger,
262
+ EmptyState as UGEmptyState,
263
+ Form as UGForm,
264
+ FormControl as UGFormControl,
265
+ FormDescription as UGFormDescription,
266
+ FormField as UGFormField,
267
+ FormItem as UGFormItem,
268
+ FormLabel as UGFormLabel,
269
+ FormMessage as UGFormMessage,
270
+ Input as UGInput,
271
+ Label as UGLabel,
272
+ Modal as UGModal,
273
+ ModalCloseButton as UGModalCloseButton,
274
+ ModalContent as UGModalContent,
275
+ ModalFooter as UGModalFooter,
276
+ ModalHeader as UGModalHeader,
277
+ ModalTitle as UGModalTitle,
278
+ NavigationMenu as UGNavigationMenu,
279
+ NavigationMenuContent as UGNavigationMenuContent,
280
+ NavigationMenuItem as UGNavigationMenuItem,
281
+ NavigationMenuLink as UGNavigationMenuLink,
282
+ NavigationMenuTrigger as UGNavigationMenuTrigger,
283
+ NumberInput as UGNumberInput,
284
+ OTPInput as UGOTPInput,
285
+ Pagination as UGPagination,
286
+ Popover as UGPopover,
287
+ PopoverContent as UGPopoverContent,
288
+ PopoverTrigger as UGPopoverTrigger,
289
+ Progress as UGProgress,
290
+ Radio as UGRadio,
291
+ RadioGroup as UGRadioGroup,
292
+ Select as UGSelect,
293
+ Separator as UGSeparator,
294
+ Sheet as UGSheet,
295
+ Sidebar as UGSidebar,
296
+ Skeleton as UGSkeleton,
297
+ Slider as UGSlider,
298
+ Spinner as UGSpinner,
299
+ Statistic as UGStatistic,
300
+ Steps as UGSteps,
301
+ Switch as UGSwitch,
302
+ Table as UGTable,
303
+ TableBody as UGTableBody,
304
+ TableCell as UGTableCell,
305
+ TableHead as UGTableHead,
306
+ TableHeader as UGTableHeader,
307
+ TableRow as UGTableRow,
308
+ Tabs as UGTabs,
309
+ TabsContent as UGTabsContent,
310
+ TabsList as UGTabsList,
311
+ TabsTrigger as UGTabsTrigger,
312
+ Tag as UGTag,
313
+ Textarea as UGTextarea,
314
+ Toaster as UGToaster,
315
+ Tooltip as UGTooltip,
316
+ TooltipContent as UGTooltipContent,
317
+ TooltipTrigger as UGTooltipTrigger,
318
+ badgeVariants,
319
+ buttonVariants,
320
+ checkboxVariants,
321
+ numberInputVariants,
322
+ tagVariants,
323
+ toast,
324
+ badgeVariants as ugBadgeVariants,
325
+ buttonVariants as ugButtonVariants,
326
+ checkboxVariants as ugCheckboxVariants,
327
+ numberInputVariants as ugNumberInputVariants,
328
+ tagVariants as ugTagVariants
329
+ };
330
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ "use client";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-debounce.ts","../../src/hooks/use-local-storage.ts","../../src/hooks/use-media-query.ts","../../src/hooks/use-click-outside.ts","../../src/hooks/use-copy-to-clipboard.ts","../../src/hooks/use-toggle.ts","../../src/hooks/use-pagination.ts","../../src/hooks/use-form.ts","../../src/hooks/use-mounted.ts","../../src/hooks/use-interval.ts","../../src/hooks/use-timeout.ts","../../src/hooks/use-hotkeys.ts","../../src/hooks/use-event-listener.ts"],"sourcesContent":["/**\n * @description: @ug666/ui-react/hooks 子入口 — 所有 Hooks 统一导出\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nexport { useDebounce } from './use-debounce'\nexport {\n useLocalStorage,\n type UseLocalStorageReturn,\n type SetLocalStorageValue,\n} from './use-local-storage'\nexport { useMediaQuery } from './use-media-query'\nexport { useClickOutside } from './use-click-outside'\nexport { useCopyToClipboard, type UseCopyToClipboardReturn } from './use-copy-to-clipboard'\nexport { useToggle, type UseToggleReturn } from './use-toggle'\nexport {\n usePagination,\n type UsePaginationOptions,\n type UsePaginationReturn,\n} from './use-pagination'\nexport { useForm, type UseFormErrors, type UseFormReturn } from './use-form'\nexport { useMounted } from './use-mounted'\nexport { useInterval } from './use-interval'\nexport { useTimeout } from './use-timeout'\nexport { useHotkeys, type UseHotkeysOptions } from './use-hotkeys'\nexport { useEventListener } from './use-event-listener'\n","/**\n * @description: 防抖 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 防抖值,延迟 delay 毫秒后才更新\n * @example\n * const keyword = useDebounce(inputValue, 300)\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debounced, setDebounced] = useState(value)\n\n useEffect(() => {\n const timer = setTimeout(() => setDebounced(value), delay)\n return () => clearTimeout(timer)\n }, [value, delay])\n\n return debounced\n}\n","/**\n * @description: localStorage 持久化 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback } from 'react'\n\nexport type SetLocalStorageValue<T> = (value: T | ((prev: T) => T)) => void\nexport type UseLocalStorageReturn<T> = [T, SetLocalStorageValue<T>, () => void]\n\n/**\n * 自动读写 localStorage 的 useState\n * @example\n * const [token, setToken, removeToken] = useLocalStorage('token', '')\n */\nexport function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageReturn<T> {\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (typeof window === 'undefined') return initialValue\n try {\n const item = window.localStorage.getItem(key)\n return item ? (JSON.parse(item) as T) : initialValue\n } catch {\n return initialValue\n }\n })\n\n const setValue = useCallback((value: T | ((prev: T) => T)) => {\n setStoredValue(prev => {\n const next = value instanceof Function ? value(prev) : value\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(key, JSON.stringify(next))\n }\n return next\n })\n }, [key])\n\n const removeValue = useCallback(() => {\n setStoredValue(initialValue)\n if (typeof window !== 'undefined') {\n window.localStorage.removeItem(key)\n }\n }, [key, initialValue])\n\n return [storedValue, setValue, removeValue]\n}\n","/**\n * @description: CSS media query 监听 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 监听 CSS media query 变化,返回当前是否匹配\n * @example\n * const isMobile = useMediaQuery('(max-width: 768px)')\n * const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n })\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n const mediaQueryList = window.matchMedia(query)\n setMatches(mediaQueryList.matches)\n\n const handleChange = (event: MediaQueryListEvent) => {\n setMatches(event.matches)\n }\n\n mediaQueryList.addEventListener('change', handleChange)\n return () => mediaQueryList.removeEventListener('change', handleChange)\n }, [query])\n\n return matches\n}\n","/**\n * @description: 点击元素外部触发回调的 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useEffect } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * 点击 ref 元素外部时触发 handler\n * @example\n * const ref = useRef<HTMLDivElement>(null)\n * useClickOutside(ref, () => setOpen(false))\n */\nexport function useClickOutside<T extends HTMLElement>(\n ref: RefObject<T | null>,\n handler: () => void,\n): void {\n useEffect(() => {\n const handleMouseDown = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n const handleTouchStart = (event: TouchEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n document.addEventListener('mousedown', handleMouseDown)\n document.addEventListener('touchstart', handleTouchStart)\n\n return () => {\n document.removeEventListener('mousedown', handleMouseDown)\n document.removeEventListener('touchstart', handleTouchStart)\n }\n }, [ref, handler])\n}\n","/**\n * @description: 复制文本到剪贴板的 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport interface UseCopyToClipboardReturn {\n copied: boolean\n copy: (text: string) => Promise<void>\n}\n\n/**\n * 复制文本到剪贴板,copied 状态 2 秒后自动重置\n * @example\n * const { copied, copy } = useCopyToClipboard()\n * <button onClick={() => copy('hello')}>{copied ? '已复制' : '复制'}</button>\n */\nexport function useCopyToClipboard(): UseCopyToClipboardReturn {\n const [copied, setCopied] = useState(false)\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n }\n }, [])\n\n const copy = useCallback(async (text: string) => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) {\n console.error('剪贴板 API 不可用')\n return\n }\n\n try {\n await navigator.clipboard.writeText(text)\n setCopied(true)\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n // 2 秒后自动重置状态\n timerRef.current = setTimeout(() => setCopied(false), 2000)\n } catch (error) {\n console.error('复制到剪贴板失败:', error)\n }\n }, [])\n\n return { copied, copy }\n}\n","/**\n * @description: 布尔值切换 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback } from 'react'\n\nexport type UseToggleReturn = [boolean, () => void, (value: boolean) => void]\n\n/**\n * 简单的布尔值切换,返回 [value, toggle, setValue]\n * @example\n * const [isOpen, toggleOpen, setIsOpen] = useToggle(false)\n * <button onClick={toggleOpen}>切换</button>\n */\nexport function useToggle(\n initial = false,\n): UseToggleReturn {\n const [value, setValue] = useState(initial)\n\n const toggle = useCallback(() => {\n setValue(prev => !prev)\n }, [])\n\n return [value, toggle, setValue]\n}\n","/**\n * @description: 分页状态管理 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react'\n\nexport interface UsePaginationOptions {\n /** 数据总条数 */\n total: number\n /** 每页条数,默认 10 */\n pageSize?: number\n /** 初始页码,默认 1 */\n initialPage?: number\n}\n\nexport interface UsePaginationReturn {\n /** 当前页码 (从 1 开始) */\n page: number\n /** 总页数 */\n totalPages: number\n /** 跳转到指定页 */\n setPage: (p: number) => void\n /** 下一页 */\n nextPage: () => void\n /** 上一页 */\n prevPage: () => void\n /** 是否有下一页 */\n hasNext: boolean\n /** 是否有上一页 */\n hasPrev: boolean\n /** 当前页起始索引 (0-based,用于切片) */\n startIndex: number\n /** 当前页结束索引 (exclusive,用于切片) */\n endIndex: number\n}\n\n/**\n * 分页状态管理,提供页码、边界判断和数据切片索引\n * @example\n * const { page, totalPages, nextPage, prevPage, startIndex, endIndex } = usePagination({ total: 100, pageSize: 10 })\n * const pageData = data.slice(startIndex, endIndex)\n */\nexport function usePagination({\n total,\n pageSize = 10,\n initialPage = 1,\n}: UsePaginationOptions): UsePaginationReturn {\n const totalPages = useMemo(\n () => Math.max(1, Math.ceil(total / pageSize)),\n [total, pageSize],\n )\n\n const [page, setPageState] = useState(() =>\n Math.min(Math.max(1, initialPage), Math.max(1, Math.ceil(total / pageSize))),\n )\n\n useEffect(() => {\n setPageState(prev => Math.min(prev, totalPages))\n }, [totalPages])\n\n const setPage = useCallback(\n (p: number) => {\n setPageState(Math.min(Math.max(1, p), totalPages))\n },\n [totalPages],\n )\n\n const nextPage = useCallback(() => {\n setPageState(prev => Math.min(prev + 1, totalPages))\n }, [totalPages])\n\n const prevPage = useCallback(() => {\n setPageState(prev => Math.max(prev - 1, 1))\n }, [])\n\n const startIndex = (page - 1) * pageSize\n const endIndex = Math.min(startIndex + pageSize, total)\n\n return {\n page,\n totalPages,\n setPage,\n nextPage,\n prevPage,\n hasNext: page < totalPages,\n hasPrev: page > 1,\n startIndex,\n endIndex,\n }\n}\n","/**\n * @description: 极简表单状态管理 hook,不依赖第三方库\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback, useMemo } from 'react'\n\nexport type UseFormErrors<T extends Record<string, unknown>> = Partial<Record<keyof T, string>>\n\nexport interface UseFormReturn<T extends Record<string, unknown>> {\n /** 当前表单值 */\n values: T\n /** 字段错误信息 */\n errors: UseFormErrors<T>\n /** 更新单个字段值 */\n handleChange: <K extends keyof T>(name: K, value: T[K]) => void\n /** 设置字段错误 */\n setError: (name: keyof T, message: string) => void\n /** 清除所有错误 */\n clearErrors: () => void\n /** 重置表单到初始值 */\n reset: () => void\n /** 表单是否被修改过 */\n isDirty: boolean\n}\n\n/**\n * 极简表单状态管理,提供值管理、错误处理和脏状态检测\n * @example\n * const form = useForm({ username: '', password: '' })\n * <input value={form.values.username} onChange={e => form.handleChange('username', e.target.value)} />\n * {form.errors.username && <span>{form.errors.username}</span>}\n */\nexport function useForm<T extends Record<string, unknown>>(\n initialValues: T,\n): UseFormReturn<T> {\n const [values, setValues] = useState<T>(initialValues)\n const [errors, setErrors] = useState<UseFormErrors<T>>({})\n\n const isDirty = useMemo(\n () =>\n (Object.keys(initialValues) as (keyof T)[]).some(\n key => values[key] !== initialValues[key],\n ),\n [values, initialValues],\n )\n\n const handleChange = useCallback(<K extends keyof T>(name: K, value: T[K]) => {\n setValues(prev => ({ ...prev, [name]: value }))\n // 修改字段时清除该字段的错误\n setErrors(prev => {\n if (!prev[name]) return prev\n const next = { ...prev }\n delete next[name]\n return next\n })\n }, [])\n\n const setError = useCallback((name: keyof T, message: string) => {\n setErrors(prev => ({ ...prev, [name]: message }))\n }, [])\n\n const clearErrors = useCallback(() => {\n setErrors({})\n }, [])\n\n const reset = useCallback(() => {\n setValues(initialValues)\n setErrors({})\n }, [initialValues])\n\n return { values, errors, handleChange, setError, clearErrors, reset, isDirty }\n}\n","/**\n * @description: 组件挂载状态 hook — 用于避免 SSR 环境下的 hydration 报错\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 返回组件是否已在客户端挂载完成。\n *\n * 在 SSR 场景中,服务端渲染时始终返回 false,\n * 客户端 hydration 完成后变为 true,可用于条件渲染仅客户端内容。\n *\n * @returns 组件是否已挂载\n *\n * @example\n * ```tsx\n * const isMounted = useMounted()\n *\n * if (!isMounted) return null // 服务端不渲染\n * return <ClientOnlyComponent />\n * ```\n */\nexport function useMounted(): boolean {\n const [mounted, setMounted] = useState(false)\n\n useEffect(() => {\n setMounted(true)\n }, [])\n\n return mounted\n}\n","/**\n * @description: 定时间隔 hook — delay 为 null 时暂停,支持动态调整间隔\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 以指定间隔重复调用回调函数。\n *\n * 当 delay 为 null 时定时器自动暂停,可动态修改 delay 或 callback。\n * 参考 Dan Abramov 的 useInterval 实现,使用 ref 保持 callback 最新引用。\n *\n * @param callback 每次触发时调用的函数\n * @param delay 间隔毫秒数;传入 null 则暂停定时器\n *\n * @example\n * ```tsx\n * const [count, setCount] = useState(0)\n * const [running, setRunning] = useState(true)\n *\n * useInterval(() => setCount(c => c + 1), running ? 1000 : null)\n * ```\n */\nexport function useInterval(callback: () => void, delay: number | null): void {\n const savedCallback = useRef<() => void>(callback)\n\n // 每次渲染都更新 ref,确保 callback 始终最新\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n if (delay === null) return\n const id = setInterval(() => savedCallback.current(), delay)\n return () => clearInterval(id)\n }, [delay])\n}\n","/**\n * @description: 延迟执行 hook — delay 为 null 时暂停,组件卸载自动清理\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 在指定延迟后执行一次回调函数。\n *\n * 当 delay 为 null 时定时器不启动(暂停模式)。\n * 每次 delay 变化都会重置定时器;组件卸载时自动清理。\n *\n * @param callback 延迟结束后调用的函数\n * @param delay 延迟毫秒数;传入 null 则不执行\n *\n * @example\n * ```tsx\n * const [show, setShow] = useState(true)\n *\n * // 3 秒后自动隐藏提示\n * useTimeout(() => setShow(false), show ? 3000 : null)\n * ```\n */\nexport function useTimeout(callback: () => void, delay: number | null): void {\n const savedCallback = useRef<() => void>(callback)\n\n // 每次渲染都更新 ref,确保 callback 始终最新\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n if (delay === null) return\n const id = setTimeout(() => savedCallback.current(), delay)\n return () => clearTimeout(id)\n }, [delay])\n}\n","/**\n * @description: 快捷键绑定 hook — 支持 \"ctrl+k\"、\"cmd+s\"、\"escape\" 等组合键\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\nexport interface UseHotkeysOptions {\n /** 是否阻止默认浏览器行为,默认 false */\n preventDefault?: boolean\n /** 监听目标,默认 window */\n target?: HTMLElement | null\n}\n\n/**\n * 解析快捷键字符串,返回规范化的修饰键 + 主键结构。\n * 支持 ctrl/cmd/alt/shift + 任意按键,键名不区分大小写。\n * \"cmd\" 在 Mac 映射为 Meta,Windows 同样可响应 Meta 键。\n */\nfunction parseHotkey(keys: string): {\n ctrl: boolean\n meta: boolean\n alt: boolean\n shift: boolean\n key: string\n} {\n const parts = keys.toLowerCase().split('+').map((s) => s.trim())\n const ctrl = parts.includes('ctrl')\n const meta = parts.includes('cmd') || parts.includes('meta')\n const alt = parts.includes('alt')\n const shift = parts.includes('shift')\n const key = parts.find(\n (p) => !['ctrl', 'cmd', 'meta', 'alt', 'shift'].includes(p),\n ) ?? ''\n\n return { ctrl, meta, alt, shift, key }\n}\n\n/**\n * 将快捷键字符串绑定到回调函数。\n *\n * 支持修饰键组合:ctrl、cmd(Meta)、alt、shift,加主键(不区分大小写)。\n * 组件卸载时自动解绑。\n *\n * @param keys 快捷键字符串,如 \"ctrl+k\"、\"cmd+s\"、\"escape\"\n * @param callback 触发时调用的函数\n * @param options 额外配置项\n *\n * @example\n * ```tsx\n * // 全局搜索快捷键\n * useHotkeys('ctrl+k', () => setSearchOpen(true), { preventDefault: true })\n *\n * // ESC 关闭弹窗\n * useHotkeys('escape', () => setOpen(false))\n *\n * // 仅在特定元素内响应\n * useHotkeys('ctrl+enter', handleSubmit, { target: formRef.current })\n * ```\n */\nexport function useHotkeys(\n keys: string,\n callback: (event: KeyboardEvent) => void,\n options: UseHotkeysOptions = {},\n): void {\n const { preventDefault = false, target = null } = options\n const savedCallback = useRef<(event: KeyboardEvent) => void>(callback)\n\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n const parsed = parseHotkey(keys)\n const eventTarget: EventTarget = target ?? window\n\n function handler(event: KeyboardEvent): void {\n const eventKey = event.key.toLowerCase()\n const match =\n eventKey === parsed.key &&\n event.ctrlKey === parsed.ctrl &&\n event.metaKey === parsed.meta &&\n event.altKey === parsed.alt &&\n event.shiftKey === parsed.shift\n\n if (match) {\n if (preventDefault) event.preventDefault()\n savedCallback.current(event)\n }\n }\n\n eventTarget.addEventListener('keydown', handler as EventListener)\n return () => {\n eventTarget.removeEventListener('keydown', handler as EventListener)\n }\n }, [keys, preventDefault, target])\n}\n","/**\n * @description: 事件监听 hook — 自动管理事件绑定与清理,支持 Window/Document/HTMLElement\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 在指定目标上绑定事件监听器,组件卸载时自动清理。\n *\n * 支持三种目标类型:\n * - 不传 target → 默认监听 window\n * - 传入 HTMLElement → 监听该元素\n * - 传入 null → 跳过绑定(适合 ref 未初始化的场景)\n *\n * @typeParam K WindowEventMap 中的事件名称\n * @param event 事件名称,如 \"scroll\"、\"resize\"、\"click\"\n * @param handler 事件处理函数\n * @param target 监听目标;默认 window;传 null 跳过绑定\n *\n * @example\n * ```tsx\n * // 监听全局滚动\n * useEventListener('scroll', () => setScrollY(window.scrollY))\n *\n * // 监听特定元素的点击\n * const divRef = useRef<HTMLDivElement>(null)\n * useEventListener('click', handleClick, divRef.current)\n *\n * // 传入 null 跳过(ref 尚未挂载时)\n * useEventListener('mouseenter', onEnter, ref.current)\n * ```\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n event: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: EventTarget | null,\n): void {\n const savedHandler = useRef<(event: WindowEventMap[K]) => void>(handler)\n\n useEffect(() => {\n savedHandler.current = handler\n }, [handler])\n\n useEffect(() => {\n // target 显式传入 null 时跳过绑定\n if (target === null) return\n const eventTarget: EventTarget = target ?? window\n\n function listener(event: Event): void {\n savedHandler.current(event as WindowEventMap[K])\n }\n\n eventTarget.addEventListener(event, listener)\n return () => {\n eventTarget.removeEventListener(event, listener)\n }\n }, [event, target])\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","event"]}
1
+ {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-debounce.ts","../../src/hooks/use-local-storage.ts","../../src/hooks/use-media-query.ts","../../src/hooks/use-click-outside.ts","../../src/hooks/use-copy-to-clipboard.ts","../../src/hooks/use-toggle.ts","../../src/hooks/use-pagination.ts","../../src/hooks/use-form.ts","../../src/hooks/use-mounted.ts","../../src/hooks/use-interval.ts","../../src/hooks/use-timeout.ts","../../src/hooks/use-hotkeys.ts","../../src/hooks/use-event-listener.ts"],"sourcesContent":["\"use client\"\n\n/**\n * @description: @ug666/ui-react/hooks 子入口 — 所有 Hooks 统一导出\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nexport { useDebounce } from './use-debounce'\nexport {\n useLocalStorage,\n type UseLocalStorageReturn,\n type SetLocalStorageValue,\n} from './use-local-storage'\nexport { useMediaQuery } from './use-media-query'\nexport { useClickOutside } from './use-click-outside'\nexport { useCopyToClipboard, type UseCopyToClipboardReturn } from './use-copy-to-clipboard'\nexport { useToggle, type UseToggleReturn } from './use-toggle'\nexport {\n usePagination,\n type UsePaginationOptions,\n type UsePaginationReturn,\n} from './use-pagination'\nexport { useForm, type UseFormErrors, type UseFormReturn } from './use-form'\nexport { useMounted } from './use-mounted'\nexport { useInterval } from './use-interval'\nexport { useTimeout } from './use-timeout'\nexport { useHotkeys, type UseHotkeysOptions } from './use-hotkeys'\nexport { useEventListener } from './use-event-listener'\n","/**\n * @description: 防抖 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 防抖值,延迟 delay 毫秒后才更新\n * @example\n * const keyword = useDebounce(inputValue, 300)\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debounced, setDebounced] = useState(value)\n\n useEffect(() => {\n const timer = setTimeout(() => setDebounced(value), delay)\n return () => clearTimeout(timer)\n }, [value, delay])\n\n return debounced\n}\n","/**\n * @description: localStorage 持久化 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback } from 'react'\n\nexport type SetLocalStorageValue<T> = (value: T | ((prev: T) => T)) => void\nexport type UseLocalStorageReturn<T> = [T, SetLocalStorageValue<T>, () => void]\n\n/**\n * 自动读写 localStorage 的 useState\n * @example\n * const [token, setToken, removeToken] = useLocalStorage('token', '')\n */\nexport function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageReturn<T> {\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (typeof window === 'undefined') return initialValue\n try {\n const item = window.localStorage.getItem(key)\n return item ? (JSON.parse(item) as T) : initialValue\n } catch {\n return initialValue\n }\n })\n\n const setValue = useCallback((value: T | ((prev: T) => T)) => {\n setStoredValue(prev => {\n const next = value instanceof Function ? value(prev) : value\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(key, JSON.stringify(next))\n }\n return next\n })\n }, [key])\n\n const removeValue = useCallback(() => {\n setStoredValue(initialValue)\n if (typeof window !== 'undefined') {\n window.localStorage.removeItem(key)\n }\n }, [key, initialValue])\n\n return [storedValue, setValue, removeValue]\n}\n","/**\n * @description: CSS media query 监听 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 监听 CSS media query 变化,返回当前是否匹配\n * @example\n * const isMobile = useMediaQuery('(max-width: 768px)')\n * const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n })\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n const mediaQueryList = window.matchMedia(query)\n setMatches(mediaQueryList.matches)\n\n const handleChange = (event: MediaQueryListEvent) => {\n setMatches(event.matches)\n }\n\n mediaQueryList.addEventListener('change', handleChange)\n return () => mediaQueryList.removeEventListener('change', handleChange)\n }, [query])\n\n return matches\n}\n","/**\n * @description: 点击元素外部触发回调的 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useEffect } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * 点击 ref 元素外部时触发 handler\n * @example\n * const ref = useRef<HTMLDivElement>(null)\n * useClickOutside(ref, () => setOpen(false))\n */\nexport function useClickOutside<T extends HTMLElement>(\n ref: RefObject<T | null>,\n handler: () => void,\n): void {\n useEffect(() => {\n const handleMouseDown = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n const handleTouchStart = (event: TouchEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n document.addEventListener('mousedown', handleMouseDown)\n document.addEventListener('touchstart', handleTouchStart)\n\n return () => {\n document.removeEventListener('mousedown', handleMouseDown)\n document.removeEventListener('touchstart', handleTouchStart)\n }\n }, [ref, handler])\n}\n","/**\n * @description: 复制文本到剪贴板的 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport interface UseCopyToClipboardReturn {\n copied: boolean\n copy: (text: string) => Promise<void>\n}\n\n/**\n * 复制文本到剪贴板,copied 状态 2 秒后自动重置\n * @example\n * const { copied, copy } = useCopyToClipboard()\n * <button onClick={() => copy('hello')}>{copied ? '已复制' : '复制'}</button>\n */\nexport function useCopyToClipboard(): UseCopyToClipboardReturn {\n const [copied, setCopied] = useState(false)\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n }\n }, [])\n\n const copy = useCallback(async (text: string) => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) {\n console.error('剪贴板 API 不可用')\n return\n }\n\n try {\n await navigator.clipboard.writeText(text)\n setCopied(true)\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n // 2 秒后自动重置状态\n timerRef.current = setTimeout(() => setCopied(false), 2000)\n } catch (error) {\n console.error('复制到剪贴板失败:', error)\n }\n }, [])\n\n return { copied, copy }\n}\n","/**\n * @description: 布尔值切换 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback } from 'react'\n\nexport type UseToggleReturn = [boolean, () => void, (value: boolean) => void]\n\n/**\n * 简单的布尔值切换,返回 [value, toggle, setValue]\n * @example\n * const [isOpen, toggleOpen, setIsOpen] = useToggle(false)\n * <button onClick={toggleOpen}>切换</button>\n */\nexport function useToggle(\n initial = false,\n): UseToggleReturn {\n const [value, setValue] = useState(initial)\n\n const toggle = useCallback(() => {\n setValue(prev => !prev)\n }, [])\n\n return [value, toggle, setValue]\n}\n","/**\n * @description: 分页状态管理 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react'\n\nexport interface UsePaginationOptions {\n /** 数据总条数 */\n total: number\n /** 每页条数,默认 10 */\n pageSize?: number\n /** 初始页码,默认 1 */\n initialPage?: number\n}\n\nexport interface UsePaginationReturn {\n /** 当前页码 (从 1 开始) */\n page: number\n /** 总页数 */\n totalPages: number\n /** 跳转到指定页 */\n setPage: (p: number) => void\n /** 下一页 */\n nextPage: () => void\n /** 上一页 */\n prevPage: () => void\n /** 是否有下一页 */\n hasNext: boolean\n /** 是否有上一页 */\n hasPrev: boolean\n /** 当前页起始索引 (0-based,用于切片) */\n startIndex: number\n /** 当前页结束索引 (exclusive,用于切片) */\n endIndex: number\n}\n\n/**\n * 分页状态管理,提供页码、边界判断和数据切片索引\n * @example\n * const { page, totalPages, nextPage, prevPage, startIndex, endIndex } = usePagination({ total: 100, pageSize: 10 })\n * const pageData = data.slice(startIndex, endIndex)\n */\nexport function usePagination({\n total,\n pageSize = 10,\n initialPage = 1,\n}: UsePaginationOptions): UsePaginationReturn {\n const totalPages = useMemo(\n () => Math.max(1, Math.ceil(total / pageSize)),\n [total, pageSize],\n )\n\n const [page, setPageState] = useState(() =>\n Math.min(Math.max(1, initialPage), Math.max(1, Math.ceil(total / pageSize))),\n )\n\n useEffect(() => {\n setPageState(prev => Math.min(prev, totalPages))\n }, [totalPages])\n\n const setPage = useCallback(\n (p: number) => {\n setPageState(Math.min(Math.max(1, p), totalPages))\n },\n [totalPages],\n )\n\n const nextPage = useCallback(() => {\n setPageState(prev => Math.min(prev + 1, totalPages))\n }, [totalPages])\n\n const prevPage = useCallback(() => {\n setPageState(prev => Math.max(prev - 1, 1))\n }, [])\n\n const startIndex = (page - 1) * pageSize\n const endIndex = Math.min(startIndex + pageSize, total)\n\n return {\n page,\n totalPages,\n setPage,\n nextPage,\n prevPage,\n hasNext: page < totalPages,\n hasPrev: page > 1,\n startIndex,\n endIndex,\n }\n}\n","/**\n * @description: 极简表单状态管理 hook,不依赖第三方库\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback, useMemo } from 'react'\n\nexport type UseFormErrors<T extends Record<string, unknown>> = Partial<Record<keyof T, string>>\n\nexport interface UseFormReturn<T extends Record<string, unknown>> {\n /** 当前表单值 */\n values: T\n /** 字段错误信息 */\n errors: UseFormErrors<T>\n /** 更新单个字段值 */\n handleChange: <K extends keyof T>(name: K, value: T[K]) => void\n /** 设置字段错误 */\n setError: (name: keyof T, message: string) => void\n /** 清除所有错误 */\n clearErrors: () => void\n /** 重置表单到初始值 */\n reset: () => void\n /** 表单是否被修改过 */\n isDirty: boolean\n}\n\n/**\n * 极简表单状态管理,提供值管理、错误处理和脏状态检测\n * @example\n * const form = useForm({ username: '', password: '' })\n * <input value={form.values.username} onChange={e => form.handleChange('username', e.target.value)} />\n * {form.errors.username && <span>{form.errors.username}</span>}\n */\nexport function useForm<T extends Record<string, unknown>>(\n initialValues: T,\n): UseFormReturn<T> {\n const [values, setValues] = useState<T>(initialValues)\n const [errors, setErrors] = useState<UseFormErrors<T>>({})\n\n const isDirty = useMemo(\n () =>\n (Object.keys(initialValues) as (keyof T)[]).some(\n key => values[key] !== initialValues[key],\n ),\n [values, initialValues],\n )\n\n const handleChange = useCallback(<K extends keyof T>(name: K, value: T[K]) => {\n setValues(prev => ({ ...prev, [name]: value }))\n // 修改字段时清除该字段的错误\n setErrors(prev => {\n if (!prev[name]) return prev\n const next = { ...prev }\n delete next[name]\n return next\n })\n }, [])\n\n const setError = useCallback((name: keyof T, message: string) => {\n setErrors(prev => ({ ...prev, [name]: message }))\n }, [])\n\n const clearErrors = useCallback(() => {\n setErrors({})\n }, [])\n\n const reset = useCallback(() => {\n setValues(initialValues)\n setErrors({})\n }, [initialValues])\n\n return { values, errors, handleChange, setError, clearErrors, reset, isDirty }\n}\n","/**\n * @description: 组件挂载状态 hook — 用于避免 SSR 环境下的 hydration 报错\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 返回组件是否已在客户端挂载完成。\n *\n * 在 SSR 场景中,服务端渲染时始终返回 false,\n * 客户端 hydration 完成后变为 true,可用于条件渲染仅客户端内容。\n *\n * @returns 组件是否已挂载\n *\n * @example\n * ```tsx\n * const isMounted = useMounted()\n *\n * if (!isMounted) return null // 服务端不渲染\n * return <ClientOnlyComponent />\n * ```\n */\nexport function useMounted(): boolean {\n const [mounted, setMounted] = useState(false)\n\n useEffect(() => {\n setMounted(true)\n }, [])\n\n return mounted\n}\n","/**\n * @description: 定时间隔 hook — delay 为 null 时暂停,支持动态调整间隔\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 以指定间隔重复调用回调函数。\n *\n * 当 delay 为 null 时定时器自动暂停,可动态修改 delay 或 callback。\n * 参考 Dan Abramov 的 useInterval 实现,使用 ref 保持 callback 最新引用。\n *\n * @param callback 每次触发时调用的函数\n * @param delay 间隔毫秒数;传入 null 则暂停定时器\n *\n * @example\n * ```tsx\n * const [count, setCount] = useState(0)\n * const [running, setRunning] = useState(true)\n *\n * useInterval(() => setCount(c => c + 1), running ? 1000 : null)\n * ```\n */\nexport function useInterval(callback: () => void, delay: number | null): void {\n const savedCallback = useRef<() => void>(callback)\n\n // 每次渲染都更新 ref,确保 callback 始终最新\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n if (delay === null) return\n const id = setInterval(() => savedCallback.current(), delay)\n return () => clearInterval(id)\n }, [delay])\n}\n","/**\n * @description: 延迟执行 hook — delay 为 null 时暂停,组件卸载自动清理\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 在指定延迟后执行一次回调函数。\n *\n * 当 delay 为 null 时定时器不启动(暂停模式)。\n * 每次 delay 变化都会重置定时器;组件卸载时自动清理。\n *\n * @param callback 延迟结束后调用的函数\n * @param delay 延迟毫秒数;传入 null 则不执行\n *\n * @example\n * ```tsx\n * const [show, setShow] = useState(true)\n *\n * // 3 秒后自动隐藏提示\n * useTimeout(() => setShow(false), show ? 3000 : null)\n * ```\n */\nexport function useTimeout(callback: () => void, delay: number | null): void {\n const savedCallback = useRef<() => void>(callback)\n\n // 每次渲染都更新 ref,确保 callback 始终最新\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n if (delay === null) return\n const id = setTimeout(() => savedCallback.current(), delay)\n return () => clearTimeout(id)\n }, [delay])\n}\n","/**\n * @description: 快捷键绑定 hook — 支持 \"ctrl+k\"、\"cmd+s\"、\"escape\" 等组合键\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\nexport interface UseHotkeysOptions {\n /** 是否阻止默认浏览器行为,默认 false */\n preventDefault?: boolean\n /** 监听目标,默认 window */\n target?: HTMLElement | null\n}\n\n/**\n * 解析快捷键字符串,返回规范化的修饰键 + 主键结构。\n * 支持 ctrl/cmd/alt/shift + 任意按键,键名不区分大小写。\n * \"cmd\" 在 Mac 映射为 Meta,Windows 同样可响应 Meta 键。\n */\nfunction parseHotkey(keys: string): {\n ctrl: boolean\n meta: boolean\n alt: boolean\n shift: boolean\n key: string\n} {\n const parts = keys.toLowerCase().split('+').map((s) => s.trim())\n const ctrl = parts.includes('ctrl')\n const meta = parts.includes('cmd') || parts.includes('meta')\n const alt = parts.includes('alt')\n const shift = parts.includes('shift')\n const key = parts.find(\n (p) => !['ctrl', 'cmd', 'meta', 'alt', 'shift'].includes(p),\n ) ?? ''\n\n return { ctrl, meta, alt, shift, key }\n}\n\n/**\n * 将快捷键字符串绑定到回调函数。\n *\n * 支持修饰键组合:ctrl、cmd(Meta)、alt、shift,加主键(不区分大小写)。\n * 组件卸载时自动解绑。\n *\n * @param keys 快捷键字符串,如 \"ctrl+k\"、\"cmd+s\"、\"escape\"\n * @param callback 触发时调用的函数\n * @param options 额外配置项\n *\n * @example\n * ```tsx\n * // 全局搜索快捷键\n * useHotkeys('ctrl+k', () => setSearchOpen(true), { preventDefault: true })\n *\n * // ESC 关闭弹窗\n * useHotkeys('escape', () => setOpen(false))\n *\n * // 仅在特定元素内响应\n * useHotkeys('ctrl+enter', handleSubmit, { target: formRef.current })\n * ```\n */\nexport function useHotkeys(\n keys: string,\n callback: (event: KeyboardEvent) => void,\n options: UseHotkeysOptions = {},\n): void {\n const { preventDefault = false, target = null } = options\n const savedCallback = useRef<(event: KeyboardEvent) => void>(callback)\n\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n const parsed = parseHotkey(keys)\n const eventTarget: EventTarget = target ?? window\n\n function handler(event: KeyboardEvent): void {\n const eventKey = event.key.toLowerCase()\n const match =\n eventKey === parsed.key &&\n event.ctrlKey === parsed.ctrl &&\n event.metaKey === parsed.meta &&\n event.altKey === parsed.alt &&\n event.shiftKey === parsed.shift\n\n if (match) {\n if (preventDefault) event.preventDefault()\n savedCallback.current(event)\n }\n }\n\n eventTarget.addEventListener('keydown', handler as EventListener)\n return () => {\n eventTarget.removeEventListener('keydown', handler as EventListener)\n }\n }, [keys, preventDefault, target])\n}\n","/**\n * @description: 事件监听 hook — 自动管理事件绑定与清理,支持 Window/Document/HTMLElement\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 在指定目标上绑定事件监听器,组件卸载时自动清理。\n *\n * 支持三种目标类型:\n * - 不传 target → 默认监听 window\n * - 传入 HTMLElement → 监听该元素\n * - 传入 null → 跳过绑定(适合 ref 未初始化的场景)\n *\n * @typeParam K WindowEventMap 中的事件名称\n * @param event 事件名称,如 \"scroll\"、\"resize\"、\"click\"\n * @param handler 事件处理函数\n * @param target 监听目标;默认 window;传 null 跳过绑定\n *\n * @example\n * ```tsx\n * // 监听全局滚动\n * useEventListener('scroll', () => setScrollY(window.scrollY))\n *\n * // 监听特定元素的点击\n * const divRef = useRef<HTMLDivElement>(null)\n * useEventListener('click', handleClick, divRef.current)\n *\n * // 传入 null 跳过(ref 尚未挂载时)\n * useEventListener('mouseenter', onEnter, ref.current)\n * ```\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n event: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: EventTarget | null,\n): void {\n const savedHandler = useRef<(event: WindowEventMap[K]) => void>(handler)\n\n useEffect(() => {\n savedHandler.current = handler\n }, [handler])\n\n useEffect(() => {\n // target 显式传入 null 时跳过绑定\n if (target === null) return\n const eventTarget: EventTarget = target ?? window\n\n function listener(event: Event): void {\n savedHandler.current(event as WindowEventMap[K])\n }\n\n eventTarget.addEventListener(event, listener)\n return () => {\n eventTarget.removeEventListener(event, listener)\n }\n }, [event, target])\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","event"]}
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import {
2
3
  useClickOutside,
3
4
  useCopyToClipboard,