@akinon/next 1.14.0 → 1.14.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/.eslintrc.js +40 -40
- package/.prettierrc +13 -13
- package/CHANGELOG.md +6 -0
- package/api/auth.ts +231 -231
- package/api/cache.ts +44 -44
- package/api/client.ts +174 -174
- package/api/logout.ts +42 -42
- package/bin/pz-check-dependencies.js +98 -98
- package/bin/pz-install-plugins.js +33 -33
- package/bin/pz-install-theme.js +58 -58
- package/bin/pz-postbuild.js +1 -1
- package/bin/pz-postdev.js +1 -1
- package/bin/pz-postinstall.js +6 -6
- package/bin/pz-poststart.js +1 -1
- package/bin/pz-prebuild.js +4 -4
- package/bin/pz-predev.js +4 -4
- package/bin/pz-prestart.js +1 -1
- package/bin/run-script.js +44 -44
- package/components/accordion.tsx +52 -52
- package/components/button.tsx +46 -46
- package/components/client-root.tsx +19 -19
- package/components/icon.tsx +18 -18
- package/components/image.tsx +133 -133
- package/components/index.ts +17 -17
- package/components/input.tsx +110 -110
- package/components/lazy-component.tsx +33 -33
- package/components/loader-spinner.tsx +23 -23
- package/components/mobile-app-toggler.tsx +26 -26
- package/components/oauth-login.tsx +24 -24
- package/components/price.tsx +55 -55
- package/components/pz-providers.tsx +24 -24
- package/components/pz-root.tsx +21 -21
- package/components/radio.tsx +18 -18
- package/components/react-portal.tsx +45 -45
- package/components/redirect-three-d/content/index.tsx +74 -74
- package/components/redirect-three-d/index.tsx +17 -17
- package/components/trans.tsx +39 -39
- package/data/client/account.ts +208 -208
- package/data/client/api.ts +85 -85
- package/data/client/basket.ts +82 -82
- package/data/client/misc.ts +101 -101
- package/data/client/product.ts +89 -89
- package/data/client/user.ts +99 -99
- package/data/client/wishlist.ts +118 -118
- package/data/server/category.ts +132 -132
- package/data/server/flatpage.ts +21 -21
- package/data/server/form.ts +22 -22
- package/data/server/index.ts +10 -10
- package/data/server/landingpage.ts +24 -24
- package/data/server/list.ts +67 -67
- package/data/server/menu.ts +35 -35
- package/data/server/product.ts +86 -86
- package/data/server/seo.ts +48 -48
- package/data/server/special-page.ts +47 -47
- package/data/server/widget.ts +27 -27
- package/data/urls.ts +221 -221
- package/hocs/client/index.ts +1 -1
- package/hocs/client/with-segment-defaults.tsx +25 -25
- package/hocs/server/index.ts +1 -1
- package/hocs/server/with-segment-defaults.tsx +85 -85
- package/hooks/index.ts +10 -10
- package/hooks/use-captcha.tsx +76 -76
- package/hooks/use-common-product-attributes.ts +36 -36
- package/hooks/use-debounce.ts +20 -20
- package/hooks/use-localization.ts +78 -78
- package/hooks/use-media-query.ts +36 -36
- package/hooks/use-mobile-iframe-handler.ts +23 -23
- package/hooks/use-on-click-outside.tsx +28 -28
- package/hooks/use-router.ts +45 -45
- package/hooks/use-translation.ts +14 -14
- package/lib/cache.ts +215 -215
- package/localization/index.ts +5 -5
- package/localization/provider.tsx +58 -58
- package/middlewares/currency.ts +100 -100
- package/middlewares/default.ts +256 -256
- package/middlewares/index.ts +29 -29
- package/middlewares/locale.ts +68 -68
- package/middlewares/oauth-login.ts +79 -79
- package/middlewares/pretty-url.ts +104 -104
- package/middlewares/redirection-payment.ts +160 -160
- package/middlewares/three-d-redirection.ts +159 -159
- package/middlewares/url-redirection.ts +65 -65
- package/package.json +2 -2
- package/redux/hooks.ts +7 -7
- package/redux/middlewares/index.ts +50 -50
- package/redux/reducers/checkout.ts +184 -184
- package/redux/reducers/config.ts +28 -28
- package/redux/reducers/header.ts +59 -59
- package/redux/reducers/root.ts +61 -61
- package/sentry/index.ts +27 -27
- package/tailwind/rtl.js +137 -137
- package/types/commerce/account.ts +64 -64
- package/types/commerce/address.ts +94 -94
- package/types/commerce/basket.ts +43 -43
- package/types/commerce/category.ts +114 -114
- package/types/commerce/checkout.ts +143 -143
- package/types/commerce/flatpage.ts +7 -7
- package/types/commerce/form.ts +66 -66
- package/types/commerce/index.ts +12 -12
- package/types/commerce/landingpage.ts +7 -7
- package/types/commerce/misc.ts +127 -127
- package/types/commerce/order.ts +119 -119
- package/types/commerce/product.ts +109 -109
- package/types/commerce/widget.ts +28 -28
- package/types/gtm.ts +16 -16
- package/types/index.ts +274 -274
- package/types/metadata.ts +7 -7
- package/types/next-auth.d.ts +24 -24
- package/utils/app-fetch.ts +69 -69
- package/utils/deep-merge.js +24 -24
- package/utils/image-loader.ts +31 -31
- package/utils/index.ts +150 -150
- package/utils/localization.ts +29 -29
- package/utils/log.ts +138 -138
- package/utils/menu-generator.ts +27 -27
- package/utils/mobile-3d-iframe.ts +77 -77
- package/utils/server-translation.ts +57 -57
- package/utils/server-variables.ts +9 -9
- package/with-pz-config.js +94 -94
package/components/image.tsx
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { CDNOptions } from '../types';
|
|
4
|
-
import NextImage, { ImageProps as NextImageProps } from 'next/image';
|
|
5
|
-
import { twMerge } from 'tailwind-merge';
|
|
6
|
-
import { ImageLoader } from '../utils/image-loader';
|
|
7
|
-
|
|
8
|
-
interface ImageProps extends NextImageProps {
|
|
9
|
-
/**
|
|
10
|
-
* Use fill to make the image responsive.
|
|
11
|
-
* You also need to provide sizes and aspectRatio props.
|
|
12
|
-
*
|
|
13
|
-
* @default false
|
|
14
|
-
*/
|
|
15
|
-
fill?: boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Crop option for CDN
|
|
18
|
-
*
|
|
19
|
-
* Available options:
|
|
20
|
-
* - `center`
|
|
21
|
-
* - `top`
|
|
22
|
-
* - `bottom`
|
|
23
|
-
* - `left`
|
|
24
|
-
* - `right`
|
|
25
|
-
*
|
|
26
|
-
* @default 'center'
|
|
27
|
-
*/
|
|
28
|
-
crop?: CDNOptions['crop'];
|
|
29
|
-
/**
|
|
30
|
-
* It is used to calculate the height of the image when fill is true.
|
|
31
|
-
* It prevents layout shift problems.
|
|
32
|
-
*/
|
|
33
|
-
aspectRatio?: number;
|
|
34
|
-
/**
|
|
35
|
-
* Sizes attribute for responsive images
|
|
36
|
-
*
|
|
37
|
-
* If it is not provided, large images will be downloaded for small screens and it will cause performance issues.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* <Image
|
|
41
|
-
* src={src}
|
|
42
|
-
* sizes="(min-width: 768px) 50vw, 100vw"
|
|
43
|
-
* fill
|
|
44
|
-
* />
|
|
45
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes
|
|
46
|
-
*/
|
|
47
|
-
sizes?: string;
|
|
48
|
-
imageClassName?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const DEFAULT_QUALITY = 70;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Image component for responsive images.
|
|
55
|
-
*
|
|
56
|
-
* It uses CDN options automatically if the image is hosted on Akinon CDN. You don't need to convert the src prop to CDN url.
|
|
57
|
-
*
|
|
58
|
-
* ## For responsive images
|
|
59
|
-
* You need to provide `fill`, `sizes` and `aspectRatio` props.
|
|
60
|
-
* If you are not sure about sizes, you can give `100vw` as a value. But it may cause performance issues.
|
|
61
|
-
*
|
|
62
|
-
* ## For non-responsive images
|
|
63
|
-
* You need to provide `width` and `height` props.
|
|
64
|
-
* This way, the browser will know the size of the image before downloading it and it will prevent layout shift problems.
|
|
65
|
-
*
|
|
66
|
-
* ## CDN options
|
|
67
|
-
* You can use `crop` option to crop the image.
|
|
68
|
-
*
|
|
69
|
-
* Available options:
|
|
70
|
-
* - `center`
|
|
71
|
-
* - `top`
|
|
72
|
-
* - `bottom`
|
|
73
|
-
* - `left`
|
|
74
|
-
* - `right`
|
|
75
|
-
*
|
|
76
|
-
* You can also use `quality` option to change the quality of the image.
|
|
77
|
-
* Be careful about the file size when you change the quality.
|
|
78
|
-
*
|
|
79
|
-
* @default 70
|
|
80
|
-
*/
|
|
81
|
-
export const Image = (props: ImageProps) => {
|
|
82
|
-
const {
|
|
83
|
-
src,
|
|
84
|
-
width,
|
|
85
|
-
fill,
|
|
86
|
-
sizes,
|
|
87
|
-
aspectRatio,
|
|
88
|
-
quality = DEFAULT_QUALITY,
|
|
89
|
-
crop = 'center',
|
|
90
|
-
className,
|
|
91
|
-
imageClassName,
|
|
92
|
-
...restImage
|
|
93
|
-
} = props;
|
|
94
|
-
|
|
95
|
-
const hasGif = typeof src === 'string' && src.includes('.gif');
|
|
96
|
-
|
|
97
|
-
if (fill && !aspectRatio) {
|
|
98
|
-
throw new Error('aspectRatio is required when fill is true');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (fill && !sizes) {
|
|
102
|
-
throw new Error('sizes is required when fill is true');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<div
|
|
107
|
-
className={twMerge('relative inline-block', fill && 'w-full', className)}
|
|
108
|
-
style={{
|
|
109
|
-
...(fill && { aspectRatio })
|
|
110
|
-
}}
|
|
111
|
-
>
|
|
112
|
-
<NextImage
|
|
113
|
-
{...restImage}
|
|
114
|
-
width={width}
|
|
115
|
-
src={src}
|
|
116
|
-
sizes={sizes}
|
|
117
|
-
fill={fill}
|
|
118
|
-
loader={({ src, width }) => {
|
|
119
|
-
return ImageLoader({
|
|
120
|
-
src,
|
|
121
|
-
width,
|
|
122
|
-
quality: parseInt(String(quality), DEFAULT_QUALITY),
|
|
123
|
-
crop,
|
|
124
|
-
fill,
|
|
125
|
-
aspectRatio
|
|
126
|
-
});
|
|
127
|
-
}}
|
|
128
|
-
className={imageClassName}
|
|
129
|
-
{...(hasGif && { unoptimized: true })}
|
|
130
|
-
/>
|
|
131
|
-
</div>
|
|
132
|
-
);
|
|
133
|
-
};
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CDNOptions } from '../types';
|
|
4
|
+
import NextImage, { ImageProps as NextImageProps } from 'next/image';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { ImageLoader } from '../utils/image-loader';
|
|
7
|
+
|
|
8
|
+
interface ImageProps extends NextImageProps {
|
|
9
|
+
/**
|
|
10
|
+
* Use fill to make the image responsive.
|
|
11
|
+
* You also need to provide sizes and aspectRatio props.
|
|
12
|
+
*
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
fill?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Crop option for CDN
|
|
18
|
+
*
|
|
19
|
+
* Available options:
|
|
20
|
+
* - `center`
|
|
21
|
+
* - `top`
|
|
22
|
+
* - `bottom`
|
|
23
|
+
* - `left`
|
|
24
|
+
* - `right`
|
|
25
|
+
*
|
|
26
|
+
* @default 'center'
|
|
27
|
+
*/
|
|
28
|
+
crop?: CDNOptions['crop'];
|
|
29
|
+
/**
|
|
30
|
+
* It is used to calculate the height of the image when fill is true.
|
|
31
|
+
* It prevents layout shift problems.
|
|
32
|
+
*/
|
|
33
|
+
aspectRatio?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Sizes attribute for responsive images
|
|
36
|
+
*
|
|
37
|
+
* If it is not provided, large images will be downloaded for small screens and it will cause performance issues.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* <Image
|
|
41
|
+
* src={src}
|
|
42
|
+
* sizes="(min-width: 768px) 50vw, 100vw"
|
|
43
|
+
* fill
|
|
44
|
+
* />
|
|
45
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes
|
|
46
|
+
*/
|
|
47
|
+
sizes?: string;
|
|
48
|
+
imageClassName?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_QUALITY = 70;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Image component for responsive images.
|
|
55
|
+
*
|
|
56
|
+
* It uses CDN options automatically if the image is hosted on Akinon CDN. You don't need to convert the src prop to CDN url.
|
|
57
|
+
*
|
|
58
|
+
* ## For responsive images
|
|
59
|
+
* You need to provide `fill`, `sizes` and `aspectRatio` props.
|
|
60
|
+
* If you are not sure about sizes, you can give `100vw` as a value. But it may cause performance issues.
|
|
61
|
+
*
|
|
62
|
+
* ## For non-responsive images
|
|
63
|
+
* You need to provide `width` and `height` props.
|
|
64
|
+
* This way, the browser will know the size of the image before downloading it and it will prevent layout shift problems.
|
|
65
|
+
*
|
|
66
|
+
* ## CDN options
|
|
67
|
+
* You can use `crop` option to crop the image.
|
|
68
|
+
*
|
|
69
|
+
* Available options:
|
|
70
|
+
* - `center`
|
|
71
|
+
* - `top`
|
|
72
|
+
* - `bottom`
|
|
73
|
+
* - `left`
|
|
74
|
+
* - `right`
|
|
75
|
+
*
|
|
76
|
+
* You can also use `quality` option to change the quality of the image.
|
|
77
|
+
* Be careful about the file size when you change the quality.
|
|
78
|
+
*
|
|
79
|
+
* @default 70
|
|
80
|
+
*/
|
|
81
|
+
export const Image = (props: ImageProps) => {
|
|
82
|
+
const {
|
|
83
|
+
src,
|
|
84
|
+
width,
|
|
85
|
+
fill,
|
|
86
|
+
sizes,
|
|
87
|
+
aspectRatio,
|
|
88
|
+
quality = DEFAULT_QUALITY,
|
|
89
|
+
crop = 'center',
|
|
90
|
+
className,
|
|
91
|
+
imageClassName,
|
|
92
|
+
...restImage
|
|
93
|
+
} = props;
|
|
94
|
+
|
|
95
|
+
const hasGif = typeof src === 'string' && src.includes('.gif');
|
|
96
|
+
|
|
97
|
+
if (fill && !aspectRatio) {
|
|
98
|
+
throw new Error('aspectRatio is required when fill is true');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (fill && !sizes) {
|
|
102
|
+
throw new Error('sizes is required when fill is true');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
className={twMerge('relative inline-block', fill && 'w-full', className)}
|
|
108
|
+
style={{
|
|
109
|
+
...(fill && { aspectRatio })
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<NextImage
|
|
113
|
+
{...restImage}
|
|
114
|
+
width={width}
|
|
115
|
+
src={src}
|
|
116
|
+
sizes={sizes}
|
|
117
|
+
fill={fill}
|
|
118
|
+
loader={({ src, width }) => {
|
|
119
|
+
return ImageLoader({
|
|
120
|
+
src,
|
|
121
|
+
width,
|
|
122
|
+
quality: parseInt(String(quality), DEFAULT_QUALITY),
|
|
123
|
+
crop,
|
|
124
|
+
fill,
|
|
125
|
+
aspectRatio
|
|
126
|
+
});
|
|
127
|
+
}}
|
|
128
|
+
className={imageClassName}
|
|
129
|
+
{...(hasGif && { unoptimized: true })}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
package/components/index.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export * from './accordion';
|
|
2
|
-
export * from './button';
|
|
3
|
-
export * from './client-root';
|
|
4
|
-
export * from './icon';
|
|
5
|
-
export * from './image';
|
|
6
|
-
export * from './input';
|
|
7
|
-
export * from './lazy-component';
|
|
8
|
-
export * from './loader-spinner';
|
|
9
|
-
export * from './mobile-app-toggler';
|
|
10
|
-
export * from './oauth-login';
|
|
11
|
-
export * from './plugin-module';
|
|
12
|
-
export * from './price';
|
|
13
|
-
export * from './pz-providers';
|
|
14
|
-
export * from './radio';
|
|
15
|
-
export * from './react-portal';
|
|
16
|
-
export * from './selected-payment-option-view';
|
|
17
|
-
export * from './trans';
|
|
1
|
+
export * from './accordion';
|
|
2
|
+
export * from './button';
|
|
3
|
+
export * from './client-root';
|
|
4
|
+
export * from './icon';
|
|
5
|
+
export * from './image';
|
|
6
|
+
export * from './input';
|
|
7
|
+
export * from './lazy-component';
|
|
8
|
+
export * from './loader-spinner';
|
|
9
|
+
export * from './mobile-app-toggler';
|
|
10
|
+
export * from './oauth-login';
|
|
11
|
+
export * from './plugin-module';
|
|
12
|
+
export * from './price';
|
|
13
|
+
export * from './pz-providers';
|
|
14
|
+
export * from './radio';
|
|
15
|
+
export * from './react-portal';
|
|
16
|
+
export * from './selected-payment-option-view';
|
|
17
|
+
export * from './trans';
|
package/components/input.tsx
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
import clsx from 'clsx';
|
|
2
|
-
import { forwardRef, FocusEvent, useState } from 'react';
|
|
3
|
-
import { Controller } from 'react-hook-form';
|
|
4
|
-
import NumberFormat, { NumberFormatProps } from 'react-number-format';
|
|
5
|
-
import { InputProps } from '../types';
|
|
6
|
-
import { twMerge } from 'tailwind-merge';
|
|
7
|
-
|
|
8
|
-
export const Input = forwardRef<
|
|
9
|
-
HTMLInputElement,
|
|
10
|
-
InputProps &
|
|
11
|
-
Pick<
|
|
12
|
-
NumberFormatProps,
|
|
13
|
-
'format' | 'mask' | 'allowEmptyFormatting' | 'onValueChange'
|
|
14
|
-
>
|
|
15
|
-
>((props, ref) => {
|
|
16
|
-
const [focused, setFocused] = useState(false);
|
|
17
|
-
const [hasValue, setHasValue] = useState(false);
|
|
18
|
-
const {
|
|
19
|
-
id,
|
|
20
|
-
label,
|
|
21
|
-
labelStyle,
|
|
22
|
-
error,
|
|
23
|
-
mask,
|
|
24
|
-
format,
|
|
25
|
-
required = false,
|
|
26
|
-
...rest
|
|
27
|
-
} = props;
|
|
28
|
-
const hasFloatingLabel = label && labelStyle === 'floating';
|
|
29
|
-
const inputClass = twMerge(
|
|
30
|
-
clsx(
|
|
31
|
-
'text-xs border px-2.5 h-10 placeholder:text-gray-600',
|
|
32
|
-
'focus-visible:outline-none', // disable outline on focus
|
|
33
|
-
{ 'pt-3': hasFloatingLabel },
|
|
34
|
-
error
|
|
35
|
-
? 'border-error focus:border-error'
|
|
36
|
-
: 'border-gray-500 hover:border-black focus:border-black'
|
|
37
|
-
),
|
|
38
|
-
props.className
|
|
39
|
-
);
|
|
40
|
-
const inputProps: any = {
|
|
41
|
-
id,
|
|
42
|
-
ref,
|
|
43
|
-
className: inputClass,
|
|
44
|
-
onFocus: () => setFocused(true),
|
|
45
|
-
onBlur: (event: FocusEvent<HTMLInputElement>) => {
|
|
46
|
-
setFocused(false);
|
|
47
|
-
setHasValue(!!event.target.value);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const Label = () => {
|
|
52
|
-
if (!label) return null;
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<label
|
|
56
|
-
htmlFor={id}
|
|
57
|
-
className={clsx(
|
|
58
|
-
'text-xs text-gray-800',
|
|
59
|
-
{
|
|
60
|
-
'absolute left-2.5 pointer-events-none transition-all transform -translate-y-1/2':
|
|
61
|
-
hasFloatingLabel
|
|
62
|
-
},
|
|
63
|
-
{ 'mb-2': !hasFloatingLabel },
|
|
64
|
-
{ 'top-1/3': hasFloatingLabel && (focused || hasValue) },
|
|
65
|
-
{ 'top-1/2': !(hasFloatingLabel && (focused || hasValue)) }
|
|
66
|
-
)}
|
|
67
|
-
>
|
|
68
|
-
{label} {required && <span className="text-secondary">*</span>}
|
|
69
|
-
</label>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="flex flex-col">
|
|
75
|
-
<div className="relative flex flex-col">
|
|
76
|
-
{props.format ? (
|
|
77
|
-
<>
|
|
78
|
-
{labelStyle !== 'floating' && <Label />}
|
|
79
|
-
<Controller
|
|
80
|
-
name={props.name ?? ''}
|
|
81
|
-
control={props.control}
|
|
82
|
-
defaultValue={false}
|
|
83
|
-
render={({ field }) => (
|
|
84
|
-
<NumberFormat
|
|
85
|
-
format={format}
|
|
86
|
-
mask={mask ?? ''}
|
|
87
|
-
{...rest}
|
|
88
|
-
{...field}
|
|
89
|
-
{...inputProps}
|
|
90
|
-
/>
|
|
91
|
-
)}
|
|
92
|
-
/>
|
|
93
|
-
{labelStyle === 'floating' && <Label />}
|
|
94
|
-
</>
|
|
95
|
-
) : (
|
|
96
|
-
<>
|
|
97
|
-
{labelStyle !== 'floating' && <Label />}
|
|
98
|
-
<input {...rest} {...inputProps} />
|
|
99
|
-
{labelStyle === 'floating' && <Label />}
|
|
100
|
-
</>
|
|
101
|
-
)}
|
|
102
|
-
</div>
|
|
103
|
-
{error && (
|
|
104
|
-
<span className="mt-1 text-sm text-error">{error.message}</span>
|
|
105
|
-
)}
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
Input.displayName = 'Input';
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { forwardRef, FocusEvent, useState } from 'react';
|
|
3
|
+
import { Controller } from 'react-hook-form';
|
|
4
|
+
import NumberFormat, { NumberFormatProps } from 'react-number-format';
|
|
5
|
+
import { InputProps } from '../types';
|
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
|
+
|
|
8
|
+
export const Input = forwardRef<
|
|
9
|
+
HTMLInputElement,
|
|
10
|
+
InputProps &
|
|
11
|
+
Pick<
|
|
12
|
+
NumberFormatProps,
|
|
13
|
+
'format' | 'mask' | 'allowEmptyFormatting' | 'onValueChange'
|
|
14
|
+
>
|
|
15
|
+
>((props, ref) => {
|
|
16
|
+
const [focused, setFocused] = useState(false);
|
|
17
|
+
const [hasValue, setHasValue] = useState(false);
|
|
18
|
+
const {
|
|
19
|
+
id,
|
|
20
|
+
label,
|
|
21
|
+
labelStyle,
|
|
22
|
+
error,
|
|
23
|
+
mask,
|
|
24
|
+
format,
|
|
25
|
+
required = false,
|
|
26
|
+
...rest
|
|
27
|
+
} = props;
|
|
28
|
+
const hasFloatingLabel = label && labelStyle === 'floating';
|
|
29
|
+
const inputClass = twMerge(
|
|
30
|
+
clsx(
|
|
31
|
+
'text-xs border px-2.5 h-10 placeholder:text-gray-600',
|
|
32
|
+
'focus-visible:outline-none', // disable outline on focus
|
|
33
|
+
{ 'pt-3': hasFloatingLabel },
|
|
34
|
+
error
|
|
35
|
+
? 'border-error focus:border-error'
|
|
36
|
+
: 'border-gray-500 hover:border-black focus:border-black'
|
|
37
|
+
),
|
|
38
|
+
props.className
|
|
39
|
+
);
|
|
40
|
+
const inputProps: any = {
|
|
41
|
+
id,
|
|
42
|
+
ref,
|
|
43
|
+
className: inputClass,
|
|
44
|
+
onFocus: () => setFocused(true),
|
|
45
|
+
onBlur: (event: FocusEvent<HTMLInputElement>) => {
|
|
46
|
+
setFocused(false);
|
|
47
|
+
setHasValue(!!event.target.value);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const Label = () => {
|
|
52
|
+
if (!label) return null;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<label
|
|
56
|
+
htmlFor={id}
|
|
57
|
+
className={clsx(
|
|
58
|
+
'text-xs text-gray-800',
|
|
59
|
+
{
|
|
60
|
+
'absolute left-2.5 pointer-events-none transition-all transform -translate-y-1/2':
|
|
61
|
+
hasFloatingLabel
|
|
62
|
+
},
|
|
63
|
+
{ 'mb-2': !hasFloatingLabel },
|
|
64
|
+
{ 'top-1/3': hasFloatingLabel && (focused || hasValue) },
|
|
65
|
+
{ 'top-1/2': !(hasFloatingLabel && (focused || hasValue)) }
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{label} {required && <span className="text-secondary">*</span>}
|
|
69
|
+
</label>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="flex flex-col">
|
|
75
|
+
<div className="relative flex flex-col">
|
|
76
|
+
{props.format ? (
|
|
77
|
+
<>
|
|
78
|
+
{labelStyle !== 'floating' && <Label />}
|
|
79
|
+
<Controller
|
|
80
|
+
name={props.name ?? ''}
|
|
81
|
+
control={props.control}
|
|
82
|
+
defaultValue={false}
|
|
83
|
+
render={({ field }) => (
|
|
84
|
+
<NumberFormat
|
|
85
|
+
format={format}
|
|
86
|
+
mask={mask ?? ''}
|
|
87
|
+
{...rest}
|
|
88
|
+
{...field}
|
|
89
|
+
{...inputProps}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
/>
|
|
93
|
+
{labelStyle === 'floating' && <Label />}
|
|
94
|
+
</>
|
|
95
|
+
) : (
|
|
96
|
+
<>
|
|
97
|
+
{labelStyle !== 'floating' && <Label />}
|
|
98
|
+
<input {...rest} {...inputProps} />
|
|
99
|
+
{labelStyle === 'floating' && <Label />}
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
{error && (
|
|
104
|
+
<span className="mt-1 text-sm text-error">{error.message}</span>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
Input.displayName = 'Input';
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import clsx from 'clsx';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
5
|
-
import { useInView } from 'react-intersection-observer';
|
|
6
|
-
|
|
7
|
-
interface LazyComponentProps {
|
|
8
|
-
children: React.ReactNode;
|
|
9
|
-
className?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default function LazyComponent({
|
|
13
|
-
children,
|
|
14
|
-
className
|
|
15
|
-
}: LazyComponentProps) {
|
|
16
|
-
const [isInView, setIsInView] = useState(false);
|
|
17
|
-
|
|
18
|
-
const { ref, inView } = useInView({
|
|
19
|
-
threshold: 0
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (inView) {
|
|
24
|
-
setIsInView(true);
|
|
25
|
-
}
|
|
26
|
-
}, [inView]);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div ref={ref} className={clsx(className)}>
|
|
30
|
-
{isInView ? <>{children}</> : null}
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { useInView } from 'react-intersection-observer';
|
|
6
|
+
|
|
7
|
+
interface LazyComponentProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function LazyComponent({
|
|
13
|
+
children,
|
|
14
|
+
className
|
|
15
|
+
}: LazyComponentProps) {
|
|
16
|
+
const [isInView, setIsInView] = useState(false);
|
|
17
|
+
|
|
18
|
+
const { ref, inView } = useInView({
|
|
19
|
+
threshold: 0
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (inView) {
|
|
24
|
+
setIsInView(true);
|
|
25
|
+
}
|
|
26
|
+
}, [inView]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div ref={ref} className={clsx(className)}>
|
|
30
|
+
{isInView ? <>{children}</> : null}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { twMerge } from 'tailwind-merge';
|
|
2
|
-
|
|
3
|
-
type LoaderSpinnerProps = {
|
|
4
|
-
className?: string;
|
|
5
|
-
borderType?: 'solid' | 'dotted' | 'dashed';
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const LoaderSpinner: React.FC<LoaderSpinnerProps> = ({
|
|
9
|
-
borderType = 'solid',
|
|
10
|
-
className
|
|
11
|
-
}) => {
|
|
12
|
-
return (
|
|
13
|
-
<div className="w-full h-full flex justify-center items-center">
|
|
14
|
-
<div
|
|
15
|
-
className={twMerge(
|
|
16
|
-
'w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin',
|
|
17
|
-
`border-${borderType}`,
|
|
18
|
-
className
|
|
19
|
-
)}
|
|
20
|
-
/>
|
|
21
|
-
</div>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
1
|
+
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
|
|
3
|
+
type LoaderSpinnerProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
borderType?: 'solid' | 'dotted' | 'dashed';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const LoaderSpinner: React.FC<LoaderSpinnerProps> = ({
|
|
9
|
+
borderType = 'solid',
|
|
10
|
+
className
|
|
11
|
+
}) => {
|
|
12
|
+
return (
|
|
13
|
+
<div className="w-full h-full flex justify-center items-center">
|
|
14
|
+
<div
|
|
15
|
+
className={twMerge(
|
|
16
|
+
'w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin',
|
|
17
|
+
`border-${borderType}`,
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|