@m1kapp/kit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +627 -0
- package/dist/index.d.mts +898 -0
- package/dist/index.d.ts +898 -0
- package/dist/index.js +12 -0
- package/dist/index.mjs +12 -0
- package/dist/server.d.mts +71 -0
- package/dist/server.d.ts +71 -0
- package/dist/server.js +1 -0
- package/dist/server.mjs +1 -0
- package/package.json +66 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import React__default, { ReactNode, CSSProperties } from 'react';
|
|
4
|
+
import { MetadataRoute, Viewport } from 'next';
|
|
5
|
+
|
|
6
|
+
type ButtonVariant = "dark" | "light";
|
|
7
|
+
type ButtonShape = "rounded" | "pill";
|
|
8
|
+
interface ButtonBaseProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
variant?: ButtonVariant;
|
|
11
|
+
shape?: ButtonShape;
|
|
12
|
+
/** Full width */
|
|
13
|
+
full?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
interface ButtonAsButton extends ButtonBaseProps {
|
|
17
|
+
href?: undefined;
|
|
18
|
+
onClick?: () => void;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface ButtonAsAnchor extends ButtonBaseProps {
|
|
22
|
+
href: string;
|
|
23
|
+
target?: string;
|
|
24
|
+
rel?: string;
|
|
25
|
+
onClick?: undefined;
|
|
26
|
+
disabled?: undefined;
|
|
27
|
+
}
|
|
28
|
+
type ButtonProps = ButtonAsButton | ButtonAsAnchor;
|
|
29
|
+
/**
|
|
30
|
+
* Dark/light button that adapts to dark mode.
|
|
31
|
+
* Renders as `<a>` when `href` is provided, `<button>` otherwise.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* <Button variant="dark" href="https://github.com/m1kapp/ui">GitHub</Button>
|
|
35
|
+
* <Button variant="light" onClick={reset}>다시 시도</Button>
|
|
36
|
+
* <Button variant="dark" shape="pill" full onClick={submit}>시작하기</Button>
|
|
37
|
+
*/
|
|
38
|
+
declare function Button({ children, variant, shape, full, className, ...props }: ButtonProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
|
|
40
|
+
interface AppShellProps {
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
className?: string;
|
|
43
|
+
/** Max width of the shell. Default: 430px */
|
|
44
|
+
maxWidth?: number;
|
|
45
|
+
/** Max height of the shell. Default: 932px (iPhone 15 Pro Max) */
|
|
46
|
+
maxHeight?: number;
|
|
47
|
+
style?: CSSProperties;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mobile app-like container with rounded corners, shadow, and ring.
|
|
51
|
+
* Centers content and constrains width for a phone-like viewport.
|
|
52
|
+
*/
|
|
53
|
+
declare function AppShell({ children, className, maxWidth, maxHeight, style, }: AppShellProps): react_jsx_runtime.JSX.Element;
|
|
54
|
+
interface AppShellHeaderProps {
|
|
55
|
+
children: ReactNode;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sticky top header with blur backdrop.
|
|
60
|
+
*/
|
|
61
|
+
declare function AppShellHeader({ children, className }: AppShellHeaderProps): react_jsx_runtime.JSX.Element;
|
|
62
|
+
interface AppShellContentProps {
|
|
63
|
+
children: ReactNode;
|
|
64
|
+
className?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Scrollable main content area.
|
|
68
|
+
*/
|
|
69
|
+
declare function AppShellContent({ children, className }: AppShellContentProps): react_jsx_runtime.JSX.Element;
|
|
70
|
+
|
|
71
|
+
interface TabBarProps {
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Sticky bottom navigation tab bar.
|
|
77
|
+
*/
|
|
78
|
+
declare function TabBar({ children, className }: TabBarProps): react_jsx_runtime.JSX.Element;
|
|
79
|
+
interface TabProps {
|
|
80
|
+
active: boolean;
|
|
81
|
+
onClick: () => void;
|
|
82
|
+
icon: ReactNode;
|
|
83
|
+
label: string;
|
|
84
|
+
/** Active tab color. Default: current text color */
|
|
85
|
+
activeColor?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Individual tab button for the TabBar.
|
|
89
|
+
*/
|
|
90
|
+
declare function Tab({ active, onClick, icon, label, activeColor }: TabProps): react_jsx_runtime.JSX.Element;
|
|
91
|
+
|
|
92
|
+
interface SectionProps {
|
|
93
|
+
children: ReactNode;
|
|
94
|
+
className?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Padded section wrapper (px-4).
|
|
98
|
+
*/
|
|
99
|
+
declare function Section({ children, className }: SectionProps): react_jsx_runtime.JSX.Element;
|
|
100
|
+
interface SectionHeaderProps {
|
|
101
|
+
children: ReactNode;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Small uppercase section title.
|
|
105
|
+
*/
|
|
106
|
+
declare function SectionHeader({ children }: SectionHeaderProps): react_jsx_runtime.JSX.Element;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Horizontal divider line with margin.
|
|
110
|
+
*/
|
|
111
|
+
declare function Divider({ className }: {
|
|
112
|
+
className?: string;
|
|
113
|
+
}): react_jsx_runtime.JSX.Element;
|
|
114
|
+
|
|
115
|
+
interface StatChipProps {
|
|
116
|
+
label: string;
|
|
117
|
+
value: number;
|
|
118
|
+
className?: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Compact stat display chip with label and number.
|
|
122
|
+
*/
|
|
123
|
+
declare function StatChip({ label, value, className }: StatChipProps): react_jsx_runtime.JSX.Element;
|
|
124
|
+
|
|
125
|
+
interface EmptyStateProps {
|
|
126
|
+
message: string;
|
|
127
|
+
icon?: ReactNode;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Empty state placeholder with icon and message.
|
|
131
|
+
*/
|
|
132
|
+
declare function EmptyState({ message, icon }: EmptyStateProps): react_jsx_runtime.JSX.Element;
|
|
133
|
+
|
|
134
|
+
interface WatermarkSponsor {
|
|
135
|
+
/** Service name displayed in the background as clickable text */
|
|
136
|
+
name: string;
|
|
137
|
+
/** URL to navigate to when the sponsor text is clicked */
|
|
138
|
+
url: string;
|
|
139
|
+
}
|
|
140
|
+
interface WatermarkProps {
|
|
141
|
+
children: ReactNode;
|
|
142
|
+
/** Background fill color. Accepts any CSS color value. Default: "#0f172a" */
|
|
143
|
+
color?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Repeating watermark text shown across the background.
|
|
146
|
+
* Default: "m1k"
|
|
147
|
+
*/
|
|
148
|
+
text?: string;
|
|
149
|
+
/** Max width of the center content area in px. Default: 430 */
|
|
150
|
+
maxWidth?: number;
|
|
151
|
+
/**
|
|
152
|
+
* 1k milestone sponsor slot.
|
|
153
|
+
* The sponsor's name is interleaved with the watermark text across the
|
|
154
|
+
* background. Sponsor tiles are clickable and open the sponsor URL.
|
|
155
|
+
*
|
|
156
|
+
* Font size auto-scales based on text length (14-28px).
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* <Watermark
|
|
160
|
+
* color={colors.blue}
|
|
161
|
+
* sponsor={{ name: "@m1kapp/ui", url: "https://github.com/m1kapp/ui" }}
|
|
162
|
+
* >
|
|
163
|
+
* <AppShell>...</AppShell>
|
|
164
|
+
* </Watermark>
|
|
165
|
+
*/
|
|
166
|
+
sponsor?: WatermarkSponsor;
|
|
167
|
+
/**
|
|
168
|
+
* Background drift animation speed in seconds per cycle.
|
|
169
|
+
* Set to `0` to disable animation (static background).
|
|
170
|
+
* Default: 40
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* <Watermark speed={0} /> // static
|
|
174
|
+
* <Watermark speed={20} /> // faster
|
|
175
|
+
* <Watermark speed={60} /> // very slow
|
|
176
|
+
*/
|
|
177
|
+
speed?: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Full-screen colored background with repeating animated text watermark pattern.
|
|
181
|
+
*
|
|
182
|
+
* Renders as a single SVG element with a <pattern> — zero extra DOM nodes per tile.
|
|
183
|
+
* Sponsor tiles inside the pattern are clickable and support hover effects.
|
|
184
|
+
*/
|
|
185
|
+
declare function Watermark({ children, color, text, maxWidth, sponsor, speed, }: WatermarkProps): react_jsx_runtime.JSX.Element;
|
|
186
|
+
|
|
187
|
+
declare const colors: {
|
|
188
|
+
readonly blue: "#3b82f6";
|
|
189
|
+
readonly purple: "#8b5cf6";
|
|
190
|
+
readonly green: "#10b981";
|
|
191
|
+
readonly orange: "#f97316";
|
|
192
|
+
readonly pink: "#ec4899";
|
|
193
|
+
readonly red: "#ef4444";
|
|
194
|
+
readonly yellow: "#eab308";
|
|
195
|
+
readonly cyan: "#06b6d4";
|
|
196
|
+
readonly slate: "#0f172a";
|
|
197
|
+
readonly zinc: "#27272a";
|
|
198
|
+
};
|
|
199
|
+
type ColorName = keyof typeof colors;
|
|
200
|
+
|
|
201
|
+
declare const THEME_SCRIPT = "(function(){try{var c=document.cookie.split('; ').find(function(r){return r.startsWith('theme=')});var t=c?c.split('=')[1]:null;if(t==='dark'){document.documentElement.classList.add('dark')}else{document.documentElement.classList.remove('dark')}}catch(e){}})()";
|
|
202
|
+
|
|
203
|
+
interface ThemeButtonProps {
|
|
204
|
+
/** Accent color for the diagonal split. Omit to show moon/sun icon instead. */
|
|
205
|
+
color?: string;
|
|
206
|
+
dark?: boolean;
|
|
207
|
+
onClick: () => void;
|
|
208
|
+
className?: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Circular theme toggle button.
|
|
212
|
+
* - With `color`: split diagonally — half dark/light, half theme color.
|
|
213
|
+
* - Without `color`: moon (light mode) or sun (dark mode) icon.
|
|
214
|
+
*/
|
|
215
|
+
declare function ThemeButton({ color, dark: darkProp, onClick, className }: ThemeButtonProps): react_jsx_runtime.JSX.Element;
|
|
216
|
+
interface ThemeDialogProps {
|
|
217
|
+
open: boolean;
|
|
218
|
+
onClose: () => void;
|
|
219
|
+
current: string;
|
|
220
|
+
onSelect: (color: string) => void;
|
|
221
|
+
dark?: boolean;
|
|
222
|
+
onDarkToggle?: () => void;
|
|
223
|
+
/** Override the color palette. Defaults to built-in colors. */
|
|
224
|
+
palette?: Record<string, string>;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Bottom-sheet style color picker dialog.
|
|
228
|
+
* Shows a 5-column grid of color circles with check on the active one.
|
|
229
|
+
*/
|
|
230
|
+
declare function ThemeDialog({ open, onClose, current, onSelect, dark: darkProp, onDarkToggle, palette, }: ThemeDialogProps): React.ReactPortal | null;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Font presets for @m1kapp/ui.
|
|
234
|
+
* CDN links only — no font files bundled.
|
|
235
|
+
*
|
|
236
|
+
* ## Quick setup (recommended)
|
|
237
|
+
*
|
|
238
|
+
* Add to your `index.html` <head>:
|
|
239
|
+
* ```html
|
|
240
|
+
* <link rel="preconnect" href="https://cdn.jsdelivr.net" />
|
|
241
|
+
* <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
|
242
|
+
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/toss/tossface/dist/tossface.css" />
|
|
243
|
+
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* Add to your global CSS:
|
|
247
|
+
* ```css
|
|
248
|
+
* html {
|
|
249
|
+
* font-family: "Tossface", "Pretendard Variable", "Pretendard", system-ui, sans-serif;
|
|
250
|
+
* }
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* That's it — Tossface handles emojis, Pretendard handles text.
|
|
254
|
+
*/
|
|
255
|
+
declare const fonts: {
|
|
256
|
+
/**
|
|
257
|
+
* Tossface — Toss emoji font (open source, jsDelivr CDN).
|
|
258
|
+
* Renders all emoji with the Toss design style.
|
|
259
|
+
* Add this to get consistent emoji across platforms.
|
|
260
|
+
*/
|
|
261
|
+
readonly tossface: "https://cdn.jsdelivr.net/gh/toss/tossface/dist/tossface.css";
|
|
262
|
+
/**
|
|
263
|
+
* Pretendard — best Korean variable web font (jsDelivr CDN).
|
|
264
|
+
* Recommended for all Korean UI text.
|
|
265
|
+
*/
|
|
266
|
+
readonly pretendard: "https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css";
|
|
267
|
+
/** Inter — clean Latin sans-serif (Google Fonts) */
|
|
268
|
+
readonly inter: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap";
|
|
269
|
+
};
|
|
270
|
+
type FontName = keyof typeof fonts;
|
|
271
|
+
/**
|
|
272
|
+
* Recommended font-family stacks.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* // In your global CSS:
|
|
276
|
+
* html { font-family: ${fontFamily.default}; }
|
|
277
|
+
*
|
|
278
|
+
* // Or in JS (e.g. main.tsx):
|
|
279
|
+
* document.documentElement.style.fontFamily = fontFamily.default;
|
|
280
|
+
*/
|
|
281
|
+
declare const fontFamily: {
|
|
282
|
+
/**
|
|
283
|
+
* Default recommended stack.
|
|
284
|
+
* Tossface for emoji + Pretendard for Korean/Latin text.
|
|
285
|
+
*/
|
|
286
|
+
readonly default: "\"Pretendard Variable\", \"Pretendard\", system-ui, -apple-system, sans-serif, \"Tossface\"";
|
|
287
|
+
/** Pretendard only */
|
|
288
|
+
readonly pretendard: "\"Pretendard Variable\", \"Pretendard\", system-ui, sans-serif";
|
|
289
|
+
/** Inter only */
|
|
290
|
+
readonly inter: "\"Inter\", system-ui, sans-serif";
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
interface TypewriterProps {
|
|
294
|
+
/** Words to cycle through */
|
|
295
|
+
words: string[];
|
|
296
|
+
/** Text color */
|
|
297
|
+
color?: string;
|
|
298
|
+
/** Typing speed in ms (base, actual varies ±50%). Default: 80 */
|
|
299
|
+
speed?: number;
|
|
300
|
+
/** Delete speed in ms. Default: 30 */
|
|
301
|
+
deleteSpeed?: number;
|
|
302
|
+
/** Pause after typing completes in ms. Default: 2200 */
|
|
303
|
+
pauseMs?: number;
|
|
304
|
+
/** Pause between words in ms. Default: 400 */
|
|
305
|
+
gapMs?: number;
|
|
306
|
+
/** Cursor color. Defaults to `color` prop */
|
|
307
|
+
cursorColor?: string;
|
|
308
|
+
className?: string;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Typewriter effect that cycles through words with human-like timing.
|
|
312
|
+
* Features irregular delays, space pauses, char pop animation, and blinking cursor.
|
|
313
|
+
*/
|
|
314
|
+
declare function Typewriter({ words, color, speed, deleteSpeed, pauseMs, gapMs, cursorColor, className, }: TypewriterProps): react_jsx_runtime.JSX.Element;
|
|
315
|
+
|
|
316
|
+
interface EmojiButtonProps {
|
|
317
|
+
emoji: string;
|
|
318
|
+
onClick: () => void;
|
|
319
|
+
className?: string;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Small button displaying the selected emoji.
|
|
323
|
+
* Use it anywhere — tab icons, headers, list items, etc.
|
|
324
|
+
*/
|
|
325
|
+
declare function EmojiButton({ emoji, onClick, className }: EmojiButtonProps): react_jsx_runtime.JSX.Element;
|
|
326
|
+
interface EmojiPickerProps {
|
|
327
|
+
open: boolean;
|
|
328
|
+
onClose: () => void;
|
|
329
|
+
current: string;
|
|
330
|
+
onSelect: (emoji: string) => void;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Bottom-sheet emoji picker with categories.
|
|
334
|
+
*/
|
|
335
|
+
declare function EmojiPicker({ open, onClose, current, onSelect }: EmojiPickerProps): React.ReactPortal | null;
|
|
336
|
+
|
|
337
|
+
interface TooltipProps {
|
|
338
|
+
label: string;
|
|
339
|
+
children: ReactNode;
|
|
340
|
+
/** Placement relative to the trigger. Default: "top" */
|
|
341
|
+
placement?: "top" | "bottom";
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Simple tooltip that appears above (or below) the trigger on hover/focus.
|
|
345
|
+
* Renders via portal so it's never clipped by overflow-hidden containers.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* <Tooltip label="복사하기">
|
|
349
|
+
* <button>복사</button>
|
|
350
|
+
* </Tooltip>
|
|
351
|
+
*/
|
|
352
|
+
declare function Tooltip({ label, children, placement }: TooltipProps): react_jsx_runtime.JSX.Element;
|
|
353
|
+
|
|
354
|
+
interface GrassMapData {
|
|
355
|
+
date: string;
|
|
356
|
+
count: number;
|
|
357
|
+
}
|
|
358
|
+
interface GrassMapProps {
|
|
359
|
+
data: GrassMapData[];
|
|
360
|
+
accent: string;
|
|
361
|
+
isDark?: boolean;
|
|
362
|
+
/** Unit label appended to count in tooltip. e.g. "명", "commits". Default: "" */
|
|
363
|
+
unit?: string;
|
|
364
|
+
}
|
|
365
|
+
declare function GrassMap({ data, accent, isDark, unit }: GrassMapProps): react_jsx_runtime.JSX.Element;
|
|
366
|
+
|
|
367
|
+
type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
368
|
+
type AvatarShape = "circle" | "rounded";
|
|
369
|
+
interface AvatarProps {
|
|
370
|
+
src?: string;
|
|
371
|
+
fallback: string;
|
|
372
|
+
size?: AvatarSize;
|
|
373
|
+
shape?: AvatarShape;
|
|
374
|
+
color?: string;
|
|
375
|
+
className?: string;
|
|
376
|
+
}
|
|
377
|
+
declare function Avatar({ src, fallback, size, shape, color, className, }: AvatarProps): react_jsx_runtime.JSX.Element;
|
|
378
|
+
|
|
379
|
+
type BadgeVariant = "default" | "green" | "red" | "yellow" | "blue" | "purple" | "orange";
|
|
380
|
+
type BadgeSize = "sm" | "md";
|
|
381
|
+
interface BadgeProps {
|
|
382
|
+
children: React__default.ReactNode;
|
|
383
|
+
variant?: BadgeVariant;
|
|
384
|
+
size?: BadgeSize;
|
|
385
|
+
className?: string;
|
|
386
|
+
}
|
|
387
|
+
declare function Badge({ children, variant, size, className, }: BadgeProps): react_jsx_runtime.JSX.Element;
|
|
388
|
+
|
|
389
|
+
interface UseShareOptions {
|
|
390
|
+
url?: string;
|
|
391
|
+
title?: string;
|
|
392
|
+
text?: string;
|
|
393
|
+
}
|
|
394
|
+
interface UseShareReturn {
|
|
395
|
+
share: (options?: UseShareOptions) => Promise<void>;
|
|
396
|
+
copied: boolean;
|
|
397
|
+
canNativeShare: boolean;
|
|
398
|
+
}
|
|
399
|
+
declare function useShare(defaults?: UseShareOptions): UseShareReturn;
|
|
400
|
+
interface ShareButtonProps {
|
|
401
|
+
url?: string;
|
|
402
|
+
title?: string;
|
|
403
|
+
text?: string;
|
|
404
|
+
label?: string;
|
|
405
|
+
copiedLabel?: string;
|
|
406
|
+
className?: string;
|
|
407
|
+
}
|
|
408
|
+
declare function ShareButton({ url, title, text, label, copiedLabel, className, }: ShareButtonProps): react_jsx_runtime.JSX.Element;
|
|
409
|
+
|
|
410
|
+
type ToastVariant = "default" | "success" | "error" | "info";
|
|
411
|
+
interface ToastOptions {
|
|
412
|
+
variant?: ToastVariant;
|
|
413
|
+
duration?: number;
|
|
414
|
+
}
|
|
415
|
+
type ToastFn = (message: string, options?: ToastOptions) => void;
|
|
416
|
+
declare function ToastProvider({ children }: {
|
|
417
|
+
children: React__default.ReactNode;
|
|
418
|
+
}): react_jsx_runtime.JSX.Element;
|
|
419
|
+
declare function useToast(): ToastFn;
|
|
420
|
+
|
|
421
|
+
declare function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void, () => void];
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Returns a debounced version of `value` that only updates
|
|
425
|
+
* after `delay` ms of inactivity.
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* const [query, setQuery] = useState("");
|
|
429
|
+
* const debouncedQuery = useDebounce(query, 300);
|
|
430
|
+
*
|
|
431
|
+
* useEffect(() => {
|
|
432
|
+
* if (debouncedQuery) fetchResults(debouncedQuery);
|
|
433
|
+
* }, [debouncedQuery]);
|
|
434
|
+
*/
|
|
435
|
+
declare function useDebounce<T>(value: T, delay: number): T;
|
|
436
|
+
|
|
437
|
+
interface UseFormSubmitOptions<T> {
|
|
438
|
+
onSuccess?: (data: T) => void;
|
|
439
|
+
onError?: (err: Error) => void;
|
|
440
|
+
}
|
|
441
|
+
interface UseFormSubmitResult<T, V> {
|
|
442
|
+
submit: (vars: V) => Promise<void>;
|
|
443
|
+
loading: boolean;
|
|
444
|
+
error: Error | null;
|
|
445
|
+
data: T | undefined;
|
|
446
|
+
reset: () => void;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Wraps an async submit function with loading / error / data state.
|
|
450
|
+
* Eliminates the try/catch/finally + setState boilerplate from every form.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* const { submit, loading, error } = useFormSubmit(async (url: string) => {
|
|
454
|
+
* return api.post<Site>("/api/sites", { url });
|
|
455
|
+
* }, {
|
|
456
|
+
* onSuccess: () => router.push("/dashboard"),
|
|
457
|
+
* });
|
|
458
|
+
*
|
|
459
|
+
* <form onSubmit={e => { e.preventDefault(); submit(input); }}>
|
|
460
|
+
* <Button loading={loading}>등록</Button>
|
|
461
|
+
* {error && <p>{error.message}</p>}
|
|
462
|
+
* </form>
|
|
463
|
+
*/
|
|
464
|
+
declare function useFormSubmit<T, V = void>(fn: (vars: V) => Promise<T>, options?: UseFormSubmitOptions<T>): UseFormSubmitResult<T, V>;
|
|
465
|
+
|
|
466
|
+
interface UseInViewOptions {
|
|
467
|
+
/** 0–1, how much of the element must be visible (default: 0) */
|
|
468
|
+
threshold?: number;
|
|
469
|
+
/** Shrink/grow the root's bounding box (default: "0px") */
|
|
470
|
+
rootMargin?: string;
|
|
471
|
+
/** Only trigger once — stops observing after first intersection (default: false) */
|
|
472
|
+
once?: boolean;
|
|
473
|
+
}
|
|
474
|
+
interface UseInViewResult {
|
|
475
|
+
/** Attach to the element you want to observe */
|
|
476
|
+
ref: (node: Element | null) => void;
|
|
477
|
+
inView: boolean;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Observe whether an element is inside the viewport.
|
|
481
|
+
* Common uses: infinite scroll trigger, lazy-load images, entrance animations.
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* const { ref, inView } = useInView({ threshold: 0.1, once: true });
|
|
485
|
+
*
|
|
486
|
+
* useEffect(() => {
|
|
487
|
+
* if (inView) fetchNextPage();
|
|
488
|
+
* }, [inView]);
|
|
489
|
+
*
|
|
490
|
+
* <div ref={ref} />
|
|
491
|
+
*/
|
|
492
|
+
declare function useInView(options?: UseInViewOptions): UseInViewResult;
|
|
493
|
+
|
|
494
|
+
interface SkeletonProps {
|
|
495
|
+
/** Tailwind classes for width / height (e.g. "h-4 w-3/4") */
|
|
496
|
+
className?: string;
|
|
497
|
+
/** Corner radius shorthand */
|
|
498
|
+
rounded?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Animated loading placeholder.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* // Text line
|
|
505
|
+
* <Skeleton className="h-4 w-3/4" />
|
|
506
|
+
*
|
|
507
|
+
* // Card block
|
|
508
|
+
* <Skeleton className="h-32 w-full" rounded="xl" />
|
|
509
|
+
*
|
|
510
|
+
* // Avatar
|
|
511
|
+
* <Skeleton className="h-10 w-10" rounded="full" />
|
|
512
|
+
*/
|
|
513
|
+
declare function Skeleton({ className, rounded }: SkeletonProps): react_jsx_runtime.JSX.Element;
|
|
514
|
+
|
|
515
|
+
interface DialogProps {
|
|
516
|
+
open: boolean;
|
|
517
|
+
onClose: () => void;
|
|
518
|
+
title?: string;
|
|
519
|
+
/** Max width of the panel (default: "max-w-sm") */
|
|
520
|
+
size?: "sm" | "md" | "lg";
|
|
521
|
+
children: React__default.ReactNode;
|
|
522
|
+
/** Hide the backdrop click-to-close behaviour */
|
|
523
|
+
persistent?: boolean;
|
|
524
|
+
className?: string;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Modal dialog with:
|
|
528
|
+
* - Portal render (no z-index issues)
|
|
529
|
+
* - Backdrop click to close
|
|
530
|
+
* - Escape key to close
|
|
531
|
+
* - Body scroll lock
|
|
532
|
+
* - Smooth scale + fade animation
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* <Dialog open={open} onClose={() => setOpen(false)} title="삭제 확인">
|
|
536
|
+
* <p className="text-sm text-zinc-500">정말 삭제할까요?</p>
|
|
537
|
+
* <div className="flex gap-2 mt-4">
|
|
538
|
+
* <Button onClick={handleDelete}>삭제</Button>
|
|
539
|
+
* <Button onClick={() => setOpen(false)}>취소</Button>
|
|
540
|
+
* </div>
|
|
541
|
+
* </Dialog>
|
|
542
|
+
*/
|
|
543
|
+
declare function Dialog({ open, onClose, title, size, children, persistent, className, }: DialogProps): React__default.ReactPortal | null;
|
|
544
|
+
|
|
545
|
+
interface OGConfig {
|
|
546
|
+
/** 앱 이름 (좌상단 로고) */
|
|
547
|
+
appName?: string;
|
|
548
|
+
/** 브랜드 색상 (hex) */
|
|
549
|
+
color?: string;
|
|
550
|
+
/** 하단 도메인 */
|
|
551
|
+
domain?: string;
|
|
552
|
+
/** 배경 스타일 — "dark"(기본) | "gradient"(다크 오로라) | "blend"(라이트 파스텔 블렌드) */
|
|
553
|
+
bg?: "dark" | "gradient" | "blend";
|
|
554
|
+
/** 로고 이미지 URL (지정 시 appName 첫 글자 대신 이미지 표시) */
|
|
555
|
+
logoUrl?: string;
|
|
556
|
+
}
|
|
557
|
+
interface OGDefaultTemplate {
|
|
558
|
+
type?: "default";
|
|
559
|
+
title: string;
|
|
560
|
+
sub?: string;
|
|
561
|
+
badge?: string;
|
|
562
|
+
}
|
|
563
|
+
interface OGMatchTemplate {
|
|
564
|
+
type: "match";
|
|
565
|
+
home: string;
|
|
566
|
+
away: string;
|
|
567
|
+
score?: string;
|
|
568
|
+
sub?: string;
|
|
569
|
+
badge?: string;
|
|
570
|
+
}
|
|
571
|
+
interface OGSquareTemplate {
|
|
572
|
+
type: "square";
|
|
573
|
+
title: string;
|
|
574
|
+
sub?: string;
|
|
575
|
+
badge?: string;
|
|
576
|
+
}
|
|
577
|
+
interface OGIconTemplate {
|
|
578
|
+
type: "icon";
|
|
579
|
+
/** 아이콘에 표시할 글자 (기본: appName 첫 글자) */
|
|
580
|
+
letter?: string;
|
|
581
|
+
/** 아이콘 모서리 둥글기 (기본: 96) */
|
|
582
|
+
radius?: number;
|
|
583
|
+
}
|
|
584
|
+
interface OGArticleTemplate {
|
|
585
|
+
type: "article";
|
|
586
|
+
title: string;
|
|
587
|
+
/** 작성자 */
|
|
588
|
+
author?: string;
|
|
589
|
+
/** 날짜 또는 발행일 */
|
|
590
|
+
date?: string;
|
|
591
|
+
/** 카테고리 / 태그 */
|
|
592
|
+
category?: string;
|
|
593
|
+
sub?: string;
|
|
594
|
+
}
|
|
595
|
+
interface OGStatTemplate {
|
|
596
|
+
type: "stat";
|
|
597
|
+
/** 강조할 숫자 또는 지표 */
|
|
598
|
+
stat: string;
|
|
599
|
+
/** 지표 설명 */
|
|
600
|
+
label: string;
|
|
601
|
+
sub?: string;
|
|
602
|
+
badge?: string;
|
|
603
|
+
}
|
|
604
|
+
interface OGProductTemplate {
|
|
605
|
+
type: "product";
|
|
606
|
+
title: string;
|
|
607
|
+
tagline?: string;
|
|
608
|
+
/** 핵심 특징 (최대 3개) */
|
|
609
|
+
features?: string[];
|
|
610
|
+
badge?: string;
|
|
611
|
+
}
|
|
612
|
+
type OGTemplate = OGDefaultTemplate | OGMatchTemplate | OGSquareTemplate | OGIconTemplate | OGArticleTemplate | OGStatTemplate | OGProductTemplate;
|
|
613
|
+
type OGProps = OGTemplate & OGConfig;
|
|
614
|
+
declare function OGImage(props: OGProps): react_jsx_runtime.JSX.Element;
|
|
615
|
+
|
|
616
|
+
/** @vercel/og (Satori) fonts 옵션에 넘길 수 있는 폰트 정의 */
|
|
617
|
+
interface OGFont {
|
|
618
|
+
name: string;
|
|
619
|
+
data: ArrayBuffer;
|
|
620
|
+
weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
|
621
|
+
style?: "normal" | "italic";
|
|
622
|
+
}
|
|
623
|
+
declare const PRETENDARD_WEIGHTS: {
|
|
624
|
+
readonly 400: "Pretendard-Regular.otf";
|
|
625
|
+
readonly 700: "Pretendard-Bold.otf";
|
|
626
|
+
readonly 900: "Pretendard-Black.otf";
|
|
627
|
+
};
|
|
628
|
+
type PretendardWeight = keyof typeof PRETENDARD_WEIGHTS;
|
|
629
|
+
/**
|
|
630
|
+
* Pretendard 폰트를 CDN에서 로드합니다.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* import { OG, loadPretendard } from "@m1kapp/seo";
|
|
635
|
+
* import { ImageResponse } from "next/og";
|
|
636
|
+
*
|
|
637
|
+
* export async function GET() {
|
|
638
|
+
* const fonts = await loadPretendard();
|
|
639
|
+
* return new ImageResponse(<OG type="default" title="Hello" />, {
|
|
640
|
+
* width: 1200, height: 630, fonts,
|
|
641
|
+
* });
|
|
642
|
+
* }
|
|
643
|
+
* ```
|
|
644
|
+
*
|
|
645
|
+
* @param weights 로드할 weight 배열 (기본: [700, 900])
|
|
646
|
+
*/
|
|
647
|
+
declare function loadPretendard(weights?: PretendardWeight[]): Promise<OGFont[]>;
|
|
648
|
+
/**
|
|
649
|
+
* 임의의 URL에서 폰트를 로드합니다.
|
|
650
|
+
*
|
|
651
|
+
* @example
|
|
652
|
+
* ```ts
|
|
653
|
+
* const fonts = await loadFont({
|
|
654
|
+
* name: "CustomFont",
|
|
655
|
+
* url: "https://example.com/CustomFont-Bold.otf",
|
|
656
|
+
* weight: 700,
|
|
657
|
+
* });
|
|
658
|
+
* ```
|
|
659
|
+
*/
|
|
660
|
+
declare function loadFont(opts: {
|
|
661
|
+
name: string;
|
|
662
|
+
url: string;
|
|
663
|
+
weight?: OGFont["weight"];
|
|
664
|
+
style?: OGFont["style"];
|
|
665
|
+
}): Promise<OGFont>;
|
|
666
|
+
/**
|
|
667
|
+
* Google Fonts에서 폰트를 로드합니다.
|
|
668
|
+
* CSS 응답을 파싱해 TTF/OTF URL을 자동 추출합니다.
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* ```ts
|
|
672
|
+
* const fonts = await loadGoogleFont("Noto Sans KR", [400, 700]);
|
|
673
|
+
* return new ImageResponse(<OG ... />, { fonts });
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
declare function loadGoogleFont(family: string, weights?: OGFont["weight"][]): Promise<OGFont[]>;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @vercel/og ImageResponse에서 지원하는 이모지 스타일.
|
|
680
|
+
* ImageResponse 옵션의 `emoji` 필드에 넘기면 됩니다.
|
|
681
|
+
*/
|
|
682
|
+
type EmojiStyle = "twemoji" | "openmoji" | "noto" | "fluent" | "fluentFlat" | "blobmoji";
|
|
683
|
+
/**
|
|
684
|
+
* raw Satori의 `loadAdditionalAsset` 콜백을 생성합니다.
|
|
685
|
+
*
|
|
686
|
+
* @example
|
|
687
|
+
* ```ts
|
|
688
|
+
* import satori from "satori";
|
|
689
|
+
* import { createEmojiLoader } from "@m1kapp/seo";
|
|
690
|
+
*
|
|
691
|
+
* const svg = await satori(<OG ... />, {
|
|
692
|
+
* width: 1200, height: 630,
|
|
693
|
+
* fonts: [...],
|
|
694
|
+
* loadAdditionalAsset: createEmojiLoader(),
|
|
695
|
+
* });
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
declare function createEmojiLoader(): (code: string, segment: string) => Promise<string | undefined>;
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* OG 이미지 URL의 캐시를 즉시 무효화할 버전 키를 반환합니다.
|
|
702
|
+
*
|
|
703
|
+
* Cache-Control로 CDN에 캐싱된 OG 이미지를 강제 갱신하고 싶을 때
|
|
704
|
+
* URL의 `v` 파라미터에 이 값을 넣으세요.
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
* ```ts
|
|
708
|
+
* import { getOGVersion } from "@m1kapp/seo";
|
|
709
|
+
*
|
|
710
|
+
* // OG 갱신 버튼 핸들러
|
|
711
|
+
* const freshUrl = `/og?title=${title}&v=${getOGVersion()}`;
|
|
712
|
+
* // → /og?title=...&v=20260415152347
|
|
713
|
+
* ```
|
|
714
|
+
*/
|
|
715
|
+
declare function getOGVersion(): string;
|
|
716
|
+
|
|
717
|
+
type PWAInstallState = "android-ready" | "ios-safari" | "installed" | "unsupported";
|
|
718
|
+
interface UsePWAInstallReturn {
|
|
719
|
+
state: PWAInstallState;
|
|
720
|
+
/** Android only — triggers the native install dialog */
|
|
721
|
+
install(): Promise<void>;
|
|
722
|
+
}
|
|
723
|
+
declare function usePWAInstall(): UsePWAInstallReturn;
|
|
724
|
+
|
|
725
|
+
interface PWAInstallButtonProps {
|
|
726
|
+
/** App name shown in iOS guide sheet */
|
|
727
|
+
appName?: string;
|
|
728
|
+
/** App icon src shown in iOS guide (optional) */
|
|
729
|
+
iconSrc?: string;
|
|
730
|
+
/** Label for the install button. Default: "앱으로 설치" */
|
|
731
|
+
label?: string;
|
|
732
|
+
/** Label shown when already installed. Default: undefined — button hidden */
|
|
733
|
+
installedLabel?: string;
|
|
734
|
+
className?: string;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* PWA install button that handles both Android and iOS.
|
|
738
|
+
*
|
|
739
|
+
* - Android (Chrome): triggers the native install dialog
|
|
740
|
+
* - iOS (Safari): opens a step-by-step "Add to Home Screen" guide sheet
|
|
741
|
+
* - Already installed: hidden by default (show with installedLabel)
|
|
742
|
+
* - Unsupported: hidden
|
|
743
|
+
*
|
|
744
|
+
* Usage:
|
|
745
|
+
* <PWAInstallButton appName="My App" iconSrc="/icon.png" />
|
|
746
|
+
*/
|
|
747
|
+
declare function PWAInstallButton({ appName, iconSrc, label, installedLabel, className, }: PWAInstallButtonProps): react_jsx_runtime.JSX.Element | null;
|
|
748
|
+
interface IOSInstallSheetProps {
|
|
749
|
+
open: boolean;
|
|
750
|
+
onClose(): void;
|
|
751
|
+
appName: string;
|
|
752
|
+
iconSrc?: string;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Bottom-sheet guide for iOS "Add to Home Screen".
|
|
756
|
+
* Can also be used standalone if you need custom trigger UI.
|
|
757
|
+
*/
|
|
758
|
+
declare function IOSInstallSheet({ open, onClose, appName, iconSrc }: IOSInstallSheetProps): react_jsx_runtime.JSX.Element | null;
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Standard mobile viewport config for Next.js.
|
|
762
|
+
* - Disables pinch zoom via maximumScale (Android, older iOS)
|
|
763
|
+
* - Prevents input auto-zoom on iOS via the CSS in @m1kapp/kit (font-size: max(16px, 1em))
|
|
764
|
+
* - touch-action: pan-x pan-y in CSS handles iOS 10+ pinch zoom
|
|
765
|
+
*
|
|
766
|
+
* Usage: export const viewport = mobileViewport;
|
|
767
|
+
*/
|
|
768
|
+
declare const mobileViewport: Viewport;
|
|
769
|
+
/**
|
|
770
|
+
* Generates an SVG icon as a data URI — no image files needed.
|
|
771
|
+
*
|
|
772
|
+
* Usage:
|
|
773
|
+
* svgIcon("m1k", { size: 192, bg: "#0f172a" })
|
|
774
|
+
* svgIcon("WP", { size: 512, bg: "#18181b", radius: 0.25 })
|
|
775
|
+
*/
|
|
776
|
+
declare function svgIcon(text: string, options?: {
|
|
777
|
+
size?: number;
|
|
778
|
+
bg?: string;
|
|
779
|
+
color?: string;
|
|
780
|
+
/** Corner radius as a fraction of size. Default: 0.25 (25%) */
|
|
781
|
+
radius?: number;
|
|
782
|
+
/** Font size as a fraction of size. Default: 0.375 */
|
|
783
|
+
fontSize?: number;
|
|
784
|
+
}): string;
|
|
785
|
+
/**
|
|
786
|
+
* Generates a Next.js web app manifest (app/manifest.ts).
|
|
787
|
+
* Icons are auto-generated as inline SVGs — no image files needed.
|
|
788
|
+
*
|
|
789
|
+
* Usage:
|
|
790
|
+
* // app/manifest.ts
|
|
791
|
+
* import { createManifest } from "@m1kapp/kit";
|
|
792
|
+
* export default createManifest({
|
|
793
|
+
* name: "m1k",
|
|
794
|
+
* shortName: "m1k",
|
|
795
|
+
* themeColor: "#0f172a",
|
|
796
|
+
* icon: { text: "m1k" },
|
|
797
|
+
* });
|
|
798
|
+
*/
|
|
799
|
+
declare function createManifest(options: {
|
|
800
|
+
name: string;
|
|
801
|
+
shortName?: string;
|
|
802
|
+
description?: string;
|
|
803
|
+
startUrl?: string;
|
|
804
|
+
backgroundColor?: string;
|
|
805
|
+
themeColor?: string;
|
|
806
|
+
/** Text-based icon config — generates SVG icons automatically */
|
|
807
|
+
icon?: {
|
|
808
|
+
text: string;
|
|
809
|
+
bg?: string;
|
|
810
|
+
color?: string;
|
|
811
|
+
radius?: number;
|
|
812
|
+
};
|
|
813
|
+
}): MetadataRoute.Manifest;
|
|
814
|
+
|
|
815
|
+
declare class ApiError extends Error {
|
|
816
|
+
readonly status: number;
|
|
817
|
+
readonly statusText: string;
|
|
818
|
+
readonly body: unknown;
|
|
819
|
+
constructor(status: number, statusText: string, body: unknown);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
interface ApiClientOptions {
|
|
823
|
+
/** Default headers merged into every request */
|
|
824
|
+
headers?: Record<string, string>;
|
|
825
|
+
/** Called before every request — mutate or replace the Request */
|
|
826
|
+
onRequest?: (req: Request) => Request | void;
|
|
827
|
+
/** Called on every non-2xx response */
|
|
828
|
+
onError?: (err: ApiError) => void;
|
|
829
|
+
}
|
|
830
|
+
type RequestOptions = {
|
|
831
|
+
headers?: Record<string, string>;
|
|
832
|
+
signal?: AbortSignal;
|
|
833
|
+
/** Override default Content-Type (e.g. for FormData uploads) */
|
|
834
|
+
contentType?: string | null;
|
|
835
|
+
};
|
|
836
|
+
declare function createApiClient(baseUrl: string, defaults?: ApiClientOptions): {
|
|
837
|
+
get: <T>(path: string, opts?: RequestOptions) => Promise<T>;
|
|
838
|
+
post: <T>(path: string, body?: unknown, opts?: RequestOptions) => Promise<T>;
|
|
839
|
+
put: <T>(path: string, body?: unknown, opts?: RequestOptions) => Promise<T>;
|
|
840
|
+
patch: <T>(path: string, body?: unknown, opts?: RequestOptions) => Promise<T>;
|
|
841
|
+
delete: <T>(path: string, opts?: RequestOptions) => Promise<T>;
|
|
842
|
+
};
|
|
843
|
+
type ApiClient = ReturnType<typeof createApiClient>;
|
|
844
|
+
|
|
845
|
+
/** Manually invalidate cache entries. Pass a URL to clear one, or nothing to clear all. */
|
|
846
|
+
declare function clearFetchCache(url?: string): void;
|
|
847
|
+
interface UseFetchOptions<T> {
|
|
848
|
+
/** Skip fetching when false (default: true) */
|
|
849
|
+
enabled?: boolean;
|
|
850
|
+
/** Cache duration in ms. 0 = no cache (default: 0) */
|
|
851
|
+
staleTime?: number;
|
|
852
|
+
/** Number of retries on network error (default: 2) */
|
|
853
|
+
retry?: number;
|
|
854
|
+
/** Base delay between retries in ms — doubles each attempt (default: 1000) */
|
|
855
|
+
retryDelay?: number;
|
|
856
|
+
/** Re-fetch when window regains focus (default: true) */
|
|
857
|
+
revalidateOnFocus?: boolean;
|
|
858
|
+
/** Custom fetcher — defaults to fetch() with JSON/text auto-parse */
|
|
859
|
+
fetcher?: (url: string) => Promise<T>;
|
|
860
|
+
onSuccess?: (data: T) => void;
|
|
861
|
+
onError?: (err: Error) => void;
|
|
862
|
+
}
|
|
863
|
+
interface UseFetchResult<T> {
|
|
864
|
+
data: T | undefined;
|
|
865
|
+
loading: boolean;
|
|
866
|
+
error: Error | undefined;
|
|
867
|
+
/** Manually trigger a refetch (ignores cache) */
|
|
868
|
+
refetch: () => void;
|
|
869
|
+
}
|
|
870
|
+
declare function useFetch<T>(url: string | null | undefined, options?: UseFetchOptions<T>): UseFetchResult<T>;
|
|
871
|
+
|
|
872
|
+
interface UsePollingOptions<T> {
|
|
873
|
+
/** Polling interval in ms (default: 5000) */
|
|
874
|
+
interval?: number;
|
|
875
|
+
/** Start polling immediately (default: true) */
|
|
876
|
+
enabled?: boolean;
|
|
877
|
+
/** Pause polling when the tab is hidden (default: true) */
|
|
878
|
+
pauseOnHidden?: boolean;
|
|
879
|
+
onSuccess?: (data: T) => void;
|
|
880
|
+
onError?: (err: Error) => void;
|
|
881
|
+
}
|
|
882
|
+
interface UsePollingResult<T> {
|
|
883
|
+
data: T | undefined;
|
|
884
|
+
loading: boolean;
|
|
885
|
+
error: Error | undefined;
|
|
886
|
+
/** Whether polling is currently active */
|
|
887
|
+
isRunning: boolean;
|
|
888
|
+
stop: () => void;
|
|
889
|
+
start: () => void;
|
|
890
|
+
}
|
|
891
|
+
declare function usePolling<T>(fetcher: () => Promise<T>, options?: UsePollingOptions<T>): UsePollingResult<T>;
|
|
892
|
+
|
|
893
|
+
declare function relativeTime(date: Date | string | number): string;
|
|
894
|
+
declare function formatNumber(n: number): string;
|
|
895
|
+
declare function formatPrice(amount: number, currency?: string, locale?: string): string;
|
|
896
|
+
declare function cn(...classes: (string | undefined | null | false | 0)[]): string;
|
|
897
|
+
|
|
898
|
+
export { type ApiClient, type ApiClientOptions, ApiError, AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, type ColorName, Dialog, type DialogProps, Divider, EmojiButton, type EmojiButtonProps, EmojiPicker, type EmojiPickerProps, type EmojiStyle, EmptyState, type EmptyStateProps, type FontName, GrassMap, type GrassMapData, type GrassMapProps, IOSInstallSheet, type OGArticleTemplate, type OGConfig, type OGDefaultTemplate, type OGFont, type OGIconTemplate, OGImage, type OGMatchTemplate, type OGProductTemplate, type OGProps, type OGSquareTemplate, type OGStatTemplate, type OGTemplate, PWAInstallButton, type PWAInstallState, Section, SectionHeader, type SectionHeaderProps, type SectionProps, ShareButton, type ShareButtonProps, Skeleton, type SkeletonProps, StatChip, type StatChipProps, THEME_SCRIPT, Tab, TabBar, type TabBarProps, type TabProps, ThemeButton, type ThemeButtonProps, ThemeDialog, type ThemeDialogProps, type ToastOptions, ToastProvider, type ToastVariant, Tooltip, type TooltipProps, Typewriter, type TypewriterProps, type UseFetchOptions, type UseFetchResult, type UseFormSubmitOptions, type UseFormSubmitResult, type UseInViewOptions, type UseInViewResult, type UsePWAInstallReturn, type UsePollingOptions, type UsePollingResult, type UseShareOptions, type UseShareReturn, Watermark, type WatermarkProps, type WatermarkSponsor, clearFetchCache, cn, colors, createApiClient, createEmojiLoader, createManifest, fontFamily, fonts, formatNumber, formatPrice, getOGVersion, loadFont, loadGoogleFont, loadPretendard, mobileViewport, relativeTime, svgIcon, useDebounce, useFetch, useFormSubmit, useInView, useLocalStorage, usePWAInstall, usePolling, useShare, useToast };
|