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.
- package/README.md +168 -0
- package/bin/cli.mjs +136 -0
- package/fonts/Manrope/Manrope-VariableFont_wght.woff2 +0 -0
- package/fonts/Manrope/OFL.txt +93 -0
- package/fonts/index.ts +30 -0
- package/package.json +77 -0
- package/src/config.ts +11 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useAutoHideOnScroll.ts +26 -0
- package/src/hooks/useCountdown.ts +44 -0
- package/src/hooks/useDevice.ts +63 -0
- package/src/hooks/useIsTelegramMiniApp.ts +17 -0
- package/src/hooks/useIsTouch.ts +23 -0
- package/src/hooks/useKeyboardOpen.ts +35 -0
- package/src/hooks/useMediaQuery.ts +24 -0
- package/src/hooks/usePlatform.ts +102 -0
- package/src/hooks/useQueryParams.tsx +70 -0
- package/src/hooks/useRevealOnce.ts +21 -0
- package/src/hooks/useScrolledPast.ts +19 -0
- package/src/hooks/useSettleOnce.ts +26 -0
- package/src/hooks/useTelegram.tsx +57 -0
- package/src/i18n/index.tsx +92 -0
- package/src/index.ts +35 -0
- package/src/lib/device.ts +28 -0
- package/src/lib/platform.ts +19 -0
- package/src/lib/utils.ts +36 -0
- package/src/logo/LogoFull.tsx +59 -0
- package/src/logo/LogoMark.tsx +31 -0
- package/src/logo/index.ts +2 -0
- package/src/next.ts +13 -0
- package/src/server.ts +4 -0
- package/src/theme-color-sync.tsx +43 -0
- package/src/theme-provider.tsx +31 -0
- package/src/types/css.d.ts +6 -0
- package/src/types/globals.d.ts +47 -0
- package/src/ui/Icon/Icon.module.css +25 -0
- package/src/ui/Icon/Icon.tsx +45 -0
- package/src/ui/Icon/Icon.types.ts +60 -0
- package/src/ui/Icon/data/brand.data.tsx +75 -0
- package/src/ui/Icon/data/index.ts +2 -0
- package/src/ui/Icon/data/ui.data.tsx +780 -0
- package/src/ui/Icon/index.ts +3 -0
- package/src/ui/badge/badge.tsx +185 -0
- package/src/ui/badge/index.ts +1 -0
- package/src/ui/breadcrumbs/breadcrumbs.tsx +63 -0
- package/src/ui/breadcrumbs/index.ts +1 -0
- package/src/ui/burger-icon/burger-icon.tsx +22 -0
- package/src/ui/burger-icon/index.ts +1 -0
- package/src/ui/button/button.tsx +179 -0
- package/src/ui/button/index.ts +1 -0
- package/src/ui/card/card.tsx +100 -0
- package/src/ui/card/index.ts +1 -0
- package/src/ui/confirm/confirm-dialog-host.tsx +89 -0
- package/src/ui/confirm/index.ts +1 -0
- package/src/ui/count-up/count-up.tsx +96 -0
- package/src/ui/count-up/index.ts +1 -0
- package/src/ui/data-list/data-list.tsx +331 -0
- package/src/ui/data-list/index.ts +1 -0
- package/src/ui/drawer/drawer.tsx +166 -0
- package/src/ui/drawer/index.ts +1 -0
- package/src/ui/flag/flag.data.ts +240 -0
- package/src/ui/flag/flag.tsx +57 -0
- package/src/ui/flag/index.ts +1 -0
- package/src/ui/image/image.tsx +67 -0
- package/src/ui/image/index.ts +1 -0
- package/src/ui/input/amount-input.tsx +55 -0
- package/src/ui/input/index.ts +2 -0
- package/src/ui/input/input.tsx +391 -0
- package/src/ui/label/index.ts +1 -0
- package/src/ui/label/label.tsx +34 -0
- package/src/ui/method-card/index.ts +1 -0
- package/src/ui/method-card/method-card.tsx +101 -0
- package/src/ui/modal/index.ts +1 -0
- package/src/ui/modal/modal.tsx +128 -0
- package/src/ui/otp-input/index.ts +1 -0
- package/src/ui/otp-input/otp-input.tsx +78 -0
- package/src/ui/pagination/index.ts +1 -0
- package/src/ui/pagination/pagination.tsx +138 -0
- package/src/ui/radio-group/index.ts +1 -0
- package/src/ui/radio-group/radio-group.tsx +81 -0
- package/src/ui/select/index.ts +1 -0
- package/src/ui/select/select.tsx +215 -0
- package/src/ui/separator/index.ts +1 -0
- package/src/ui/separator/separator.tsx +54 -0
- package/src/ui/skeleton/index.ts +1 -0
- package/src/ui/skeleton/skeleton.tsx +44 -0
- package/src/ui/spinner/index.ts +1 -0
- package/src/ui/spinner/spinner.tsx +33 -0
- package/src/ui/switch/index.ts +1 -0
- package/src/ui/switch/switch.tsx +48 -0
- package/src/ui/tabs/index.ts +1 -0
- package/src/ui/tabs/tabs.tsx +190 -0
- package/src/ui/textarea/index.ts +1 -0
- package/src/ui/textarea/textarea.tsx +151 -0
- package/src/ui/theme-switch/index.ts +1 -0
- package/src/ui/theme-switch/theme-switch.tsx +161 -0
- package/src/ui/toast/index.ts +1 -0
- package/src/ui/toast/toast.tsx +159 -0
- package/src/utils/api/stringFormat.ts +5 -0
- package/src/utils/clearAllCookies.ts +7 -0
- package/src/utils/clsx.ts +18 -0
- package/src/utils/confirm.ts +48 -0
- package/src/utils/copy-to-clipboard.ts +12 -0
- package/src/utils/date/convertTimestamp.ts +44 -0
- package/src/utils/date/formatCountdown.ts +9 -0
- package/src/utils/date/formatDayLabel.ts +50 -0
- package/src/utils/date/formatRelativeDateTime.ts +34 -0
- package/src/utils/date/locales.ts +9 -0
- package/src/utils/form/validationSchema.ts +46 -0
- package/src/utils/getByKeyString.ts +15 -0
- package/src/utils/getCssVar.ts +2 -0
- package/src/utils/getDeepType.ts +3 -0
- package/src/utils/getQueryParams.ts +21 -0
- package/src/utils/inAppBrowser.ts +151 -0
- package/src/utils/index.ts +28 -0
- package/src/utils/isRTL.ts +29 -0
- package/src/utils/notify.ts +46 -0
- package/src/utils/openExternal.ts +27 -0
- package/src/utils/system/getMillisecondsPeriod.ts +31 -0
- package/src/utils/validation/authValidators.ts +23 -0
- package/src/utils/validation/refCode.ts +30 -0
- package/styles/base.css +97 -0
- package/styles/brand.css +36 -0
- package/styles/index.css +19 -0
- package/styles/motion.css +207 -0
- package/styles/semantic.css +151 -0
- package/styles/theme.css +143 -0
- 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
|
+
}
|