@musecat/functionkit 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.
Files changed (157) hide show
  1. package/.agents/references/components/ScrolltoTop.md +36 -0
  2. package/.agents/references/components/SwitchCase.md +47 -0
  3. package/.agents/references/components/ViewportPortal.md +34 -0
  4. package/.agents/references/cookie/cookie.md +47 -0
  5. package/.agents/references/datetime/dateTime.client.md +42 -0
  6. package/.agents/references/datetime/dateTime.server.md +42 -0
  7. package/.agents/references/datetime/dateTime.shared.md +86 -0
  8. package/.agents/references/hooks/useAvoidKeyboard.md +29 -0
  9. package/.agents/references/hooks/useCheckInvisible.md +36 -0
  10. package/.agents/references/hooks/useCheckScroll.md +36 -0
  11. package/.agents/references/hooks/useClientDateTime.md +39 -0
  12. package/.agents/references/hooks/useDebounce.md +46 -0
  13. package/.agents/references/hooks/useDebouncedCallback.md +34 -0
  14. package/.agents/references/hooks/useDoubleClick.md +40 -0
  15. package/.agents/references/hooks/useGeolocation.md +37 -0
  16. package/.agents/references/hooks/useHasMounted.md +27 -0
  17. package/.agents/references/hooks/useIntersectionObserver.md +37 -0
  18. package/.agents/references/hooks/useInterval.md +40 -0
  19. package/.agents/references/hooks/useKeyboardHeight.md +33 -0
  20. package/.agents/references/hooks/useKeyboardListNavigation.md +58 -0
  21. package/.agents/references/hooks/useLongPress.md +48 -0
  22. package/.agents/references/hooks/usePreservedCallback.md +33 -0
  23. package/.agents/references/hooks/usePreservedReference.md +34 -0
  24. package/.agents/references/hooks/useRefEffect.md +35 -0
  25. package/.agents/references/hooks/useRelativeDateTime.md +35 -0
  26. package/.agents/references/hooks/useTimeout.md +42 -0
  27. package/.agents/references/hooks/useToggleState.md +39 -0
  28. package/.agents/references/hooks/useViewportHeight.md +26 -0
  29. package/.agents/references/hooks/useViewportMatch.md +32 -0
  30. package/.agents/references/utils/browserStorage.md +41 -0
  31. package/.agents/references/utils/buildContext.md +47 -0
  32. package/.agents/references/utils/clipboardShare.md +59 -0
  33. package/.agents/references/utils/floatingMotion.md +67 -0
  34. package/.agents/references/utils/getDeviceInfo.md +43 -0
  35. package/.agents/references/utils/isEditableKeyboardTarget.md +35 -0
  36. package/.agents/references/utils/mergeRefs.md +29 -0
  37. package/.agents/references/utils/seen.md +37 -0
  38. package/.agents/references/utils/subscribeKeyboardHeight.md +35 -0
  39. package/AGENTS.md +103 -396
  40. package/LICENSE +9 -0
  41. package/README.md +38 -0
  42. package/index.ts +97 -102
  43. package/package.json +27 -13
  44. package/{components → packages/components}/ScrolltoTop.tsx +3 -3
  45. package/{components → packages/components}/SwitchCase.tsx +7 -7
  46. package/packages/components/ViewportPortal.tsx +40 -0
  47. package/packages/cookie/cookie.shared.ts +138 -0
  48. package/packages/datetime/dateTime.client.ts +108 -0
  49. package/packages/datetime/dateTime.server.ts +108 -0
  50. package/packages/datetime/dateTime.shared.ts +358 -0
  51. package/packages/hooks/useAvoidKeyboard.ts +28 -0
  52. package/packages/hooks/useCheckInvisible.ts +25 -0
  53. package/packages/hooks/useCheckScroll.ts +19 -0
  54. package/packages/hooks/useClientDateTime.ts +49 -0
  55. package/packages/hooks/useDebounce.ts +118 -0
  56. package/packages/hooks/useDebouncedCallback.ts +69 -0
  57. package/packages/hooks/useDoubleClick.ts +53 -0
  58. package/packages/hooks/useGeolocation.ts +146 -0
  59. package/packages/hooks/useHasMounted.ts +13 -0
  60. package/packages/hooks/useIntersectionObserver.ts +34 -0
  61. package/packages/hooks/useInterval.ts +56 -0
  62. package/packages/hooks/useKeyboardHeight.ts +18 -0
  63. package/packages/hooks/useKeyboardListNavigation.ts +170 -0
  64. package/packages/hooks/useLongPress.ts +120 -0
  65. package/packages/hooks/usePreservedCallback.ts +21 -0
  66. package/packages/hooks/usePreservedReference.ts +21 -0
  67. package/packages/hooks/useRefEffect.ts +35 -0
  68. package/packages/hooks/useRelativeDateTime.ts +54 -0
  69. package/packages/hooks/useTimeout.ts +56 -0
  70. package/packages/hooks/useToggleState.ts +13 -0
  71. package/packages/hooks/useViewportHeight.ts +23 -0
  72. package/packages/hooks/useViewportMatch.ts +27 -0
  73. package/packages/utils/browserStorage.ts +59 -0
  74. package/packages/utils/buildContext.tsx +19 -0
  75. package/packages/utils/checkDevice.ts +74 -0
  76. package/packages/utils/clipboardShare.tsx +47 -0
  77. package/{utils → packages/utils}/clipboardShare.types.ts +7 -7
  78. package/packages/utils/floatingMotion.ts +99 -0
  79. package/packages/utils/keyboardTarget.ts +13 -0
  80. package/packages/utils/mergeRefs.ts +15 -0
  81. package/packages/utils/seen.ts +29 -0
  82. package/packages/utils/subscribeKeyboardHeight.ts +54 -0
  83. package/tests/components/ScrolltoTop.test.tsx +15 -0
  84. package/tests/components/SwitchCase.test.tsx +34 -0
  85. package/tests/components/ViewportPortal.test.tsx +42 -0
  86. package/tests/cookie/cookie.test.ts +156 -0
  87. package/tests/datetime/datetime.test.ts +250 -0
  88. package/tests/hooks/useAvoidKeyboard.test.ts +12 -0
  89. package/tests/hooks/useCheckInvisible.test.ts +28 -0
  90. package/tests/hooks/useCheckScroll.test.ts +23 -0
  91. package/tests/hooks/useClientDateTime.test.ts +14 -0
  92. package/tests/hooks/useDebounce.test.ts +71 -0
  93. package/tests/hooks/useDebouncedCallback.test.ts +34 -0
  94. package/tests/hooks/useDoubleClick.test.ts +31 -0
  95. package/tests/hooks/useGeolocation.test.ts +50 -0
  96. package/tests/hooks/useHasMounted.test.ts +10 -0
  97. package/tests/hooks/useIntersectionObserver.test.ts +13 -0
  98. package/tests/hooks/useInterval.test.ts +31 -0
  99. package/tests/hooks/useKeyboardHeight.test.ts +10 -0
  100. package/tests/hooks/useKeyboardListNavigation.test.ts +82 -0
  101. package/tests/hooks/useLongPress.test.ts +52 -0
  102. package/tests/hooks/usePreservedCallback.test.ts +35 -0
  103. package/tests/hooks/usePreservedReference.test.ts +35 -0
  104. package/tests/hooks/useRefEffect.test.ts +30 -0
  105. package/tests/hooks/useRelativeDateTime.test.ts +19 -0
  106. package/tests/hooks/useTimeout.test.ts +34 -0
  107. package/tests/hooks/useToggleState.test.ts +35 -0
  108. package/tests/hooks/useViewportHeight.test.ts +10 -0
  109. package/tests/hooks/useViewportMatch.test.ts +35 -0
  110. package/tests/setup.ts +9 -0
  111. package/tests/utils/browserStorage.test.ts +54 -0
  112. package/tests/utils/buildContext.test.tsx +27 -0
  113. package/tests/utils/checkDevice.test.ts +84 -0
  114. package/tests/utils/clipboardShare.test.ts +78 -0
  115. package/tests/utils/floatingMotion.test.ts +84 -0
  116. package/tests/utils/keyboardTarget.test.ts +44 -0
  117. package/tests/utils/mergeRefs.test.ts +18 -0
  118. package/tests/utils/seen.test.ts +31 -0
  119. package/tests/utils/subscribeKeyboardHeight.test.ts +50 -0
  120. package/tsconfig.json +16 -16
  121. package/vitest.config.ts +16 -0
  122. package/components/ViewportPortal.tsx +0 -40
  123. package/cookie/cookie.shared.ts +0 -142
  124. package/datetime/dateTime.client.ts +0 -108
  125. package/datetime/dateTime.server.ts +0 -108
  126. package/datetime/dateTime.shared.ts +0 -358
  127. package/hooks/useAvoidKeyboard.ts +0 -28
  128. package/hooks/useCheckInvisible.ts +0 -25
  129. package/hooks/useCheckScroll.ts +0 -19
  130. package/hooks/useClientDateTime.ts +0 -49
  131. package/hooks/useDebounce.ts +0 -118
  132. package/hooks/useDebouncedCallback.ts +0 -69
  133. package/hooks/useDoubleClick.ts +0 -53
  134. package/hooks/useGeolocation.ts +0 -146
  135. package/hooks/useHasMounted.ts +0 -13
  136. package/hooks/useIntersectionObserver.ts +0 -34
  137. package/hooks/useInterval.ts +0 -55
  138. package/hooks/useKeyboardHeight.ts +0 -18
  139. package/hooks/useKeyboardListNavigation.ts +0 -170
  140. package/hooks/useLongPress.ts +0 -120
  141. package/hooks/usePreservedCallback.ts +0 -21
  142. package/hooks/usePreservedReference.ts +0 -21
  143. package/hooks/useRefEffect.ts +0 -35
  144. package/hooks/useRelativeDateTime.ts +0 -54
  145. package/hooks/useTimeout.ts +0 -56
  146. package/hooks/useToggleState.ts +0 -13
  147. package/hooks/useViewportHeight.ts +0 -23
  148. package/hooks/useViewportMatch.ts +0 -33
  149. package/utils/browserStorage.ts +0 -59
  150. package/utils/buildContext.tsx +0 -19
  151. package/utils/checkDevice.ts +0 -74
  152. package/utils/clipboardShare.tsx +0 -47
  153. package/utils/floatingMotion.ts +0 -99
  154. package/utils/keyboardTarget.ts +0 -13
  155. package/utils/mergeRefs.ts +0 -15
  156. package/utils/seen.ts +0 -29
  157. package/utils/subscribeKeyboardHeight.ts +0 -54
@@ -0,0 +1,36 @@
1
+ # ScrolltoTop
2
+
3
+ ## Purpose
4
+
5
+ Scrolls the window to the top (`0, 0`) when the component mounts. Typically placed at the top of a page layout to reset scroll position on navigation.
6
+
7
+ ## Usage Logic
8
+
9
+ Calls `window.scrollTo(0, 0)` inside a `useEffect` with empty dependencies. No state, no props.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function ScrolltoTop(): null;
15
+ ```
16
+
17
+ Renders nothing — returns `null`.
18
+
19
+ ## Example Code
20
+
21
+ ```tsx
22
+ import { ScrolltoTop } from "@musecat/functionkit";
23
+
24
+ export default function Page() {
25
+ return (
26
+ <>
27
+ <ScrolltoTop />
28
+ <PageContent />
29
+ </>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## Note
35
+
36
+ Not exported from the barrel. Import via component subpath. Client-only (`"use client"`).
@@ -0,0 +1,47 @@
1
+ # SwitchCase
2
+
3
+ ## Purpose
4
+
5
+ A declarative `switch`/`case` component for JSX. Renders the matching `ReactNode` based on a value, with an optional fallback (`otherwise`). Eliminates messy ternary chains and `&&` conditions.
6
+
7
+ ## Usage Logic
8
+
9
+ Takes a `value` and a `cases` record. Iterates over the keys of `cases` and returns the first match using `Object.hasOwn`. If no key matches, renders `otherwise` (or `null`).
10
+
11
+ SSR-safe — pure function with no `"use client"`.
12
+
13
+ ## Type Signature
14
+
15
+ ```ts
16
+ interface SwitchCaseProps<T extends string | number> {
17
+ value: T;
18
+ cases: Partial<Record<T, ReactNode>>;
19
+ otherwise?: ReactNode;
20
+ }
21
+
22
+ function SwitchCase<T extends string | number>(props: SwitchCaseProps<T>): ReactNode;
23
+ ```
24
+
25
+ ## Example Code
26
+
27
+ ```tsx
28
+ import { SwitchCase } from "@musecat/functionkit";
29
+
30
+ function StatusBadge({ status }: { status: "loading" | "success" | "error" }) {
31
+ return (
32
+ <SwitchCase
33
+ value={status}
34
+ cases={{
35
+ loading: <Spinner />,
36
+ success: <CheckIcon />,
37
+ error: <ErrorIcon />,
38
+ }}
39
+ otherwise={<Fallback />}
40
+ />
41
+ );
42
+ }
43
+ ```
44
+
45
+ ## Note
46
+
47
+ Not exported from the barrel. Import via component subpath.
@@ -0,0 +1,34 @@
1
+ # ViewportPortal
2
+
3
+ ## Purpose
4
+
5
+ Renders children into a fixed-position portal root (`#viewport-portal-root`) using `createPortal`. Designed for overlays, modals, toasts, and tooltips that need to escape parent overflow/stacking contexts.
6
+
7
+ ## Usage Logic
8
+
9
+ On first render, ensures the portal root `<div>` exists in `document.body`. Then portals children into it. The portal root has `position: fixed`, `inset: 0`, `z-index: 9999`, and `pointer-events: none`. Children must set `pointer-events: auto` to be interactive.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function ViewportPortal({ children }: { children: ReactNode }): ReactPortal;
15
+
16
+ // Utility to get the portal root element
17
+ function getViewportPortalRoot(): HTMLDivElement;
18
+ ```
19
+
20
+ ## Example Code
21
+
22
+ ```tsx
23
+ import { ViewportPortal } from "@musecat/functionkit";
24
+
25
+ function ToastContainer() {
26
+ return (
27
+ <ViewportPortal>
28
+ <div style={{ pointerEvents: "auto" }}>
29
+ <Toast message="Saved!" />
30
+ </div>
31
+ </ViewportPortal>
32
+ );
33
+ }
34
+ ```
@@ -0,0 +1,47 @@
1
+ # Cookie API
2
+
3
+ ## Purpose
4
+
5
+ Client-side cookie read/write/delete utilities. All functions except `parseClientCookieNames` access `document.cookie` and are client-only.
6
+
7
+ ## Usage Logic
8
+
9
+ Each function wraps native `document.cookie` operations. `setClientCookie` handles expiry via `Max-Age`. `clearClientCookie` and `clearAllClientCookies` set `Max-Age=0` to delete. `clearAllClientCookies` iterates over all domain/path combinations to ensure thorough cleanup.
10
+
11
+ ## Type Signatures
12
+
13
+ ```ts
14
+ function getClientCookie(name: string): string | null;
15
+ function setClientCookie(name: string, value: string, days?: number): void;
16
+ function clearClientCookie(name: string, domain?: string, path?: string): void;
17
+ function clearAllClientCookies(): void;
18
+ function parseClientCookieNames(cookieString: string): string[]; // SSR-safe
19
+ ```
20
+
21
+ ## Example Code
22
+
23
+ ```tsx
24
+ import {
25
+ getClientCookie,
26
+ setClientCookie,
27
+ clearClientCookie,
28
+ clearAllClientCookies,
29
+ parseClientCookieNames,
30
+ } from "@musecat/functionkit";
31
+
32
+ function useSession() {
33
+ const token = getClientCookie("session_token");
34
+
35
+ const login = (token: string) => setClientCookie("session_token", token, 7);
36
+
37
+ const logout = () => clearClientCookie("session_token");
38
+
39
+ return { token, login, logout };
40
+ }
41
+
42
+ // SSR-safe usage (Server Component)
43
+ function CookieNames({ cookieString }: { cookieString: string }) {
44
+ const names = parseClientCookieNames(cookieString);
45
+ return <div>Cookies: {names.join(", ")}</div>;
46
+ }
47
+ ```
@@ -0,0 +1,42 @@
1
+ # dateTime.client
2
+
3
+ ## Purpose
4
+
5
+ Client-side date/time formatting wrappers. Same signatures as `dateTime.server` but bundled exclusively for client usage. Use in `"use client"` components only.
6
+
7
+ ## Usage Logic
8
+
9
+ Same API surface as `dateTime.server` — identical function signatures. Separated to keep the server bundle clean of client-only code.
10
+
11
+ ## Type Signatures
12
+
13
+ ```ts
14
+ function formatClientDate(date: DateInput, locale: AppLocale, preset?: DatePreset): string;
15
+ function formatClientTime(date: DateInput, locale: AppLocale, preset?: TimePreset): string;
16
+ function formatClientDateTime(
17
+ date: DateInput,
18
+ locale: AppLocale,
19
+ options?: { datePreset?: DatePreset; timePreset?: TimePreset }
20
+ ): string;
21
+ function formatClientRelative(
22
+ date: DateInput,
23
+ locale: AppLocale,
24
+ options?: { maxRelativeDays?: number }
25
+ ): string;
26
+ ```
27
+
28
+ ## Example Code
29
+
30
+ ```tsx
31
+ "use client";
32
+ import { formatClientDate, formatClientRelative } from "@musecat/functionkit";
33
+
34
+ function PostMeta({ createdAt }: { createdAt: string }) {
35
+ return (
36
+ <div>
37
+ <time>{formatClientDate(createdAt, "kr", "long")}</time>
38
+ <span>{formatClientRelative(createdAt, "kr")}</span>
39
+ </div>
40
+ );
41
+ }
42
+ ```
@@ -0,0 +1,42 @@
1
+ # dateTime.server
2
+
3
+ ## Purpose
4
+
5
+ Server-side date/time formatting wrappers. Use in Server Components, RSC, and any server-side rendering context. Pure functions, SSR-safe.
6
+
7
+ ## Usage Logic
8
+
9
+ Delegates to `dateTime.shared` utilities internally. Provides simplified `DatePreset`/`TimePreset`-based APIs that compose multiple shared functions.
10
+
11
+ ## Type Signatures
12
+
13
+ ```ts
14
+ function formatServerDate(date: DateInput, locale: AppLocale, preset?: DatePreset): string;
15
+ function formatServerTime(date: DateInput, locale: AppLocale, preset?: TimePreset): string;
16
+ function formatServerDateTime(
17
+ date: DateInput,
18
+ locale: AppLocale,
19
+ options?: { datePreset?: DatePreset; timePreset?: TimePreset }
20
+ ): string;
21
+ function formatServerRelative(
22
+ date: DateInput,
23
+ locale: AppLocale,
24
+ options?: { maxRelativeDays?: number }
25
+ ): string;
26
+ ```
27
+
28
+ ## Example Code
29
+
30
+ ```tsx
31
+ import { formatServerDate, formatServerRelative } from "@musecat/functionkit";
32
+
33
+ // Server Component
34
+ export default function ArticlePage({ post }: { post: { createdAt: string } }) {
35
+ return (
36
+ <article>
37
+ <time>{formatServerDate(post.createdAt, "kr", "long")}</time>
38
+ <span>{formatServerRelative(post.createdAt, "kr", { maxRelativeDays: 7 })}</span>
39
+ </article>
40
+ );
41
+ }
42
+ ```
@@ -0,0 +1,86 @@
1
+ # dateTime.shared
2
+
3
+ ## Purpose
4
+
5
+ Core date/time formatting utilities and types. All functions are pure and SSR-safe — no `"use client"`, no `window` access. Forms the foundation for both server and client date wrappers.
6
+
7
+ ## Types
8
+
9
+ ```ts
10
+ type AppLocale = "kr" | "en" | "jp";
11
+ type DateInput = string | number | Date;
12
+ type DatePreset = "long" | "dot";
13
+ type TimePreset = "ko" | "12h" | "24h-minute" | "24h-second";
14
+ ```
15
+
16
+ ## Locale Conversion
17
+
18
+ ```ts
19
+ // "ko" → "kr", "ja" → "jp", anything else → "en"
20
+ function normalizeAppLocale(locale: string): AppLocale;
21
+
22
+ // "kr" → "ko-KR", "jp" → "ja-JP", "en" → "en-US"
23
+ function toIntlLocale(locale: AppLocale): string;
24
+ ```
25
+
26
+ ## UTC Date Utilities
27
+
28
+ ```ts
29
+ function toDate(input: DateInput): Date | null; // NaN-safe Date conversion
30
+ function toUtcMidnight(date: Date): Date; // UTC midnight of the given date
31
+ function parseUtcDateInput(input: string): Date | null; // "YYYY-MM-DD" → UTC Date
32
+ function formatUtcDateKey(date: Date): string; // UTC Date → "YYYY-MM-DD"
33
+ function getUtcWeekdayIndex(date: Date): number; // 0=Sunday
34
+ function addUtcDays(date: Date, days: number): Date; // Add days in UTC
35
+ ```
36
+
37
+ ## Formatted Output Functions
38
+
39
+ ```ts
40
+ // Long date: "2026년 7월 16일" / "July 16, 2026" / "2026年7月16日"
41
+ function formatLongDate(date: DateInput, locale: AppLocale): string;
42
+
43
+ // Dot date: "2026. 7. 16."
44
+ function formatDotDate(date: DateInput, locale: AppLocale): string;
45
+
46
+ // 24-hour time: "14:30" or "14:30:00"
47
+ function format24HourTime(date: DateInput, options?: { withSeconds?: boolean }): string;
48
+
49
+ // Korean time: "오후 2시 30분"
50
+ function formatKoreanTime(date: DateInput): string;
51
+
52
+ // 12-hour with locale: "오후 2:30" / "2:30 PM" / "午後 2:30"
53
+ function formatTwelveHourTime(date: DateInput, locale: AppLocale): string;
54
+ ```
55
+
56
+ ## Relative / Remaining Time
57
+
58
+ ```ts
59
+ // Relative: { text: "3분 전", isRelative: true }
60
+ function formatRelativeText(diffMs: number, locale: AppLocale): { text: string; isRelative: boolean };
61
+
62
+ // Remaining: "2일 3시간 남음" / "2 days 3 hours left" / "あと2日3時間"
63
+ function formatRemainingText(diffMs: number, locale: AppLocale): string;
64
+ ```
65
+
66
+ ## Example Code
67
+
68
+ ```tsx
69
+ import {
70
+ formatLongDate,
71
+ formatRelativeText,
72
+ formatUtcDateKey,
73
+ normalizeAppLocale,
74
+ toIntlLocale,
75
+ } from "@musecat/functionkit";
76
+
77
+ function EventDate({ date, locale }: { date: Date; locale: string }) {
78
+ const appLocale = normalizeAppLocale(locale);
79
+ return <time>{formatLongDate(date, appLocale)}</time>;
80
+ }
81
+
82
+ function RelativeTime({ diffMs }: { diffMs: number }) {
83
+ const { text, isRelative } = formatRelativeText(diffMs, "kr");
84
+ return <span>{text}</span>;
85
+ }
86
+ ```
@@ -0,0 +1,29 @@
1
+ # useAvoidKeyboard
2
+
3
+ ## Purpose
4
+
5
+ Returns a style object that pushes the bottom of the viewport upward via `translateY` when the virtual keyboard appears. Useful for fixing mobile layout issues where the keyboard overlaps input fields.
6
+
7
+ ## Usage Logic
8
+
9
+ Internally subscribes to `useKeyboardHeight`. When the keyboard opens, the returned style applies a negative `translateY` equal to the keyboard height, keeping the focused element visible. The transition is animated via CSS `transition`.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useAvoidKeyboard(): React.CSSProperties
15
+ ```
16
+
17
+ Returns a style object containing `transform`, `transition`, and `willChange` properties.
18
+
19
+ ## Example Code
20
+
21
+ ```tsx
22
+ import { useAvoidKeyboard } from "@musecat/functionkit";
23
+ import { View } from "./YourView";
24
+
25
+ function ChatInput() {
26
+ const keyboardStyle = useAvoidKeyboard();
27
+ return <View style={keyboardStyle}>...</View>;
28
+ }
29
+ ```
@@ -0,0 +1,36 @@
1
+ # useCheckInvisible
2
+
3
+ ## Purpose
4
+
5
+ Detects whether an element with a given CSS class name has scrolled above the viewport (`top < 0`). Useful for triggering "sticky" or "collapsed" states when a section scrolls out of view.
6
+
7
+ ## Usage Logic
8
+
9
+ Uses `IntersectionObserver` or scroll event tracking to monitor the first element matching the provided class name. Returns `true` when the element's top edge is above the viewport.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useCheckInvisible(className: string): boolean
15
+ ```
16
+
17
+ | Parameter | Type | Description |
18
+ |-----------|------|-------------|
19
+ | `className` | `string` | CSS class name of the element to monitor |
20
+
21
+ Returns `boolean` — `true` when the element is scrolled out of view (top < 0).
22
+
23
+ ## Example Code
24
+
25
+ ```tsx
26
+ import { useCheckInvisible } from "@musecat/functionkit/hooks/useCheckInvisible";
27
+
28
+ function Header() {
29
+ const isHidden = useCheckInvisible("hero-section");
30
+ return <header className={isHidden ? "sticky" : ""}>...</header>;
31
+ }
32
+ ```
33
+
34
+ ## Note
35
+
36
+ Not exported from the barrel. Import via subpath only: `@musecat/functionkit/hooks/useCheckInvisible`.
@@ -0,0 +1,36 @@
1
+ # useCheckScroll
2
+
3
+ ## Purpose
4
+
5
+ Detects whether the page has been scrolled vertically (`window.scrollY > 0`). Returns an `isScrolled` flag for toggling UI states such as sticky header shadows or floating action buttons.
6
+
7
+ ## Usage Logic
8
+
9
+ Listens to the `scroll` event on `window`. Memoizes the check to avoid unnecessary re-renders.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useCheckScroll(): { isScrolled: boolean }
15
+ ```
16
+
17
+ Returns `{ isScrolled: boolean }` — `isScrolled` is `true` when `window.scrollY > 0`.
18
+
19
+ ## Example Code
20
+
21
+ ```tsx
22
+ import { useCheckScroll } from "@musecat/functionkit/hooks/useCheckScroll";
23
+
24
+ function StickyHeader() {
25
+ const { isScrolled } = useCheckScroll();
26
+ return (
27
+ <header className={isScrolled ? "header--scrolled" : "header--top"}>
28
+ ...
29
+ </header>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## Note
35
+
36
+ Not exported from the barrel. Import via subpath only: `@musecat/functionkit/hooks/useCheckScroll`.
@@ -0,0 +1,39 @@
1
+ # useClientDateTime
2
+
3
+ ## Purpose
4
+
5
+ Formats a date on the client side using the user's locale and timezone. Returns the formatted text inside a `useEffect`, ensuring hydration safety via a `ready` flag.
6
+
7
+ ## Usage Logic
8
+
9
+ Takes a `DateInput` and locale, then computes the formatted date/time string on mount and whenever inputs change. Returns a `ready` boolean (initially `false`) so the component can render a fallback (e.g., server-rendered date) before hydration completes.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useClientDateTime(
15
+ date: DateInput,
16
+ locale: AppLocale,
17
+ options?: {
18
+ datePreset?: DatePreset;
19
+ timePreset?: TimePreset;
20
+ }
21
+ ): {
22
+ ready: boolean;
23
+ text: string;
24
+ date: Date | null;
25
+ }
26
+ ```
27
+
28
+ ## Example Code
29
+
30
+ ```tsx
31
+ import { useClientDateTime } from "@musecat/functionkit";
32
+
33
+ function PostDate({ timestamp }: { timestamp: string }) {
34
+ const { ready, text } = useClientDateTime(timestamp, "kr", {
35
+ datePreset: "long",
36
+ });
37
+ return <time>{ready ? text : timestamp}</time>;
38
+ }
39
+ ```
@@ -0,0 +1,46 @@
1
+ # useDebounce / debounce
2
+
3
+ ## Purpose
4
+
5
+ Delays the propagation of a rapidly changing value. Includes both a raw utility function (`debounce`) for non-React use and a React hook (`useDebounce`) that auto-cleans up on unmount.
6
+
7
+ ## Usage Logic
8
+
9
+ **`debounce`**: A pure function that wraps any callback with configurable leading/trailing edge execution. Returns the debounced function with a `.cancel()` method.
10
+
11
+ **`useDebounce`**: React hook that returns the debounced value. Internally uses `useState` + `useEffect` with the raw `debounce` utility. Automatically cancels pending timers on unmount.
12
+
13
+ ## Type Signatures
14
+
15
+ ```ts
16
+ // Pure function — SSR-safe
17
+ function debounce<T extends (...args: any[]) => any>(
18
+ fn: T,
19
+ delay: number,
20
+ options?: { leading?: boolean; trailing?: boolean }
21
+ ): T & { cancel: () => void };
22
+
23
+ // React hook — client-only
24
+ function useDebounce<T>(
25
+ value: T,
26
+ delay: number,
27
+ options?: { leading?: boolean; trailing?: boolean }
28
+ ): T;
29
+ ```
30
+
31
+ ## Example Code
32
+
33
+ ```tsx
34
+ import { useDebounce } from "@musecat/functionkit";
35
+
36
+ function SearchInput() {
37
+ const [query, setQuery] = useState("");
38
+ const debouncedQuery = useDebounce(query, 300);
39
+
40
+ useEffect(() => {
41
+ if (debouncedQuery) fetchResults(debouncedQuery);
42
+ }, [debouncedQuery]);
43
+
44
+ return <input onChange={(e) => setQuery(e.target.value)} />;
45
+ }
46
+ ```
@@ -0,0 +1,34 @@
1
+ # useDebouncedCallback
2
+
3
+ ## Purpose
4
+
5
+ Provides a debounced callback for boolean value changes. Unlike `useDebounce` which debounces a value, this hook debounces the *invocation* of a callback, automatically canceling the previous pending call.
6
+
7
+ ## Usage Logic
8
+
9
+ Wraps the `onChange` handler so it only fires after `timeThreshold` ms of inactivity. Each call replaces the previous pending timer, acting as a "settled" change detector.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useDebouncedCallback(
15
+ onChange: (value: boolean) => void,
16
+ timeThreshold: number
17
+ ): (value: boolean) => void;
18
+ ```
19
+
20
+ Returns a memoized setter that debounces calls to `onChange`.
21
+
22
+ ## Example Code
23
+
24
+ ```tsx
25
+ import { useDebouncedCallback } from "@musecat/functionkit";
26
+
27
+ function ToggleTracker() {
28
+ const handleChange = useDebouncedCallback((value) => {
29
+ console.log("Toggle settled:", value);
30
+ }, 500);
31
+
32
+ return <Toggle onChange={handleChange} />;
33
+ }
34
+ ```
@@ -0,0 +1,40 @@
1
+ # useDoubleClick
2
+
3
+ ## Purpose
4
+
5
+ Distinguishes between a single click and a double click on the same element. Fires `click` on a single click after a configurable delay, or `doubleClick` if a second click arrives within the delay window.
6
+
7
+ ## Usage Logic
8
+
9
+ Maintains an internal timer. On the first click it waits for `delay` ms. If a second click arrives within that window, it fires `doubleClick` and clears the timer. Otherwise it fires `click` after the delay expires.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useDoubleClick(
15
+ handlers: {
16
+ click?: () => void;
17
+ doubleClick?: () => void;
18
+ },
19
+ delay?: number
20
+ ): {
21
+ onClick: () => void;
22
+ };
23
+ ```
24
+
25
+ Returns an `onClick` handler to spread onto the target element.
26
+
27
+ ## Example Code
28
+
29
+ ```tsx
30
+ import { useDoubleClick } from "@musecat/functionkit";
31
+
32
+ function FileIcon() {
33
+ const { onClick } = useDoubleClick({
34
+ click: () => console.log("select"),
35
+ doubleClick: () => console.log("open"),
36
+ });
37
+
38
+ return <div onClick={onClick}>📄</div>;
39
+ }
40
+ ```
@@ -0,0 +1,37 @@
1
+ # useGeolocation
2
+
3
+ ## Purpose
4
+
5
+ Wraps the Geolocation API (`navigator.geolocation`) for retrieving the user's current position or continuously watching position changes.
6
+
7
+ ## Usage Logic
8
+
9
+ Supports two modes via the `action` option: `"GET"` calls `getCurrentPosition` once, `"WATCH"` uses `watchPosition` for continuous tracking. Provides imperative `startTracking` / `stopTracking` controls.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ interface UseGeolocationOptions {
15
+ action?: "GET" | "WATCH";
16
+ }
17
+
18
+ function useGeolocation(options?: UseGeolocationOptions): {
19
+ latitude: number | null;
20
+ longitude: number | null;
21
+ error: GeolocationPositionError | null;
22
+ startTracking: () => void;
23
+ stopTracking: () => void;
24
+ };
25
+ ```
26
+
27
+ ## Example Code
28
+
29
+ ```tsx
30
+ import { useGeolocation } from "@musecat/functionkit";
31
+
32
+ function LocationDisplay() {
33
+ const { latitude, longitude, error } = useGeolocation({ action: "GET" });
34
+ if (error) return <div>Location unavailable</div>;
35
+ return <div>{latitude}, {longitude}</div>;
36
+ }
37
+ ```
@@ -0,0 +1,27 @@
1
+ # useHasMounted
2
+
3
+ ## Purpose
4
+
5
+ Returns a boolean indicating whether the component has mounted on the client. A simple hydration guard for preventing server-client content mismatches.
6
+
7
+ ## Usage Logic
8
+
9
+ Uses `useEffect` to set `true` after mount. Returns `false` on the server and during the first client render before hydration.
10
+
11
+ ## Type Signature
12
+
13
+ ```ts
14
+ function useHasMounted(): boolean;
15
+ ```
16
+
17
+ ## Example Code
18
+
19
+ ```tsx
20
+ import { useHasMounted } from "@musecat/functionkit";
21
+
22
+ function ClientOnly() {
23
+ const mounted = useHasMounted();
24
+ if (!mounted) return <Fallback />;
25
+ return <ClientContent />;
26
+ }
27
+ ```