@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.
Files changed (119) hide show
  1. package/.eslintrc.js +40 -40
  2. package/.prettierrc +13 -13
  3. package/CHANGELOG.md +6 -0
  4. package/api/auth.ts +231 -231
  5. package/api/cache.ts +44 -44
  6. package/api/client.ts +174 -174
  7. package/api/logout.ts +42 -42
  8. package/bin/pz-check-dependencies.js +98 -98
  9. package/bin/pz-install-plugins.js +33 -33
  10. package/bin/pz-install-theme.js +58 -58
  11. package/bin/pz-postbuild.js +1 -1
  12. package/bin/pz-postdev.js +1 -1
  13. package/bin/pz-postinstall.js +6 -6
  14. package/bin/pz-poststart.js +1 -1
  15. package/bin/pz-prebuild.js +4 -4
  16. package/bin/pz-predev.js +4 -4
  17. package/bin/pz-prestart.js +1 -1
  18. package/bin/run-script.js +44 -44
  19. package/components/accordion.tsx +52 -52
  20. package/components/button.tsx +46 -46
  21. package/components/client-root.tsx +19 -19
  22. package/components/icon.tsx +18 -18
  23. package/components/image.tsx +133 -133
  24. package/components/index.ts +17 -17
  25. package/components/input.tsx +110 -110
  26. package/components/lazy-component.tsx +33 -33
  27. package/components/loader-spinner.tsx +23 -23
  28. package/components/mobile-app-toggler.tsx +26 -26
  29. package/components/oauth-login.tsx +24 -24
  30. package/components/price.tsx +55 -55
  31. package/components/pz-providers.tsx +24 -24
  32. package/components/pz-root.tsx +21 -21
  33. package/components/radio.tsx +18 -18
  34. package/components/react-portal.tsx +45 -45
  35. package/components/redirect-three-d/content/index.tsx +74 -74
  36. package/components/redirect-three-d/index.tsx +17 -17
  37. package/components/trans.tsx +39 -39
  38. package/data/client/account.ts +208 -208
  39. package/data/client/api.ts +85 -85
  40. package/data/client/basket.ts +82 -82
  41. package/data/client/misc.ts +101 -101
  42. package/data/client/product.ts +89 -89
  43. package/data/client/user.ts +99 -99
  44. package/data/client/wishlist.ts +118 -118
  45. package/data/server/category.ts +132 -132
  46. package/data/server/flatpage.ts +21 -21
  47. package/data/server/form.ts +22 -22
  48. package/data/server/index.ts +10 -10
  49. package/data/server/landingpage.ts +24 -24
  50. package/data/server/list.ts +67 -67
  51. package/data/server/menu.ts +35 -35
  52. package/data/server/product.ts +86 -86
  53. package/data/server/seo.ts +48 -48
  54. package/data/server/special-page.ts +47 -47
  55. package/data/server/widget.ts +27 -27
  56. package/data/urls.ts +221 -221
  57. package/hocs/client/index.ts +1 -1
  58. package/hocs/client/with-segment-defaults.tsx +25 -25
  59. package/hocs/server/index.ts +1 -1
  60. package/hocs/server/with-segment-defaults.tsx +85 -85
  61. package/hooks/index.ts +10 -10
  62. package/hooks/use-captcha.tsx +76 -76
  63. package/hooks/use-common-product-attributes.ts +36 -36
  64. package/hooks/use-debounce.ts +20 -20
  65. package/hooks/use-localization.ts +78 -78
  66. package/hooks/use-media-query.ts +36 -36
  67. package/hooks/use-mobile-iframe-handler.ts +23 -23
  68. package/hooks/use-on-click-outside.tsx +28 -28
  69. package/hooks/use-router.ts +45 -45
  70. package/hooks/use-translation.ts +14 -14
  71. package/lib/cache.ts +215 -215
  72. package/localization/index.ts +5 -5
  73. package/localization/provider.tsx +58 -58
  74. package/middlewares/currency.ts +100 -100
  75. package/middlewares/default.ts +256 -256
  76. package/middlewares/index.ts +29 -29
  77. package/middlewares/locale.ts +68 -68
  78. package/middlewares/oauth-login.ts +79 -79
  79. package/middlewares/pretty-url.ts +104 -104
  80. package/middlewares/redirection-payment.ts +160 -160
  81. package/middlewares/three-d-redirection.ts +159 -159
  82. package/middlewares/url-redirection.ts +65 -65
  83. package/package.json +2 -2
  84. package/redux/hooks.ts +7 -7
  85. package/redux/middlewares/index.ts +50 -50
  86. package/redux/reducers/checkout.ts +184 -184
  87. package/redux/reducers/config.ts +28 -28
  88. package/redux/reducers/header.ts +59 -59
  89. package/redux/reducers/root.ts +61 -61
  90. package/sentry/index.ts +27 -27
  91. package/tailwind/rtl.js +137 -137
  92. package/types/commerce/account.ts +64 -64
  93. package/types/commerce/address.ts +94 -94
  94. package/types/commerce/basket.ts +43 -43
  95. package/types/commerce/category.ts +114 -114
  96. package/types/commerce/checkout.ts +143 -143
  97. package/types/commerce/flatpage.ts +7 -7
  98. package/types/commerce/form.ts +66 -66
  99. package/types/commerce/index.ts +12 -12
  100. package/types/commerce/landingpage.ts +7 -7
  101. package/types/commerce/misc.ts +127 -127
  102. package/types/commerce/order.ts +119 -119
  103. package/types/commerce/product.ts +109 -109
  104. package/types/commerce/widget.ts +28 -28
  105. package/types/gtm.ts +16 -16
  106. package/types/index.ts +274 -274
  107. package/types/metadata.ts +7 -7
  108. package/types/next-auth.d.ts +24 -24
  109. package/utils/app-fetch.ts +69 -69
  110. package/utils/deep-merge.js +24 -24
  111. package/utils/image-loader.ts +31 -31
  112. package/utils/index.ts +150 -150
  113. package/utils/localization.ts +29 -29
  114. package/utils/log.ts +138 -138
  115. package/utils/menu-generator.ts +27 -27
  116. package/utils/mobile-3d-iframe.ts +77 -77
  117. package/utils/server-translation.ts +57 -57
  118. package/utils/server-variables.ts +9 -9
  119. package/with-pz-config.js +94 -94
@@ -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
+ };
@@ -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';
@@ -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
+ };