@mr-m/telegram-webapp-kit 2.0.0 β 2.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/LICENSE +21 -0
- package/README.md +706 -137
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mr-m-apps
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,25 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# π± Telegram WebApp Kit
|
|
2
2
|
|
|
3
|
-
Full-featured Telegram Mini
|
|
4
|
-
|
|
3
|
+
**Full-featured SDK for building Telegram Mini Apps with React & Next.js**
|
|
4
|
+
|
|
5
|
+
A zero-dependency React library that provides complete access to the Telegram WebApp API with type-safe hooks, providers, and utilities.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@mr-m/telegram-webapp-kit)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](#peerDependencies)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## β‘ Features
|
|
14
|
+
|
|
15
|
+
β
**Complete TypeScript Support** β Fully typed hooks, providers, and utilities
|
|
16
|
+
β
**Zero Dependencies** β Only requires React (no extra packages)
|
|
17
|
+
β
**React & Next.js Ready** β Works with both App Router and Pages Router
|
|
18
|
+
β
**Safe Areas Handling** β Built-in support for notches and safe areas
|
|
19
|
+
β
**Haptic Feedback** β Impact, notification, and selection feedback
|
|
20
|
+
β
**Device Integration** β Accelerometer, gyroscope, device orientation
|
|
21
|
+
β
**Biometric Authentication** β Face ID / fingerprint support
|
|
22
|
+
β
**Location & Geolocation** β Get user location with proper permissions
|
|
23
|
+
β
**Cloud Storage** β Promise-based cloud storage wrapper
|
|
24
|
+
β
**Button Management** β Main, secondary, back, and settings buttons
|
|
25
|
+
β
**Theme & Color Scheme** β Dark/light mode detection
|
|
26
|
+
β
**Event Subscriptions** β Subscribe to any Telegram WebApp event
|
|
27
|
+
β
**Development Bypass** β Test locally without Telegram
|
|
5
28
|
|
|
6
29
|
---
|
|
7
30
|
|
|
8
|
-
##
|
|
31
|
+
## π¦ Installation
|
|
9
32
|
|
|
33
|
+
### npm
|
|
10
34
|
```bash
|
|
11
35
|
npm install @mr-m/telegram-webapp-kit
|
|
12
|
-
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### pnpm
|
|
39
|
+
```bash
|
|
13
40
|
pnpm add @mr-m/telegram-webapp-kit
|
|
14
41
|
```
|
|
15
42
|
|
|
16
|
-
|
|
43
|
+
### yarn
|
|
44
|
+
```bash
|
|
45
|
+
yarn add @mr-m/telegram-webapp-kit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### bun
|
|
49
|
+
```bash
|
|
50
|
+
bun add @mr-m/telegram-webapp-kit
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## π Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Add Telegram Script
|
|
58
|
+
|
|
59
|
+
Add the official Telegram WebApp script to your `layout.tsx` (Next.js App Router):
|
|
17
60
|
|
|
18
61
|
```tsx
|
|
19
|
-
//
|
|
62
|
+
// app/layout.tsx
|
|
20
63
|
import Script from 'next/script';
|
|
21
64
|
|
|
22
|
-
export default function RootLayout({ children }) {
|
|
65
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
23
66
|
return (
|
|
24
67
|
<html>
|
|
25
68
|
<head>
|
|
@@ -34,11 +77,9 @@ export default function RootLayout({ children }) {
|
|
|
34
77
|
}
|
|
35
78
|
```
|
|
36
79
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## Setup Providers
|
|
80
|
+
### 2. Wrap App with Providers
|
|
40
81
|
|
|
41
|
-
|
|
82
|
+
Create a providers component and wrap your app:
|
|
42
83
|
|
|
43
84
|
```tsx
|
|
44
85
|
// app/providers.tsx
|
|
@@ -50,16 +91,14 @@ import {
|
|
|
50
91
|
} from '@mr-m/telegram-webapp-kit';
|
|
51
92
|
|
|
52
93
|
const telegramOptions: TelegramProviderOptions = {
|
|
53
|
-
langStorageKey: 'my-app-lang', // localStorage key for saved language
|
|
54
94
|
onUserReady: (user) => {
|
|
55
|
-
//
|
|
95
|
+
// Called when user data is loaded
|
|
96
|
+
console.log('User ready:', user);
|
|
97
|
+
// Sync user to your backend
|
|
56
98
|
fetch('/api/user', { method: 'POST', body: JSON.stringify(user) });
|
|
57
99
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
},
|
|
61
|
-
loadingComponent: <div>Loadingβ¦</div>,
|
|
62
|
-
notInTelegramComponent: <div>Open in Telegram</div>,
|
|
100
|
+
loadingComponent: <div className="p-4">Loading Mini App...</div>,
|
|
101
|
+
notInTelegramComponent: <div className="p-4">Please open in Telegram</div>,
|
|
63
102
|
};
|
|
64
103
|
|
|
65
104
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
@@ -73,218 +112,528 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
73
112
|
}
|
|
74
113
|
```
|
|
75
114
|
|
|
76
|
-
|
|
115
|
+
Use providers in layout:
|
|
77
116
|
|
|
78
|
-
|
|
117
|
+
```tsx
|
|
118
|
+
// app/layout.tsx
|
|
119
|
+
import { Providers } from './providers';
|
|
120
|
+
import './globals.css';
|
|
79
121
|
|
|
80
|
-
|
|
81
|
-
|
|
122
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
123
|
+
return (
|
|
124
|
+
<html>
|
|
125
|
+
<body>
|
|
126
|
+
<Providers>
|
|
127
|
+
<div className="app-container">
|
|
128
|
+
{children}
|
|
129
|
+
</div>
|
|
130
|
+
</Providers>
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Use Hooks
|
|
138
|
+
|
|
139
|
+
Access Telegram features in your components:
|
|
82
140
|
|
|
83
141
|
```tsx
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
142
|
+
'use client';
|
|
143
|
+
import { useTelegram, useTelegramMainButton } from '@mr-m/telegram-webapp-kit';
|
|
144
|
+
|
|
145
|
+
export default function Home() {
|
|
146
|
+
const { user, colorScheme, ready } = useTelegram();
|
|
147
|
+
|
|
148
|
+
useTelegramMainButton({
|
|
149
|
+
text: 'Continue',
|
|
150
|
+
onClick: () => console.log('Clicked!'),
|
|
151
|
+
isVisible: ready,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div>
|
|
156
|
+
<h1>Hello {user?.first_name}!</h1>
|
|
157
|
+
<p>Theme: {colorScheme}</p>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
97
161
|
```
|
|
98
162
|
|
|
99
163
|
---
|
|
100
164
|
|
|
101
|
-
|
|
102
|
-
|
|
165
|
+
## π― Core Concepts
|
|
166
|
+
|
|
167
|
+
### Safe Areas & Fullscreen
|
|
168
|
+
|
|
169
|
+
The `FullscreenProvider` automatically injects CSS variables for notches and safe areas:
|
|
170
|
+
|
|
171
|
+
```css
|
|
172
|
+
:root {
|
|
173
|
+
/* Safe area (includes notch, keyboard, etc.) */
|
|
174
|
+
--tg-safe-area-inset-top: 0px;
|
|
175
|
+
--tg-safe-area-inset-bottom: 0px;
|
|
176
|
+
--tg-safe-area-inset-left: 0px;
|
|
177
|
+
--tg-safe-area-inset-right: 0px;
|
|
178
|
+
|
|
179
|
+
/* Content safe area (keyboard, etc. but not notch) */
|
|
180
|
+
--tg-content-safe-area-inset-top: 0px;
|
|
181
|
+
--tg-content-safe-area-inset-bottom: 0px;
|
|
182
|
+
--tg-content-safe-area-inset-left: 0px;
|
|
183
|
+
--tg-content-safe-area-inset-right: 0px;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### App Container
|
|
188
|
+
|
|
189
|
+
Wrap your main content with `.app-container` to handle safe areas automatically:
|
|
190
|
+
|
|
191
|
+
```css
|
|
192
|
+
.app-container {
|
|
193
|
+
position: relative;
|
|
194
|
+
min-height: 100vh;
|
|
195
|
+
padding-top: calc(var(--tg-safe-area-inset-top, 0px) + var(--tg-content-safe-area-inset-top, 0px));
|
|
196
|
+
padding-bottom: calc(var(--tg-safe-area-inset-bottom, 0px) + var(--tg-content-safe-area-inset-bottom, 0px));
|
|
197
|
+
padding-left: calc(var(--tg-safe-area-inset-left, 0px) + var(--tg-content-safe-area-inset-left, 0px));
|
|
198
|
+
padding-right: calc(var(--tg-safe-area-inset-right, 0px) + var(--tg-content-safe-area-inset-right, 0px));
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Fixed Elements
|
|
203
|
+
|
|
204
|
+
For headers, footers, or floating buttons:
|
|
205
|
+
|
|
206
|
+
```css
|
|
207
|
+
/* Fixed header with safe area */
|
|
208
|
+
.fixed-header {
|
|
209
|
+
position: fixed;
|
|
210
|
+
top: 0;
|
|
211
|
+
left: 0;
|
|
212
|
+
right: 0;
|
|
213
|
+
padding-top: var(--tg-safe-area-inset-top, 0px);
|
|
214
|
+
z-index: 100;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Fixed bottom navigation */
|
|
218
|
+
.bottom-nav {
|
|
219
|
+
position: fixed;
|
|
220
|
+
bottom: 0;
|
|
221
|
+
left: 0;
|
|
222
|
+
right: 0;
|
|
223
|
+
padding-bottom: var(--tg-safe-area-inset-bottom, 0px);
|
|
224
|
+
z-index: 100;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Floating action button */
|
|
228
|
+
.fab {
|
|
229
|
+
position: fixed;
|
|
230
|
+
bottom: calc(20px + var(--tg-safe-area-inset-bottom, 0px));
|
|
231
|
+
right: calc(20px + var(--tg-safe-area-inset-right, 0px));
|
|
232
|
+
z-index: 50;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Fullscreen content */
|
|
236
|
+
.fullscreen-content {
|
|
237
|
+
position: fixed;
|
|
238
|
+
top: var(--tg-safe-area-inset-top, 0px);
|
|
239
|
+
left: var(--tg-safe-area-inset-left, 0px);
|
|
240
|
+
right: var(--tg-safe-area-inset-right, 0px);
|
|
241
|
+
bottom: var(--tg-safe-area-inset-bottom, 0px);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## πͺ Hooks API
|
|
248
|
+
|
|
249
|
+
### useTelegram()
|
|
250
|
+
|
|
251
|
+
Main hook for Telegram context and WebApp instance:
|
|
103
252
|
|
|
104
253
|
```tsx
|
|
105
|
-
|
|
254
|
+
const {
|
|
255
|
+
ready, // boolean β SDK initialized?
|
|
256
|
+
inTelegram, // boolean β Running in Telegram?
|
|
257
|
+
bypass, // boolean β Dev bypass mode active?
|
|
258
|
+
webApp, // TgWebApp | null β Raw WebApp instance
|
|
259
|
+
user, // TgUser | null β Current user
|
|
260
|
+
colorScheme, // 'light' | 'dark' β User's theme preference
|
|
261
|
+
startParam, // string | null β Deep link parameter
|
|
262
|
+
} = useTelegram();
|
|
263
|
+
```
|
|
106
264
|
|
|
107
|
-
|
|
108
|
-
|
|
265
|
+
**Example:**
|
|
266
|
+
```tsx
|
|
267
|
+
const { user, ready, inTelegram } = useTelegram();
|
|
109
268
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
hideOnRoot: true, // default: true
|
|
115
|
-
});
|
|
269
|
+
if (!ready) return <div>Loading...</div>;
|
|
270
|
+
if (!inTelegram) return <div>Open this in Telegram</div>;
|
|
271
|
+
|
|
272
|
+
return <h1>Welcome {user?.first_name}</h1>;
|
|
116
273
|
```
|
|
117
274
|
|
|
118
275
|
---
|
|
119
276
|
|
|
120
|
-
###
|
|
121
|
-
|
|
277
|
+
### useTelegramMainButton()
|
|
278
|
+
|
|
279
|
+
Control the main action button at the bottom:
|
|
122
280
|
|
|
123
281
|
```tsx
|
|
124
282
|
useTelegramMainButton({
|
|
125
|
-
text: 'Continue',
|
|
126
|
-
onClick:
|
|
127
|
-
isVisible:
|
|
128
|
-
isActive: !isLoading,
|
|
129
|
-
showProgress: isLoading,
|
|
130
|
-
color: '#2481cc',
|
|
131
|
-
hasShineEffect: true,
|
|
283
|
+
text: 'Continue', // Button label
|
|
284
|
+
onClick: handleClick, // Click handler
|
|
285
|
+
isVisible: true, // Show/hide button
|
|
286
|
+
isActive: !isLoading, // Enable/disable
|
|
287
|
+
showProgress: isLoading, // Show loading indicator
|
|
288
|
+
color: '#2481cc', // Button color (optional)
|
|
289
|
+
hasShineEffect: true, // Shine animation (optional)
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Example:**
|
|
294
|
+
```tsx
|
|
295
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
296
|
+
|
|
297
|
+
useTelegramMainButton({
|
|
298
|
+
text: 'Submit Form',
|
|
299
|
+
onClick: async () => {
|
|
300
|
+
setIsSubmitting(true);
|
|
301
|
+
await submitForm();
|
|
302
|
+
setIsSubmitting(false);
|
|
303
|
+
},
|
|
304
|
+
isActive: !isSubmitting,
|
|
305
|
+
showProgress: isSubmitting,
|
|
132
306
|
});
|
|
133
307
|
```
|
|
134
308
|
|
|
135
309
|
---
|
|
136
310
|
|
|
137
|
-
###
|
|
311
|
+
### useTelegramSecondaryButton()
|
|
312
|
+
|
|
313
|
+
Secondary button (often on the left):
|
|
314
|
+
|
|
138
315
|
```tsx
|
|
139
316
|
useTelegramSecondaryButton({
|
|
140
317
|
text: 'Cancel',
|
|
141
318
|
onClick: handleCancel,
|
|
142
|
-
position: 'left',
|
|
319
|
+
position: 'left', // 'left' | 'right'
|
|
143
320
|
isVisible: true,
|
|
144
321
|
});
|
|
145
322
|
```
|
|
146
323
|
|
|
147
324
|
---
|
|
148
325
|
|
|
149
|
-
###
|
|
326
|
+
### useTelegramBackButton()
|
|
327
|
+
|
|
328
|
+
Auto-manages back button based on route:
|
|
329
|
+
|
|
150
330
|
```tsx
|
|
151
|
-
|
|
331
|
+
import { useRouter } from 'next/navigation';
|
|
332
|
+
import { useTelegramBackButton } from '@mr-m/telegram-webapp-kit';
|
|
333
|
+
|
|
334
|
+
export default function Page() {
|
|
335
|
+
const router = useRouter();
|
|
336
|
+
|
|
337
|
+
useTelegramBackButton({
|
|
338
|
+
pathname: '/current-page',
|
|
339
|
+
onBack: () => router.back(),
|
|
340
|
+
hideOnRoot: true, // Hide on home page
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
return <div>Your content</div>;
|
|
344
|
+
}
|
|
152
345
|
```
|
|
153
346
|
|
|
154
347
|
---
|
|
155
348
|
|
|
156
|
-
###
|
|
349
|
+
### useTelegramSettingsButton()
|
|
350
|
+
|
|
351
|
+
Handle settings button click:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { useRouter } from 'next/navigation';
|
|
355
|
+
import { useTelegramSettingsButton } from '@mr-m/telegram-webapp-kit';
|
|
356
|
+
|
|
357
|
+
export default function Page() {
|
|
358
|
+
const router = useRouter();
|
|
359
|
+
|
|
360
|
+
useTelegramSettingsButton(() => {
|
|
361
|
+
router.push('/settings');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return <div>Your content</div>;
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### useHapticFeedback()
|
|
371
|
+
|
|
372
|
+
Trigger haptic feedback (vibration):
|
|
373
|
+
|
|
157
374
|
```tsx
|
|
158
375
|
const haptic = useHapticFeedback();
|
|
159
376
|
|
|
160
|
-
|
|
161
|
-
haptic.
|
|
162
|
-
haptic.
|
|
377
|
+
// Impact vibrations
|
|
378
|
+
haptic.impact('light'); // Light tap
|
|
379
|
+
haptic.impact('medium'); // Medium tap
|
|
380
|
+
haptic.impact('heavy'); // Strong tap
|
|
381
|
+
haptic.impact('rigid'); // Rigid tap
|
|
382
|
+
haptic.impact('soft'); // Soft tap
|
|
383
|
+
|
|
384
|
+
// Notifications
|
|
385
|
+
haptic.notification('success'); // Success pattern
|
|
386
|
+
haptic.notification('warning'); // Warning pattern
|
|
387
|
+
haptic.notification('error'); // Error pattern
|
|
388
|
+
|
|
389
|
+
// Selection
|
|
390
|
+
haptic.selectionChanged(); // Selection feedback
|
|
163
391
|
```
|
|
164
392
|
|
|
165
|
-
Or use
|
|
393
|
+
**Or use static methods:**
|
|
166
394
|
```tsx
|
|
167
395
|
import { haptic } from '@mr-m/telegram-webapp-kit';
|
|
396
|
+
|
|
168
397
|
haptic.success();
|
|
398
|
+
haptic.error();
|
|
399
|
+
haptic.warning();
|
|
169
400
|
```
|
|
170
401
|
|
|
171
402
|
---
|
|
172
403
|
|
|
173
|
-
###
|
|
404
|
+
### useTelegramTheme()
|
|
405
|
+
|
|
406
|
+
Get theme and color information:
|
|
407
|
+
|
|
174
408
|
```tsx
|
|
175
409
|
const { colorScheme, themeParams, isDark } = useTelegramTheme();
|
|
410
|
+
|
|
411
|
+
console.log(isDark ? 'Dark mode' : 'Light mode');
|
|
412
|
+
console.log('Primary color:', themeParams?.button_color);
|
|
176
413
|
```
|
|
177
414
|
|
|
178
415
|
---
|
|
179
416
|
|
|
180
|
-
###
|
|
417
|
+
### useTelegramViewport()
|
|
418
|
+
|
|
419
|
+
Manage WebApp viewport:
|
|
420
|
+
|
|
181
421
|
```tsx
|
|
182
422
|
const { height, stableHeight, isExpanded, expand } = useTelegramViewport();
|
|
423
|
+
|
|
424
|
+
// Expand webview to fill available height
|
|
425
|
+
expand();
|
|
183
426
|
```
|
|
184
427
|
|
|
185
428
|
---
|
|
186
429
|
|
|
187
|
-
###
|
|
430
|
+
### useTelegramFullscreen()
|
|
431
|
+
|
|
432
|
+
Control fullscreen mode:
|
|
433
|
+
|
|
188
434
|
```tsx
|
|
189
435
|
const { isFullscreen, isSupported, enter, exit, toggle, error } = useTelegramFullscreen();
|
|
436
|
+
|
|
437
|
+
if (isSupported) {
|
|
438
|
+
toggle(); // Toggle fullscreen
|
|
439
|
+
}
|
|
190
440
|
```
|
|
191
441
|
|
|
192
|
-
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### useFullscreen()
|
|
445
|
+
|
|
446
|
+
Extended fullscreen from `FullscreenProvider` (includes safe areas):
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
const { isFullscreen, error, safeArea } = useFullscreen();
|
|
450
|
+
```
|
|
193
451
|
|
|
194
452
|
---
|
|
195
453
|
|
|
196
|
-
###
|
|
454
|
+
### useSafeArea()
|
|
455
|
+
|
|
456
|
+
Get safe area values and CSS variables:
|
|
457
|
+
|
|
197
458
|
```tsx
|
|
198
459
|
const { safeArea, contentSafeArea } = useSafeArea();
|
|
199
|
-
// safeArea.top, .bottom, .left, .right β in pixels
|
|
200
460
|
|
|
201
|
-
//
|
|
202
|
-
//
|
|
461
|
+
console.log(safeArea.top); // Top inset (includes notch)
|
|
462
|
+
console.log(contentSafeArea.bottom); // Bottom inset (keyboard safe)
|
|
203
463
|
```
|
|
204
464
|
|
|
205
465
|
---
|
|
206
466
|
|
|
207
|
-
###
|
|
208
|
-
|
|
467
|
+
### useCloudStorage()
|
|
468
|
+
|
|
469
|
+
Promise-based cloud storage (persists across devices):
|
|
209
470
|
|
|
210
471
|
```tsx
|
|
211
472
|
const storage = useCloudStorage();
|
|
212
473
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
474
|
+
// Set value
|
|
475
|
+
await storage.setItem('user-settings', JSON.stringify({ theme: 'dark' }));
|
|
476
|
+
|
|
477
|
+
// Get value
|
|
478
|
+
const settings = await storage.getItem('user-settings');
|
|
479
|
+
|
|
480
|
+
// Get multiple values
|
|
481
|
+
const [val1, val2] = await storage.getItems(['key1', 'key2']);
|
|
482
|
+
|
|
483
|
+
// Remove value
|
|
484
|
+
await storage.removeItem('key1');
|
|
485
|
+
|
|
486
|
+
// Get all keys
|
|
487
|
+
const allKeys = await storage.getKeys();
|
|
218
488
|
```
|
|
219
489
|
|
|
220
490
|
---
|
|
221
491
|
|
|
222
|
-
###
|
|
492
|
+
### useAccelerometer()
|
|
493
|
+
|
|
494
|
+
Device accelerometer (motion sensor):
|
|
495
|
+
|
|
223
496
|
```tsx
|
|
224
497
|
const { x, y, z, isStarted, start, stop } = useAccelerometer({
|
|
225
|
-
refreshRate: 50,
|
|
498
|
+
refreshRate: 50, // Hz
|
|
499
|
+
autoStart: true, // Start automatically
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Manual control
|
|
503
|
+
start();
|
|
504
|
+
stop();
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
### useGyroscope()
|
|
510
|
+
|
|
511
|
+
Device gyroscope (rotation sensor):
|
|
512
|
+
|
|
513
|
+
```tsx
|
|
514
|
+
const { x, y, z, isStarted, start, stop } = useGyroscope({
|
|
226
515
|
autoStart: true,
|
|
227
516
|
});
|
|
517
|
+
```
|
|
228
518
|
|
|
229
|
-
|
|
519
|
+
---
|
|
230
520
|
|
|
231
|
-
|
|
521
|
+
### useDeviceOrientation()
|
|
522
|
+
|
|
523
|
+
Device orientation (compass):
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
const { alpha, beta, gamma, absolute, isStarted, start, stop } = useDeviceOrientation({
|
|
232
527
|
needAbsolute: true,
|
|
233
528
|
autoStart: true,
|
|
234
529
|
});
|
|
530
|
+
|
|
531
|
+
// alpha: 0-360Β° (compass bearing)
|
|
532
|
+
// beta: -180 to 180Β° (tilt front/back)
|
|
533
|
+
// gamma: -90 to 90Β° (tilt left/right)
|
|
235
534
|
```
|
|
236
535
|
|
|
237
536
|
---
|
|
238
537
|
|
|
239
|
-
###
|
|
538
|
+
### useBiometric()
|
|
539
|
+
|
|
540
|
+
Biometric authentication (Face ID / Fingerprint):
|
|
541
|
+
|
|
240
542
|
```tsx
|
|
241
543
|
const { isAvailable, biometricType, init, requestAccess, authenticate } = useBiometric();
|
|
242
544
|
|
|
545
|
+
// Initialize
|
|
243
546
|
await init();
|
|
244
|
-
|
|
245
|
-
|
|
547
|
+
|
|
548
|
+
// Request permission
|
|
549
|
+
const granted = await requestAccess('Authenticate to unlock premium features');
|
|
550
|
+
|
|
551
|
+
// Authenticate
|
|
552
|
+
if (granted) {
|
|
553
|
+
const { authenticated, token } = await authenticate('Confirm your identity');
|
|
554
|
+
if (authenticated) {
|
|
555
|
+
console.log('Auth token:', token);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
246
558
|
```
|
|
247
559
|
|
|
248
560
|
---
|
|
249
561
|
|
|
250
|
-
###
|
|
562
|
+
### useLocation()
|
|
563
|
+
|
|
564
|
+
Get user location:
|
|
565
|
+
|
|
251
566
|
```tsx
|
|
252
567
|
const { isAvailable, isGranted, init, getLocation, openSettings } = useLocation();
|
|
253
568
|
|
|
569
|
+
// Initialize permission
|
|
254
570
|
await init();
|
|
255
|
-
|
|
256
|
-
//
|
|
571
|
+
|
|
572
|
+
// Request permission
|
|
573
|
+
if (isGranted) {
|
|
574
|
+
const location = await getLocation();
|
|
575
|
+
console.log('Latitude:', location.latitude);
|
|
576
|
+
console.log('Longitude:', location.longitude);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Open settings to grant permission
|
|
580
|
+
openSettings();
|
|
257
581
|
```
|
|
258
582
|
|
|
259
583
|
---
|
|
260
584
|
|
|
261
|
-
###
|
|
585
|
+
### useHomeScreen()
|
|
586
|
+
|
|
587
|
+
Add app to home screen:
|
|
588
|
+
|
|
262
589
|
```tsx
|
|
263
590
|
const { addToHomeScreen, checkStatus } = useHomeScreen();
|
|
264
|
-
|
|
591
|
+
|
|
592
|
+
const status = await checkStatus();
|
|
593
|
+
if (status === 'ok') {
|
|
594
|
+
await addToHomeScreen(); // Shows system dialog
|
|
595
|
+
}
|
|
265
596
|
```
|
|
266
597
|
|
|
267
598
|
---
|
|
268
599
|
|
|
269
|
-
###
|
|
270
|
-
|
|
600
|
+
### useIsActive()
|
|
601
|
+
|
|
602
|
+
Track if Mini App is in foreground:
|
|
603
|
+
|
|
271
604
|
```tsx
|
|
272
605
|
const isActive = useIsActive();
|
|
606
|
+
|
|
607
|
+
useEffect(() => {
|
|
608
|
+
if (isActive) {
|
|
609
|
+
console.log('App is visible');
|
|
610
|
+
} else {
|
|
611
|
+
console.log('App is backgrounded');
|
|
612
|
+
}
|
|
613
|
+
}, [isActive]);
|
|
273
614
|
```
|
|
274
615
|
|
|
275
616
|
---
|
|
276
617
|
|
|
277
|
-
###
|
|
278
|
-
|
|
618
|
+
### useTelegramEvent()
|
|
619
|
+
|
|
620
|
+
Subscribe to raw Telegram events:
|
|
621
|
+
|
|
279
622
|
```tsx
|
|
280
623
|
useTelegramEvent('themeChanged', () => {
|
|
281
|
-
console.log('
|
|
624
|
+
console.log('User switched theme!');
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
useTelegramEvent('viewportChanged', () => {
|
|
628
|
+
console.log('Viewport height changed');
|
|
282
629
|
});
|
|
283
630
|
```
|
|
284
631
|
|
|
285
632
|
---
|
|
286
633
|
|
|
287
|
-
## Utility Functions
|
|
634
|
+
## π Utility Functions
|
|
635
|
+
|
|
636
|
+
Standalone utilities (no hooks required):
|
|
288
637
|
|
|
289
638
|
```tsx
|
|
290
639
|
import {
|
|
@@ -298,79 +647,299 @@ import {
|
|
|
298
647
|
getRawUserData,
|
|
299
648
|
openExternalLink,
|
|
300
649
|
openTelegramLink,
|
|
301
|
-
tgLangToTmdb,
|
|
302
|
-
tgLangToUi,
|
|
303
|
-
isRtlLang,
|
|
304
|
-
SUPPORTED_LANGS,
|
|
305
650
|
haptic,
|
|
306
|
-
cloudStorage,
|
|
307
|
-
dialog,
|
|
651
|
+
cloudStorage,
|
|
652
|
+
dialog,
|
|
308
653
|
readClipboard,
|
|
309
654
|
openInvoice,
|
|
310
655
|
scanQr,
|
|
311
|
-
biometric,
|
|
312
|
-
location,
|
|
656
|
+
biometric,
|
|
657
|
+
location,
|
|
313
658
|
} from '@mr-m/telegram-webapp-kit';
|
|
314
659
|
|
|
315
|
-
//
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
660
|
+
// Get raw WebApp instance
|
|
661
|
+
const webApp = getWebApp();
|
|
662
|
+
|
|
663
|
+
// Check if running in Telegram
|
|
664
|
+
if (isInTelegram()) {
|
|
665
|
+
console.log('In Telegram!');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Check WebApp version
|
|
669
|
+
if (isVersionAtLeast('6.0')) {
|
|
670
|
+
console.log('Version 6.0+');
|
|
671
|
+
}
|
|
319
672
|
|
|
320
|
-
|
|
673
|
+
// Get user info
|
|
674
|
+
const displayName = getUserDisplayName();
|
|
675
|
+
const avatarUrl = getUserAvatarUrl();
|
|
676
|
+
const info = getUserInfoWithAvatar();
|
|
677
|
+
|
|
678
|
+
// Open links
|
|
679
|
+
openExternalLink('https://example.com');
|
|
680
|
+
openTelegramLink('https://t.me/username');
|
|
681
|
+
|
|
682
|
+
// Dialog
|
|
683
|
+
await dialog.alert('Alert message');
|
|
321
684
|
const confirmed = await dialog.confirm('Are you sure?');
|
|
322
|
-
const buttonId = await dialog.popup({
|
|
685
|
+
const buttonId = await dialog.popup({
|
|
686
|
+
message: 'Choose an option',
|
|
687
|
+
buttons: [
|
|
688
|
+
{ id: 'option-a', text: 'Option A' },
|
|
689
|
+
{ id: 'option-b', text: 'Option B' },
|
|
690
|
+
],
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Clipboard
|
|
694
|
+
const clipboardText = await readClipboard();
|
|
323
695
|
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
696
|
+
// Invoice / Payment
|
|
697
|
+
const status = await openInvoice('https://t.me/invoice_link');
|
|
698
|
+
|
|
699
|
+
// QR Code Scanner
|
|
700
|
+
const qrResult = await scanQr('Scan QR code');
|
|
701
|
+
|
|
702
|
+
// Biometric
|
|
703
|
+
await biometric.init();
|
|
704
|
+
|
|
705
|
+
// Location
|
|
706
|
+
const location = await location.getLocation();
|
|
327
707
|
```
|
|
328
708
|
|
|
329
709
|
---
|
|
330
710
|
|
|
331
|
-
## Start
|
|
711
|
+
## π Deep Links & Start Parameters
|
|
332
712
|
|
|
333
|
-
|
|
713
|
+
Handle deep links via `startParam`:
|
|
334
714
|
|
|
335
715
|
```tsx
|
|
336
|
-
|
|
716
|
+
'use client';
|
|
717
|
+
import { useTelegram } from '@mr-m/telegram-webapp-kit';
|
|
718
|
+
import { useRouter } from 'next/navigation';
|
|
719
|
+
import { useEffect } from 'react';
|
|
720
|
+
|
|
721
|
+
export default function Page() {
|
|
722
|
+
const { startParam, ready } = useTelegram();
|
|
723
|
+
const router = useRouter();
|
|
337
724
|
|
|
338
|
-
function App() {
|
|
339
|
-
const startParam = useTelegramStartParam();
|
|
340
|
-
|
|
341
725
|
useEffect(() => {
|
|
342
|
-
if (
|
|
343
|
-
|
|
726
|
+
if (!ready) return;
|
|
727
|
+
|
|
728
|
+
switch (startParam) {
|
|
729
|
+
case 'premium':
|
|
730
|
+
router.push('/upgrade');
|
|
731
|
+
break;
|
|
732
|
+
case 'invite':
|
|
733
|
+
router.push('/invite');
|
|
734
|
+
break;
|
|
735
|
+
default:
|
|
736
|
+
router.push('/home');
|
|
737
|
+
}
|
|
738
|
+
}, [startParam, ready, router]);
|
|
739
|
+
|
|
740
|
+
return <div>Redirecting...</div>;
|
|
344
741
|
}
|
|
345
742
|
```
|
|
346
743
|
|
|
347
744
|
---
|
|
348
745
|
|
|
349
|
-
##
|
|
746
|
+
## π¨ Type Definitions
|
|
350
747
|
|
|
351
|
-
All
|
|
748
|
+
All types are exported for TypeScript projects:
|
|
352
749
|
|
|
353
750
|
```tsx
|
|
354
751
|
import type {
|
|
355
|
-
TgUser,
|
|
356
|
-
TgWebApp,
|
|
357
|
-
TgThemeParams,
|
|
358
|
-
SafeAreaInset,
|
|
359
|
-
LocationData,
|
|
360
|
-
WebAppEventType,
|
|
361
|
-
PopupParams,
|
|
362
|
-
TelegramContextValue,
|
|
363
|
-
TelegramProviderOptions,
|
|
752
|
+
TgUser, // User object
|
|
753
|
+
TgWebApp, // WebApp instance
|
|
754
|
+
TgThemeParams, // Theme colors
|
|
755
|
+
SafeAreaInset, // Safe area values
|
|
756
|
+
LocationData, // Location coordinates
|
|
757
|
+
WebAppEventType, // Event types
|
|
758
|
+
PopupParams, // Dialog parameters
|
|
759
|
+
TelegramContextValue, // Context type
|
|
760
|
+
TelegramProviderOptions, // Provider options
|
|
364
761
|
} from '@mr-m/telegram-webapp-kit';
|
|
365
762
|
```
|
|
366
763
|
|
|
367
764
|
---
|
|
368
765
|
|
|
369
|
-
##
|
|
766
|
+
## π§ Development & Testing
|
|
767
|
+
|
|
768
|
+
### Dev Bypass Mode
|
|
370
769
|
|
|
371
|
-
|
|
372
|
-
In `development` or when `?bypass` is in the URL, it renders normally so you can develop locally.
|
|
770
|
+
Test your Mini App outside Telegram:
|
|
373
771
|
|
|
374
772
|
```bash
|
|
773
|
+
# Add ?bypass to URL
|
|
375
774
|
http://localhost:3000?bypass
|
|
376
775
|
```
|
|
776
|
+
|
|
777
|
+
This will:
|
|
778
|
+
- Skip Telegram availability check
|
|
779
|
+
- Render normally without Telegram script
|
|
780
|
+
- Allow local testing and debugging
|
|
781
|
+
|
|
782
|
+
### Fallback UI
|
|
783
|
+
|
|
784
|
+
Customize what users see when not in Telegram:
|
|
785
|
+
|
|
786
|
+
```tsx
|
|
787
|
+
const telegramOptions: TelegramProviderOptions = {
|
|
788
|
+
notInTelegramComponent: (
|
|
789
|
+
<div className="flex flex-col items-center justify-center min-h-screen">
|
|
790
|
+
<p className="text-lg">π± Open this app in Telegram</p>
|
|
791
|
+
<a href="https://t.me/YourBot?startapp=..." className="mt-4">
|
|
792
|
+
Launch Mini App
|
|
793
|
+
</a>
|
|
794
|
+
</div>
|
|
795
|
+
),
|
|
796
|
+
};
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
## π Complete Example
|
|
802
|
+
|
|
803
|
+
Here's a complete Mini App example:
|
|
804
|
+
|
|
805
|
+
```tsx
|
|
806
|
+
// app/layout.tsx
|
|
807
|
+
import Script from 'next/script';
|
|
808
|
+
import { Providers } from './providers';
|
|
809
|
+
import './globals.css';
|
|
810
|
+
|
|
811
|
+
export const metadata = {
|
|
812
|
+
title: 'My Mini App',
|
|
813
|
+
description: 'Built with telegram-webapp-kit',
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
817
|
+
return (
|
|
818
|
+
<html>
|
|
819
|
+
<head>
|
|
820
|
+
<Script
|
|
821
|
+
src="https://telegram.org/js/telegram-web-app.js"
|
|
822
|
+
strategy="beforeInteractive"
|
|
823
|
+
/>
|
|
824
|
+
</head>
|
|
825
|
+
<body>
|
|
826
|
+
<Providers>
|
|
827
|
+
<div className="app-container">
|
|
828
|
+
{children}
|
|
829
|
+
</div>
|
|
830
|
+
</Providers>
|
|
831
|
+
</body>
|
|
832
|
+
</html>
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
// app/providers.tsx
|
|
839
|
+
'use client';
|
|
840
|
+
import {
|
|
841
|
+
TelegramProvider,
|
|
842
|
+
FullscreenProvider,
|
|
843
|
+
type TelegramProviderOptions,
|
|
844
|
+
} from '@mr-m/telegram-webapp-kit';
|
|
845
|
+
|
|
846
|
+
const telegramOptions: TelegramProviderOptions = {
|
|
847
|
+
onUserReady: (user) => {
|
|
848
|
+
fetch('/api/user', {
|
|
849
|
+
method: 'POST',
|
|
850
|
+
body: JSON.stringify(user),
|
|
851
|
+
});
|
|
852
|
+
},
|
|
853
|
+
loadingComponent: <div className="p-4 text-center">Loading...</div>,
|
|
854
|
+
notInTelegramComponent: (
|
|
855
|
+
<div className="p-4 text-center">
|
|
856
|
+
Open this app in Telegram
|
|
857
|
+
</div>
|
|
858
|
+
),
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
862
|
+
return (
|
|
863
|
+
<FullscreenProvider options={{ persistPreference: true }}>
|
|
864
|
+
<TelegramProvider options={telegramOptions}>
|
|
865
|
+
{children}
|
|
866
|
+
</TelegramProvider>
|
|
867
|
+
</FullscreenProvider>
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
```tsx
|
|
873
|
+
// app/page.tsx
|
|
874
|
+
'use client';
|
|
875
|
+
import { useTelegram, useTelegramMainButton, useHapticFeedback } from '@mr-m/telegram-webapp-kit';
|
|
876
|
+
import { useState } from 'react';
|
|
877
|
+
|
|
878
|
+
export default function Home() {
|
|
879
|
+
const { user, colorScheme, ready } = useTelegram();
|
|
880
|
+
const haptic = useHapticFeedback();
|
|
881
|
+
const [count, setCount] = useState(0);
|
|
882
|
+
|
|
883
|
+
useTelegramMainButton({
|
|
884
|
+
text: 'Increment',
|
|
885
|
+
onClick: () => {
|
|
886
|
+
setCount(c => c + 1);
|
|
887
|
+
haptic.impact('light');
|
|
888
|
+
},
|
|
889
|
+
isVisible: ready,
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
if (!ready) {
|
|
893
|
+
return <div className="p-4">Loading...</div>;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return (
|
|
897
|
+
<div className="p-4 max-w-md mx-auto">
|
|
898
|
+
<h1 className="text-2xl font-bold">Welcome {user?.first_name}</h1>
|
|
899
|
+
<p className="mt-2 text-gray-600">Theme: {colorScheme}</p>
|
|
900
|
+
<div className="mt-6 text-4xl font-bold text-center">{count}</div>
|
|
901
|
+
</div>
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
---
|
|
907
|
+
|
|
908
|
+
## π Troubleshooting
|
|
909
|
+
|
|
910
|
+
**Q: Hooks return null or undefined**
|
|
911
|
+
A: Make sure you've wrapped your component with both `TelegramProvider` and `FullscreenProvider`.
|
|
912
|
+
|
|
913
|
+
**Q: Buttons don't appear**
|
|
914
|
+
A: Wait for `ready === true` before rendering button controls.
|
|
915
|
+
|
|
916
|
+
**Q: Safe areas not working**
|
|
917
|
+
A: Ensure `.app-container` has the correct CSS and safe area CSS variables are set.
|
|
918
|
+
|
|
919
|
+
**Q: Getting "not in Telegram" message**
|
|
920
|
+
A: Add `?bypass` to your URL for local testing, or open the app through Telegram.
|
|
921
|
+
|
|
922
|
+
---
|
|
923
|
+
|
|
924
|
+
## π Resources
|
|
925
|
+
|
|
926
|
+
- [Telegram WebApp Documentation](https://core.telegram.org/bots/webapps)
|
|
927
|
+
- [Telegram Bot API](https://core.telegram.org/bots/api)
|
|
928
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
929
|
+
- [React Documentation](https://react.dev)
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
933
|
+
## π License
|
|
934
|
+
|
|
935
|
+
MIT Β© [mr-m-apps](https://github.com/mr-m-apps)
|
|
936
|
+
|
|
937
|
+
---
|
|
938
|
+
|
|
939
|
+
## π€ Contributing
|
|
940
|
+
|
|
941
|
+
Found a bug or have a feature request? Open an issue on [GitHub](https://github.com/mr-m-apps/telegram-webapp-kit/issues).
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
**Built with β€οΈ for Telegram Mini Apps**
|
package/package.json
CHANGED