hallo-kit 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +168 -0
  2. package/bin/cli.mjs +136 -0
  3. package/fonts/Manrope/Manrope-VariableFont_wght.woff2 +0 -0
  4. package/fonts/Manrope/OFL.txt +93 -0
  5. package/fonts/index.ts +30 -0
  6. package/package.json +77 -0
  7. package/src/config.ts +11 -0
  8. package/src/hooks/index.ts +14 -0
  9. package/src/hooks/useAutoHideOnScroll.ts +26 -0
  10. package/src/hooks/useCountdown.ts +44 -0
  11. package/src/hooks/useDevice.ts +63 -0
  12. package/src/hooks/useIsTelegramMiniApp.ts +17 -0
  13. package/src/hooks/useIsTouch.ts +23 -0
  14. package/src/hooks/useKeyboardOpen.ts +35 -0
  15. package/src/hooks/useMediaQuery.ts +24 -0
  16. package/src/hooks/usePlatform.ts +102 -0
  17. package/src/hooks/useQueryParams.tsx +70 -0
  18. package/src/hooks/useRevealOnce.ts +21 -0
  19. package/src/hooks/useScrolledPast.ts +19 -0
  20. package/src/hooks/useSettleOnce.ts +26 -0
  21. package/src/hooks/useTelegram.tsx +57 -0
  22. package/src/i18n/index.tsx +92 -0
  23. package/src/index.ts +35 -0
  24. package/src/lib/device.ts +28 -0
  25. package/src/lib/platform.ts +19 -0
  26. package/src/lib/utils.ts +36 -0
  27. package/src/logo/LogoFull.tsx +59 -0
  28. package/src/logo/LogoMark.tsx +31 -0
  29. package/src/logo/index.ts +2 -0
  30. package/src/next.ts +13 -0
  31. package/src/server.ts +4 -0
  32. package/src/theme-color-sync.tsx +43 -0
  33. package/src/theme-provider.tsx +31 -0
  34. package/src/types/css.d.ts +6 -0
  35. package/src/types/globals.d.ts +47 -0
  36. package/src/ui/Icon/Icon.module.css +25 -0
  37. package/src/ui/Icon/Icon.tsx +45 -0
  38. package/src/ui/Icon/Icon.types.ts +60 -0
  39. package/src/ui/Icon/data/brand.data.tsx +75 -0
  40. package/src/ui/Icon/data/index.ts +2 -0
  41. package/src/ui/Icon/data/ui.data.tsx +780 -0
  42. package/src/ui/Icon/index.ts +3 -0
  43. package/src/ui/badge/badge.tsx +185 -0
  44. package/src/ui/badge/index.ts +1 -0
  45. package/src/ui/breadcrumbs/breadcrumbs.tsx +63 -0
  46. package/src/ui/breadcrumbs/index.ts +1 -0
  47. package/src/ui/burger-icon/burger-icon.tsx +22 -0
  48. package/src/ui/burger-icon/index.ts +1 -0
  49. package/src/ui/button/button.tsx +179 -0
  50. package/src/ui/button/index.ts +1 -0
  51. package/src/ui/card/card.tsx +100 -0
  52. package/src/ui/card/index.ts +1 -0
  53. package/src/ui/confirm/confirm-dialog-host.tsx +89 -0
  54. package/src/ui/confirm/index.ts +1 -0
  55. package/src/ui/count-up/count-up.tsx +96 -0
  56. package/src/ui/count-up/index.ts +1 -0
  57. package/src/ui/data-list/data-list.tsx +331 -0
  58. package/src/ui/data-list/index.ts +1 -0
  59. package/src/ui/drawer/drawer.tsx +166 -0
  60. package/src/ui/drawer/index.ts +1 -0
  61. package/src/ui/flag/flag.data.ts +240 -0
  62. package/src/ui/flag/flag.tsx +57 -0
  63. package/src/ui/flag/index.ts +1 -0
  64. package/src/ui/image/image.tsx +67 -0
  65. package/src/ui/image/index.ts +1 -0
  66. package/src/ui/input/amount-input.tsx +55 -0
  67. package/src/ui/input/index.ts +2 -0
  68. package/src/ui/input/input.tsx +391 -0
  69. package/src/ui/label/index.ts +1 -0
  70. package/src/ui/label/label.tsx +34 -0
  71. package/src/ui/method-card/index.ts +1 -0
  72. package/src/ui/method-card/method-card.tsx +101 -0
  73. package/src/ui/modal/index.ts +1 -0
  74. package/src/ui/modal/modal.tsx +128 -0
  75. package/src/ui/otp-input/index.ts +1 -0
  76. package/src/ui/otp-input/otp-input.tsx +78 -0
  77. package/src/ui/pagination/index.ts +1 -0
  78. package/src/ui/pagination/pagination.tsx +138 -0
  79. package/src/ui/radio-group/index.ts +1 -0
  80. package/src/ui/radio-group/radio-group.tsx +81 -0
  81. package/src/ui/select/index.ts +1 -0
  82. package/src/ui/select/select.tsx +215 -0
  83. package/src/ui/separator/index.ts +1 -0
  84. package/src/ui/separator/separator.tsx +54 -0
  85. package/src/ui/skeleton/index.ts +1 -0
  86. package/src/ui/skeleton/skeleton.tsx +44 -0
  87. package/src/ui/spinner/index.ts +1 -0
  88. package/src/ui/spinner/spinner.tsx +33 -0
  89. package/src/ui/switch/index.ts +1 -0
  90. package/src/ui/switch/switch.tsx +48 -0
  91. package/src/ui/tabs/index.ts +1 -0
  92. package/src/ui/tabs/tabs.tsx +190 -0
  93. package/src/ui/textarea/index.ts +1 -0
  94. package/src/ui/textarea/textarea.tsx +151 -0
  95. package/src/ui/theme-switch/index.ts +1 -0
  96. package/src/ui/theme-switch/theme-switch.tsx +161 -0
  97. package/src/ui/toast/index.ts +1 -0
  98. package/src/ui/toast/toast.tsx +159 -0
  99. package/src/utils/api/stringFormat.ts +5 -0
  100. package/src/utils/clearAllCookies.ts +7 -0
  101. package/src/utils/clsx.ts +18 -0
  102. package/src/utils/confirm.ts +48 -0
  103. package/src/utils/copy-to-clipboard.ts +12 -0
  104. package/src/utils/date/convertTimestamp.ts +44 -0
  105. package/src/utils/date/formatCountdown.ts +9 -0
  106. package/src/utils/date/formatDayLabel.ts +50 -0
  107. package/src/utils/date/formatRelativeDateTime.ts +34 -0
  108. package/src/utils/date/locales.ts +9 -0
  109. package/src/utils/form/validationSchema.ts +46 -0
  110. package/src/utils/getByKeyString.ts +15 -0
  111. package/src/utils/getCssVar.ts +2 -0
  112. package/src/utils/getDeepType.ts +3 -0
  113. package/src/utils/getQueryParams.ts +21 -0
  114. package/src/utils/inAppBrowser.ts +151 -0
  115. package/src/utils/index.ts +28 -0
  116. package/src/utils/isRTL.ts +29 -0
  117. package/src/utils/notify.ts +46 -0
  118. package/src/utils/openExternal.ts +27 -0
  119. package/src/utils/system/getMillisecondsPeriod.ts +31 -0
  120. package/src/utils/validation/authValidators.ts +23 -0
  121. package/src/utils/validation/refCode.ts +30 -0
  122. package/styles/base.css +97 -0
  123. package/styles/brand.css +36 -0
  124. package/styles/index.css +19 -0
  125. package/styles/motion.css +207 -0
  126. package/styles/semantic.css +151 -0
  127. package/styles/theme.css +143 -0
  128. package/styles/utilities.css +289 -0
@@ -0,0 +1,240 @@
1
+ // AUTO-GENERATED из public/flags. Карта ISO 3166-1 alpha-2 → слуг файла флага.
2
+ // Источник — пользовательский набор из 260 SVG (круглые флаги). Не редактировать вручную.
3
+
4
+ export const FLAG_BY_ALPHA2 = {
5
+ ad: 'andorra',
6
+ ae: 'united-arab-emirates',
7
+ af: 'afghanistan',
8
+ ag: 'antigua-and-barbuda',
9
+ ai: 'anguilla',
10
+ al: 'albania',
11
+ am: 'armenia',
12
+ ao: 'angola',
13
+ ar: 'argentina',
14
+ as: 'american-samoa',
15
+ at: 'austria',
16
+ au: 'australia',
17
+ aw: 'aruba',
18
+ ax: 'aland-islands',
19
+ az: 'azerbaijan',
20
+ ba: 'bosnia-and-herzegovina',
21
+ bb: 'barbados',
22
+ bd: 'bangladesh',
23
+ be: 'belgium',
24
+ bf: 'burkina-faso',
25
+ bg: 'bulgaria',
26
+ bh: 'bahrain',
27
+ bi: 'burundi',
28
+ bj: 'benin',
29
+ bl: 'st-barts',
30
+ bm: 'bermuda',
31
+ bn: 'brunei',
32
+ bo: 'bolivia',
33
+ bq: 'bonaire',
34
+ br: 'brazil',
35
+ bs: 'bahamas',
36
+ bt: 'bhutan',
37
+ bw: 'botswana',
38
+ by: 'belarus',
39
+ bz: 'belize',
40
+ ca: 'canada',
41
+ cc: 'cocos-island',
42
+ cd: 'democratic-republic-of-congo',
43
+ cf: 'central-african-republic',
44
+ cg: 'republic-of-the-congo',
45
+ ch: 'switzerland',
46
+ ci: 'ivory-coast',
47
+ ck: 'cook-islands',
48
+ cl: 'chile',
49
+ cm: 'cameroon',
50
+ cn: 'china',
51
+ co: 'colombia',
52
+ cr: 'costa-rica',
53
+ cu: 'cuba',
54
+ cv: 'cape-verde',
55
+ cw: 'curacao',
56
+ cy: 'cyprus',
57
+ cz: 'czech-republic',
58
+ de: 'germany',
59
+ dj: 'djibouti',
60
+ dk: 'denmark',
61
+ dm: 'dominica',
62
+ do: 'dominican-republic',
63
+ dz: 'algeria',
64
+ ec: 'ecuador',
65
+ ee: 'estonia',
66
+ eg: 'egypt',
67
+ eh: 'sahrawi-arab-democratic-republic',
68
+ er: 'eritrea',
69
+ es: 'spain',
70
+ et: 'ethiopia',
71
+ eu: 'european-union',
72
+ fi: 'finland',
73
+ fj: 'fiji',
74
+ fk: 'falkland-islands',
75
+ fm: 'micronesia',
76
+ fo: 'faroe-islands',
77
+ fr: 'france',
78
+ ga: 'gabon',
79
+ gb: 'united-kingdom',
80
+ gd: 'grenada',
81
+ ge: 'georgia',
82
+ gg: 'guernsey',
83
+ gh: 'ghana',
84
+ gi: 'gibraltar',
85
+ gl: 'greenland',
86
+ gm: 'gambia',
87
+ gn: 'guinea',
88
+ gq: 'equatorial-guinea',
89
+ gr: 'greece',
90
+ gt: 'guatemala',
91
+ gu: 'guam',
92
+ gw: 'guinea-bissau',
93
+ gy: 'guyana',
94
+ hk: 'hong-kong',
95
+ hn: 'honduras',
96
+ hr: 'croatia',
97
+ ht: 'haiti',
98
+ hu: 'hungary',
99
+ id: 'indonesia',
100
+ ie: 'ireland',
101
+ il: 'israel',
102
+ im: 'isle-of-man',
103
+ in: 'india',
104
+ io: 'british-indian-ocean-territory',
105
+ iq: 'iraq',
106
+ ir: 'iran',
107
+ is: 'iceland',
108
+ it: 'italy',
109
+ je: 'jersey',
110
+ jm: 'jamaica',
111
+ jo: 'jordan',
112
+ jp: 'japan',
113
+ ke: 'kenya',
114
+ kg: 'kyrgyzstan',
115
+ kh: 'cambodia',
116
+ ki: 'kiribati',
117
+ km: 'comoros',
118
+ kp: 'north-korea',
119
+ kr: 'south-korea',
120
+ kw: 'kuwait',
121
+ ky: 'cayman-islands',
122
+ kz: 'kazakhstan',
123
+ la: 'laos',
124
+ lb: 'lebanon',
125
+ lc: 'st-lucia',
126
+ li: 'liechtenstein',
127
+ lk: 'sri-lanka',
128
+ lr: 'liberia',
129
+ ls: 'lesotho',
130
+ lt: 'lithuania',
131
+ lu: 'luxembourg',
132
+ lv: 'latvia',
133
+ ly: 'libya',
134
+ ma: 'morocco',
135
+ mc: 'monaco',
136
+ md: 'moldova',
137
+ me: 'montenegro',
138
+ mg: 'madagascar',
139
+ mh: 'marshall-island',
140
+ mk: 'republic-of-macedonia',
141
+ ml: 'mali',
142
+ mm: 'myanmar',
143
+ mn: 'mongolia',
144
+ mo: 'macao',
145
+ mp: 'northern-marianas-islands',
146
+ mq: 'martinique',
147
+ mr: 'mauritania',
148
+ ms: 'montserrat',
149
+ mt: 'malta',
150
+ mu: 'mauritius',
151
+ mv: 'maldives',
152
+ mw: 'malawi',
153
+ mx: 'mexico',
154
+ my: 'malaysia',
155
+ mz: 'mozambique',
156
+ na: 'namibia',
157
+ ne: 'niger',
158
+ nf: 'norfolk-island',
159
+ ng: 'nigeria',
160
+ ni: 'nicaragua',
161
+ nl: 'netherlands',
162
+ no: 'norway',
163
+ np: 'nepal',
164
+ nr: 'nauru',
165
+ nu: 'niue',
166
+ nz: 'new-zealand',
167
+ om: 'oman',
168
+ pa: 'panama',
169
+ pe: 'peru',
170
+ pf: 'french-polynesia',
171
+ pg: 'papua-new-guinea',
172
+ ph: 'philippines',
173
+ pk: 'pakistan',
174
+ pl: 'poland',
175
+ pn: 'pitcairn-islands',
176
+ pr: 'puerto-rico',
177
+ ps: 'palestine',
178
+ pt: 'portugal',
179
+ pw: 'palau',
180
+ py: 'paraguay',
181
+ qa: 'qatar',
182
+ ro: 'romania',
183
+ rs: 'serbia',
184
+ ru: 'russia',
185
+ rw: 'rwanda',
186
+ sa: 'saudi-arabia',
187
+ sb: 'solomon-islands',
188
+ sc: 'seychelles',
189
+ sd: 'sudan',
190
+ se: 'sweden',
191
+ sg: 'singapore',
192
+ si: 'slovenia',
193
+ sk: 'slovakia',
194
+ sl: 'sierra-leone',
195
+ sm: 'san-marino',
196
+ sn: 'senegal',
197
+ so: 'somalia',
198
+ sr: 'suriname',
199
+ ss: 'south-sudan',
200
+ st: 'sao-tome-and-prince',
201
+ sv: 'el-salvador',
202
+ sx: 'sint-maarten',
203
+ sy: 'syria',
204
+ sz: 'swaziland',
205
+ tc: 'turks-and-caicos',
206
+ td: 'chad',
207
+ tg: 'togo',
208
+ th: 'thailand',
209
+ tj: 'tajikistan',
210
+ tk: 'tokelau',
211
+ tl: 'east-timor',
212
+ tm: 'turkmenistan',
213
+ tn: 'tunisia',
214
+ to: 'tonga',
215
+ tr: 'turkey',
216
+ tt: 'trinidad-and-tobago',
217
+ tv: 'tuvalu',
218
+ tw: 'taiwan',
219
+ tz: 'tanzania',
220
+ ua: 'ukraine',
221
+ ug: 'uganda',
222
+ us: 'united-states',
223
+ uy: 'uruguay',
224
+ uz: 'uzbekistan',
225
+ va: 'vatican-city',
226
+ vc: 'st-vincent-and-the-grenadines',
227
+ ve: 'venezuela',
228
+ vg: 'british-virgin-islands',
229
+ vi: 'virgin-islands',
230
+ vn: 'vietnam',
231
+ vu: 'vanuatu',
232
+ ws: 'samoa',
233
+ xk: 'kosovo',
234
+ ye: 'yemen',
235
+ za: 'south-africa',
236
+ zm: 'zambia',
237
+ zw: 'zimbabwe',
238
+ } as const
239
+
240
+ export type Alpha2Code = keyof typeof FLAG_BY_ALPHA2
@@ -0,0 +1,57 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '../../lib/utils'
4
+
5
+ import { FLAG_BY_ALPHA2, type Alpha2Code } from './flag.data'
6
+
7
+ export interface FlagProps
8
+ extends Omit<React.ComponentProps<'img'>, 'src' | 'alt'> {
9
+ /** ISO 3166-1 alpha-2 код страны (регистр любой): 'ru', 'GB', 'us'… */
10
+ code: Alpha2Code | (string & {})
11
+ /** Сторона квадрата: число → px (по умолчанию 20); строка → CSS-длина ('1.25em'). */
12
+ size?: number | string
13
+ /** Круглая обрезка. По умолчанию true (исходные SVG уже круглые). */
14
+ rounded?: boolean
15
+ /** Подпись для скринридера. Без неё флаг считается декоративным (aria-hidden). */
16
+ alt?: string
17
+ }
18
+
19
+ // Flag — флаг страны по ISO 3166-1 alpha-2. Тянет статичный круглый SVG из
20
+ // public/flags по карте flag.data (alpha2 → слуг). Неизвестный код → null.
21
+ // Через <img> (а не Icon): флаги цветные и их сотни — грузим по требованию, без бандла.
22
+ export function Flag({
23
+ code,
24
+ size = 20,
25
+ rounded = true,
26
+ alt,
27
+ className,
28
+ style,
29
+ ...props
30
+ }: FlagProps) {
31
+ const slug = FLAG_BY_ALPHA2[code.toLowerCase() as Alpha2Code]
32
+ if (!slug) return null
33
+
34
+ const dim = typeof size === 'number' ? `${size}px` : size
35
+
36
+ return (
37
+ // eslint-disable-next-line @next/next/no-img-element -- статичный SVG из public, next/image тут лишний
38
+ <img
39
+ src={`/flags/${slug}.svg`}
40
+ alt={alt ?? ''}
41
+ aria-hidden={alt ? undefined : true}
42
+ // lazy + async: в длинных списках флаги вне вьюпорта не грузятся, пока не доскроллишь
43
+ loading='lazy'
44
+ decoding='async'
45
+ // width/height-атрибуты — только для px (целые); для em размер задаётся style.
46
+ width={typeof size === 'number' ? size : undefined}
47
+ height={typeof size === 'number' ? size : undefined}
48
+ className={cn(
49
+ 'inline-block shrink-0 object-cover',
50
+ rounded && 'rounded-full',
51
+ className,
52
+ )}
53
+ style={{ width: dim, height: dim, ...style }}
54
+ {...props}
55
+ />
56
+ )
57
+ }
@@ -0,0 +1 @@
1
+ export * from './flag'
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+
5
+ import Image, { type ImageProps } from 'next/image'
6
+
7
+ import { Skeleton } from '../skeleton'
8
+ import { cn } from '../../lib/utils'
9
+
10
+ export interface ImageWithSkeletonProps extends ImageProps {
11
+ /** Класс на обёртку (размер/скругление). Обёртка — `relative`, чтобы лёг скелетон. */
12
+ wrapperClassName?: string
13
+ }
14
+
15
+ // Картинка со скелетоном на время загрузки — важно на слабом соединении.
16
+ //
17
+ // Место под картинку резервирует САМА обёртка через `aspect-ratio` (из числовых
18
+ // width/height) — это чистый CSS, срабатывает мгновенно, до загрузки и до JS. Без
19
+ // этого бокс был бы 0 и «прыгал» в финальный размер, когда картинка приедет.
20
+ //
21
+ // Картинка и скелетон лежат `absolute` поверх обёртки → НЕ участвуют в расчёте её
22
+ // размера (иначе `size-full` картинки зависел бы от родителя, а родитель от неё —
23
+ // цикл, схлопывающийся в 0). Скелетон снимается по `onLoad`; `onError` тоже его
24
+ // снимает (иначе висел бы вечно на битой ссылке), а ref-проверка `complete`
25
+ // закрывает гонку с кешем (картинка долетела раньше, чем навесился onLoad).
26
+ export function ImageWithSkeleton({
27
+ wrapperClassName,
28
+ className,
29
+ onLoad,
30
+ onError,
31
+ ...props
32
+ }: ImageWithSkeletonProps) {
33
+ const [loaded, setLoaded] = useState(false)
34
+
35
+ const { width, height } = props
36
+ const aspectRatio =
37
+ typeof width === 'number' && typeof height === 'number' && height > 0
38
+ ? width / height
39
+ : undefined
40
+
41
+ return (
42
+ <span
43
+ style={aspectRatio ? { aspectRatio } : undefined}
44
+ className={cn('relative block overflow-hidden', wrapperClassName)}
45
+ >
46
+ {!loaded && (
47
+ <Skeleton className='absolute inset-0 z-10 size-full rounded-none' />
48
+ )}
49
+ {/* eslint-disable-next-line jsx-a11y/alt-text -- alt приходит через ...props */}
50
+ <Image
51
+ {...props}
52
+ className={cn('inset-0 size-full', className)}
53
+ ref={node => {
54
+ if (node?.complete) setLoaded(true)
55
+ }}
56
+ onLoad={e => {
57
+ setLoaded(true)
58
+ onLoad?.(e)
59
+ }}
60
+ onError={e => {
61
+ setLoaded(true)
62
+ onError?.(e)
63
+ }}
64
+ />
65
+ </span>
66
+ )
67
+ }
@@ -0,0 +1 @@
1
+ export * from './image'
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type Control,
5
+ type FieldPath,
6
+ type FieldValues,
7
+ useController,
8
+ } from 'react-hook-form'
9
+
10
+ import { useKitLocale } from '../../i18n'
11
+
12
+ import { Input, type InputProps } from './input'
13
+
14
+ // Поле суммы для react-hook-form. Инкапсулирует связку с формой (useController):
15
+ // в ЗНАЧЕНИИ формы — только цифры строкой ('150000'), в ПОЛЕ — число с разрядами
16
+ // локали ('150 000') и символ валюты слева. Ошибка поля подтягивается сама из
17
+ // fieldState. register такое не умеет (DOM-значение ≠ форм-значение), поэтому
18
+ // контролируемая обёртка живёт здесь, а формы пишут просто:
19
+ // <AmountInput control={form.control} name='amount' currency='₽' ... />
20
+ export function AmountInput<T extends FieldValues>({
21
+ control,
22
+ name,
23
+ currency,
24
+ ...props
25
+ }: Omit<InputProps, 'value' | 'onChange' | 'error' | 'name' | 'ref'> & {
26
+ control: Control<T>
27
+ name: FieldPath<T>
28
+ /** Символ валюты слева от значения (показывается, когда поле непустое). */
29
+ currency?: string
30
+ }) {
31
+ const locale = useKitLocale()
32
+ const {
33
+ field: { ref, name: fieldName, onBlur, onChange, value },
34
+ fieldState,
35
+ } = useController({ control, name })
36
+ const digits = String(value ?? '')
37
+
38
+ return (
39
+ <Input
40
+ {...props}
41
+ name={fieldName}
42
+ ref={ref}
43
+ onBlur={onBlur}
44
+ inputMode='numeric'
45
+ value={digits ? Number(digits).toLocaleString(locale) : ''}
46
+ onChange={event => onChange(event.target.value.replace(/\D/g, ''))}
47
+ error={fieldState.error?.message}
48
+ startContent={
49
+ currency && digits ? (
50
+ <span className='text-foreground'>{currency}</span>
51
+ ) : undefined
52
+ }
53
+ />
54
+ )
55
+ }
@@ -0,0 +1,2 @@
1
+ export * from './amount-input'
2
+ export * from './input'