@alfatech/livechat 2024.12.3-1.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 (71) hide show
  1. package/.github/workflows/release.yml +33 -0
  2. package/.husky/pre-commit +2 -0
  3. package/README.md +30 -0
  4. package/biome.json +55 -0
  5. package/index.html +20 -0
  6. package/package.json +43 -0
  7. package/postcss.config.cjs +5 -0
  8. package/public/vite.svg +1 -0
  9. package/src/api/base.api.ts +118 -0
  10. package/src/api/error.ts +109 -0
  11. package/src/assets/react.svg +1 -0
  12. package/src/components/app/WidgetProvider.tsx +80 -0
  13. package/src/components/app/button/index.tsx +47 -0
  14. package/src/components/app/button/types.ts +68 -0
  15. package/src/components/app/card/profile.tsx +33 -0
  16. package/src/components/app/icon/index.tsx +144 -0
  17. package/src/components/app/input/checkbox.tsx +115 -0
  18. package/src/components/app/input/index.tsx +133 -0
  19. package/src/components/app/input/label.tsx +33 -0
  20. package/src/components/app/input/radio.tsx +143 -0
  21. package/src/components/app/input/select.tsx +54 -0
  22. package/src/components/app/input/textarea.tsx +68 -0
  23. package/src/components/app/input/toggle.tsx +162 -0
  24. package/src/components/app/input/verification.tsx +111 -0
  25. package/src/features/chat/chat.api.ts +30 -0
  26. package/src/features/chat/chat.types.ts +50 -0
  27. package/src/features/chat/partials/ChatCreateForm.tsx +103 -0
  28. package/src/features/chat/partials/ChatMessageFormFooter.tsx +67 -0
  29. package/src/features/chat/partials/ChatMessageSection.tsx +103 -0
  30. package/src/features/widget/components/WidgetIcon.tsx +26 -0
  31. package/src/features/widget/contexts/page-tracker.tsx +74 -0
  32. package/src/features/widget/hooks/page-tracker.tsx +57 -0
  33. package/src/features/widget/widget.api.ts +14 -0
  34. package/src/features/widget/widget.store.ts +16 -0
  35. package/src/features/widget/widget.types.ts +28 -0
  36. package/src/index.css +40 -0
  37. package/src/layouts/auth.tsx +17 -0
  38. package/src/layouts/chat.tsx +18 -0
  39. package/src/lib/cdn.tsx +3 -0
  40. package/src/lib/cookie.tsx +7 -0
  41. package/src/lib/debounce.tsx +23 -0
  42. package/src/lib/hooks/cookie.tsx +22 -0
  43. package/src/lib/hooks/dayjs.tsx +10 -0
  44. package/src/lib/hooks/dom.tsx +26 -0
  45. package/src/lib/hooks/form.tsx +26 -0
  46. package/src/lib/hooks/router.tsx +31 -0
  47. package/src/lib/list.tsx +14 -0
  48. package/src/lib/obj.tsx +23 -0
  49. package/src/lib/phone.tsx +12 -0
  50. package/src/main.tsx +31 -0
  51. package/src/pages/auth/registration-view.tsx +67 -0
  52. package/src/pages/chat/chat-detail-view.tsx +200 -0
  53. package/src/router/router.tsx +17 -0
  54. package/src/router/routes/auth-routes.ts +7 -0
  55. package/src/router/routes/chat-routes.ts +7 -0
  56. package/src/router/routes/index.ts +8 -0
  57. package/src/router/routes/routes.types.ts +0 -0
  58. package/src/types/i18n.ts +11 -0
  59. package/src/vite-env.d.ts +1 -0
  60. package/src/widget.tsx +19 -0
  61. package/src/ws/events.tsx +69 -0
  62. package/src/ws/guard.tsx +16 -0
  63. package/src/ws/hooks.tsx +122 -0
  64. package/tailwind.config.js +27 -0
  65. package/tsconfig.app.json +29 -0
  66. package/tsconfig.app.tsbuildinfo +1 -0
  67. package/tsconfig.json +7 -0
  68. package/tsconfig.node.json +22 -0
  69. package/tsconfig.node.tsbuildinfo +1 -0
  70. package/version.cjs +5 -0
  71. package/vite.config.ts +14 -0
@@ -0,0 +1,144 @@
1
+ function Icon() {}
2
+
3
+ type Props = React.SVGProps<SVGSVGElement>;
4
+
5
+ Icon.Home = function Home(props: Props) {
6
+ return (
7
+ <svg
8
+ viewBox="0 0 24 24"
9
+ fill="currentColor"
10
+ height="1em"
11
+ width="1em"
12
+ {...props}
13
+ >
14
+ <path d="M3 13h1v7c0 1.103.897 2 2 2h12c1.103 0 2-.897 2-2v-7h1a1 1 0 00.707-1.707l-9-9a.999.999 0 00-1.414 0l-9 9A1 1 0 003 13zm7 7v-5h4v5h-4zm2-15.586l6 6V15l.001 5H16v-5c0-1.103-.897-2-2-2h-4c-1.103 0-2 .897-2 2v5H6v-9.586l6-6z" />
15
+ </svg>
16
+ );
17
+ };
18
+
19
+ Icon.Chat = (props: Props) => (
20
+ <svg
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ fill="none"
23
+ viewBox="0 0 24 24"
24
+ strokeWidth={1.5}
25
+ stroke="currentColor"
26
+ {...props}
27
+ >
28
+ <path
29
+ strokeLinecap="round"
30
+ strokeLinejoin="round"
31
+ d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"
32
+ />
33
+ </svg>
34
+ );
35
+
36
+ Icon.MessageTyping = ({ className = "size-5", ...props }: Props) => (
37
+ <svg
38
+ xmlns="http://www.w3.org/2000/svg"
39
+ fill="none"
40
+ viewBox="0 0 24 24"
41
+ strokeWidth={1.5}
42
+ stroke="currentColor"
43
+ className={className}
44
+ {...props}
45
+ >
46
+ <path
47
+ strokeLinecap="round"
48
+ strokeLinejoin="round"
49
+ d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.184-4.183a1.14 1.14 0 0 1 .778-.332 48.294 48.294 0 0 0 5.83-.498c1.585-.233 2.708-1.626 2.708-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"
50
+ />
51
+ </svg>
52
+ );
53
+
54
+ Icon.Check = function Check(props: Props) {
55
+ return (
56
+ <svg
57
+ fill="currentColor"
58
+ viewBox="0 0 16 16"
59
+ height="1em"
60
+ width="1em"
61
+ {...props}
62
+ >
63
+ <path d="M12.736 3.97a.733.733 0 011.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 01-1.065.02L3.217 8.384a.757.757 0 010-1.06.733.733 0 011.047 0l3.052 3.093 5.4-6.425a.247.247 0 01.02-.022z" />
64
+ </svg>
65
+ );
66
+ };
67
+
68
+ Icon.Website = function Website(props: Props) {
69
+ return (
70
+ <svg fill="none" viewBox="0 0 24 24" height="1em" width="1em" {...props}>
71
+ <path
72
+ fill="currentColor"
73
+ fillRule="evenodd"
74
+ d="M14 7a1 1 0 00-1 1v8a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1h-4zm3 2h-2v6h2V9z"
75
+ clipRule="evenodd"
76
+ />
77
+ <path
78
+ fill="currentColor"
79
+ d="M6 7a1 1 0 000 2h4a1 1 0 100-2H6zM6 11a1 1 0 100 2h4a1 1 0 100-2H6zM5 16a1 1 0 011-1h4a1 1 0 110 2H6a1 1 0 01-1-1z"
80
+ />
81
+ <path
82
+ fill="currentColor"
83
+ fillRule="evenodd"
84
+ d="M4 3a3 3 0 00-3 3v12a3 3 0 003 3h16a3 3 0 003-3V6a3 3 0 00-3-3H4zm16 2H4a1 1 0 00-1 1v12a1 1 0 001 1h16a1 1 0 001-1V6a1 1 0 00-1-1z"
85
+ clipRule="evenodd"
86
+ />
87
+ </svg>
88
+ );
89
+ };
90
+
91
+ Icon.ArrowDown = (props: Props) => (
92
+ <svg
93
+ xmlns="http://www.w3.org/2000/svg"
94
+ fill="none"
95
+ viewBox="0 0 24 24"
96
+ strokeWidth={1.5}
97
+ stroke="currentColor"
98
+ {...props}
99
+ >
100
+ <path
101
+ strokeLinecap="round"
102
+ strokeLinejoin="round"
103
+ d="M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3"
104
+ />
105
+ </svg>
106
+ );
107
+
108
+ Icon.XCircle = ({ className = "size-5", ...props }: Props) => (
109
+ <svg
110
+ xmlns="http://www.w3.org/2000/svg"
111
+ fill="none"
112
+ viewBox="0 0 24 24"
113
+ strokeWidth={1.5}
114
+ stroke="currentColor"
115
+ className={className}
116
+ {...props}
117
+ >
118
+ <path
119
+ strokeLinecap="round"
120
+ strokeLinejoin="round"
121
+ d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
122
+ />
123
+ </svg>
124
+ );
125
+
126
+ Icon.X = ({ className = "size-5", ...props }: Props) => (
127
+ <svg
128
+ xmlns="http://www.w3.org/2000/svg"
129
+ fill="none"
130
+ viewBox="0 0 24 24"
131
+ strokeWidth={1.5}
132
+ stroke="currentColor"
133
+ className={className}
134
+ {...props}
135
+ >
136
+ <path
137
+ strokeLinecap="round"
138
+ strokeLinejoin="round"
139
+ d="M6 18 18 6M6 6l12 12"
140
+ />
141
+ </svg>
142
+ );
143
+
144
+ export default Icon;
@@ -0,0 +1,115 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import Icon from "../icon";
3
+
4
+ type Variant = "blue" | "indigo" | "green" | "red" | "orange";
5
+ type Size = "sm" | "md" | "lg";
6
+ type Effect = "hover" | "ring";
7
+
8
+ type Props = {
9
+ name: string;
10
+ required?: boolean;
11
+ value?: string;
12
+ checked?: boolean;
13
+ id?: string;
14
+ red?: string;
15
+ reversed?: boolean;
16
+ effect?: Effect;
17
+ variant?: Variant;
18
+ size?: Size;
19
+ onChange?: (value: boolean) => void;
20
+ onBlur?: (value: boolean) => void;
21
+ };
22
+
23
+ type EffectType = {
24
+ label: string;
25
+ input: string;
26
+ };
27
+
28
+ const effects: Record<Effect, EffectType> = {
29
+ ring: {
30
+ label: "",
31
+ input:
32
+ "relative before:content[''] before:absolute before:top-2/4 before:left-2/4 before:block before:h-12 before:w-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:bg-blue-gray-500 before:opacity-0 before:transition-opacity hover:before:opacity-10",
33
+ },
34
+ hover: {
35
+ label:
36
+ "hover:bg-second transition-colors duration-200 px-2 py-1 rounded-md",
37
+ input: "",
38
+ },
39
+ };
40
+
41
+ const variants: Record<Variant, string> = {
42
+ blue: "checked:border-blue-500 checked:bg-blue-500 checked:before:bg-blue-500",
43
+ indigo:
44
+ "checked:border-indigo-500 checked:bg-indigo-500 checked:before:bg-indigo-500",
45
+ green:
46
+ "checked:border-green-500 checked:bg-green-500 checked:before:bg-green-500",
47
+ red: "checked:border-red-500 checked:bg-red-500 checked:before:bg-red-500",
48
+ orange:
49
+ "checked:border-orange-500 checked:bg-orange-500 checked:before:bg-orange-500",
50
+ };
51
+
52
+ const sizes: Record<Size, string> = {
53
+ sm: "h-4 w-4 lg:h-3 lg:w-3",
54
+ md: "h-6 w-6 lg:h-5 lg:w-5",
55
+ lg: "h-8 w-8 lg:h-7 lg:w-7",
56
+ };
57
+
58
+ const svgSizes: Record<Size, string> = {
59
+ sm: "h-3 w-3 lg:h-2.5 lg:w-2.5",
60
+ md: "h-5 w-5 lg:h-4 lg:w-4",
61
+ lg: "h-7 w-7 lg:h-6 lg:w-6",
62
+ };
63
+
64
+ export default function Checkbox({
65
+ children,
66
+ name,
67
+ required,
68
+ value,
69
+ checked,
70
+ onChange,
71
+ onBlur,
72
+ red,
73
+ id = name,
74
+ size = "md",
75
+ effect = "ring",
76
+ reversed = false,
77
+ variant = "blue",
78
+ ...props
79
+ }: PropsWithChildren<Props>) {
80
+ return (
81
+ <div>
82
+ <div
83
+ className={`flex items-center justify-between ${reversed ? "flex-row-reverse" : ""} ${effects[effect].label}`}
84
+ >
85
+ <label
86
+ className="disable-highlight relative mr-3 flex cursor-pointer items-center rounded-full"
87
+ htmlFor={id}
88
+ >
89
+ <input
90
+ id={id}
91
+ name={name}
92
+ type="checkbox"
93
+ className={`border-blue-gray-200 peer cursor-pointer appearance-none rounded-md border bg-white transition-all dark:bg-inherit ${variants[variant]} ${sizes[size]} ${effects[effect].input}`}
94
+ required={required}
95
+ checked={checked}
96
+ value={value}
97
+ onChange={(e) => onChange?.((e.target as any).checked)}
98
+ onBlur={(e) => onBlur?.((e.target as any).checked)}
99
+ {...props}
100
+ />
101
+ <div className="pointer-events-none absolute left-2/4 top-2/4 -translate-x-2/4 -translate-y-2/4 text-white opacity-0 transition-opacity peer-checked:opacity-100">
102
+ <Icon.Check className={svgSizes[size]} />
103
+ </div>
104
+ </label>
105
+ <label
106
+ className="disable-highlight mt-px flex grow cursor-pointer select-none items-center justify-between font-light text-gray-800 dark:text-gray-400"
107
+ htmlFor={id}
108
+ >
109
+ {children}
110
+ </label>
111
+ </div>
112
+ {red && <small className="text-xs text-red-500">{red}</small>}
113
+ </div>
114
+ );
115
+ }
@@ -0,0 +1,133 @@
1
+ import classNames from "classnames";
2
+ import cn from "classnames";
3
+ import type { ChangeEvent } from "react";
4
+ import Label from "./label";
5
+
6
+ type Size = "sm" | "md" | "lg";
7
+ type InputValue = string | number | readonly string[] | undefined;
8
+ type InputType =
9
+ | "button"
10
+ | "checkbox"
11
+ | "color"
12
+ | "date"
13
+ | "datetime-local"
14
+ | "email"
15
+ | "file"
16
+ | "hidden"
17
+ | "image"
18
+ | "month"
19
+ | "number"
20
+ | "password"
21
+ | "radio"
22
+ | "range"
23
+ | "reset"
24
+ | "search"
25
+ | "submit"
26
+ | "tel"
27
+ | "text"
28
+ | "time"
29
+ | "url"
30
+ | "week";
31
+
32
+ type Props<V extends InputValue = string> = {
33
+ id: string;
34
+ label?: string;
35
+ value?: V;
36
+ name?: string;
37
+ size?: Size;
38
+ type?: InputType;
39
+ onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
40
+ onBlur?: (e: ChangeEvent<HTMLInputElement>) => void;
41
+ autoComplete?: string;
42
+ required?: boolean;
43
+ disabled?: boolean;
44
+ readOnly?: boolean;
45
+ placeholder?: string;
46
+ className?: string;
47
+ pattern?: string;
48
+ noMargin?: boolean;
49
+ error?: string;
50
+ min?: number;
51
+ max?: number;
52
+ minLength?: number;
53
+ maxLength?: number;
54
+ ariaLabel?: string;
55
+ autoFocus?: boolean;
56
+ ariaDescribedBy?: string;
57
+ ariaInvalid?: boolean;
58
+ suffix?: string | JSX.Element;
59
+ prefix?: string | JSX.Element;
60
+ };
61
+
62
+ const sizes: Record<Size, string> = {
63
+ sm: "px-3 py-1 text-xs",
64
+ md: "px-4 py-2 text-sm",
65
+ lg: "px-5 py-3 text-15",
66
+ };
67
+
68
+ export default function Input<T extends InputValue = string>({
69
+ id,
70
+ label,
71
+ suffix,
72
+ prefix,
73
+ size = "md",
74
+ type = "text",
75
+ noMargin = false,
76
+ required = false,
77
+ error,
78
+ ariaDescribedBy,
79
+ className,
80
+ ariaInvalid,
81
+ ariaLabel,
82
+ autoFocus,
83
+ ...props
84
+ }: Props<T>) {
85
+ return (
86
+ <div className={classNames({ "mb-3": !noMargin }, className)}>
87
+ <Label id={id} text={label} required={required} />
88
+ <div className="flex items-center">
89
+ {prefix && (
90
+ <span
91
+ className={cn(
92
+ "inline-block border border-r-0 border-slate-200 bg-slate-100 rounded-l-md",
93
+ sizes[size],
94
+ { "!border-red-500": !!error },
95
+ )}
96
+ >
97
+ {prefix}
98
+ </span>
99
+ )}
100
+ <input
101
+ id={id}
102
+ className={cn(
103
+ "border rounded-md block text-base py-2 px-4 w-full border-slate-200 focus:outline-none focus:border-custom-500 disabled:bg-slate-100 disabled:border-slate-300 disabled:text-slate-500 placeholder:text-slate-400",
104
+ sizes[size],
105
+ {
106
+ "rounded-l-none": !!prefix,
107
+ "rounded-r-none": !!suffix,
108
+ "!border-red-500 !text-red-500": !!error,
109
+ },
110
+ )}
111
+ type={type}
112
+ required={required}
113
+ aria-describedby={ariaDescribedBy}
114
+ aria-invalid={ariaInvalid}
115
+ aria-label={ariaLabel}
116
+ {...props}
117
+ />
118
+ {suffix && (
119
+ <span
120
+ className={cn(
121
+ "inline-block border border-l-0 border-slate-200 bg-slate-100rounded-r-md",
122
+ sizes[size],
123
+ { "!border-red-500": !!error },
124
+ )}
125
+ >
126
+ {suffix}
127
+ </span>
128
+ )}
129
+ </div>
130
+ {!!error && <p className="mt-1 text-sm text-red-500">{error}</p>}
131
+ </div>
132
+ );
133
+ }
@@ -0,0 +1,33 @@
1
+ import classNames from "classnames";
2
+
3
+ type Props = {
4
+ id?: string;
5
+ text?: string;
6
+ required?: boolean;
7
+ className?: string;
8
+ noMargin?: boolean;
9
+ };
10
+
11
+ export default function Label({
12
+ id,
13
+ text,
14
+ required,
15
+ className,
16
+ noMargin,
17
+ }: Props) {
18
+ if (!text) return null;
19
+ return (
20
+ <label
21
+ htmlFor={id}
22
+ className={classNames(
23
+ className,
24
+ {
25
+ "mb-2": !noMargin,
26
+ },
27
+ "inline-block text-base font-medium",
28
+ )}
29
+ >
30
+ {text} {required && <span className="text-red-500">*</span>}
31
+ </label>
32
+ );
33
+ }
@@ -0,0 +1,143 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import Icon from "../icon";
3
+
4
+ type Variant = "blue" | "emerald" | "red" | "orange";
5
+ type Effect = "hover" | "ring";
6
+ type Size = "sm" | "md" | "lg";
7
+
8
+ interface RadioProps {
9
+ id: string;
10
+ name: string;
11
+ value?: boolean;
12
+ variant?: Variant;
13
+ effect?: Effect;
14
+ reverse?: boolean;
15
+ size?: Size;
16
+ onChange?: (value: boolean) => void;
17
+ onBlur?: (value: boolean) => void;
18
+ onClick?: (value: boolean) => void;
19
+ }
20
+
21
+ const variants: Record<Variant, string> = {
22
+ blue: "text-blue-500 border-blue-gray-200 checked:bg-blue-500 checked:border-blue-500 checked:before:bg-blue-500 before:bg-blue-gray-500",
23
+ red: "text-red-500 border-blue-gray-200 checked:border-red-500 checked:before:bg-red-500 before:bg-blue-gray-500",
24
+ emerald:
25
+ "text-emerald-500 border-blue-gray-200 checked:border-emerald-500 checked:before:bg-emerald-500 before:bg-blue-gray-500",
26
+ orange:
27
+ "text-orange-500 border-blue-gray-200 checked:border-orange-500 checked:before:bg-orange-500 before:bg-blue-gray-500",
28
+ };
29
+
30
+ const svgVariants: Record<Variant, string> = {
31
+ blue: "text-white",
32
+ red: "text-red-500",
33
+ emerald: "text-emerald-500",
34
+ orange: "text-orange-500",
35
+ };
36
+
37
+ const sizes: Record<Size, string> = {
38
+ sm: "h4 w-4 lg:h-3 lg:w-3",
39
+ md: "h-6 w-6 lg:h-5 lg:w-5",
40
+ lg: "h-8 w-8 lg:h-7 lg:w-7",
41
+ };
42
+
43
+ const iconSizes: Record<Size, string> = {
44
+ sm: "size-3",
45
+ md: "size-4",
46
+ lg: "size-5",
47
+ };
48
+
49
+ type EffectType = {
50
+ label: string;
51
+ input: string;
52
+ };
53
+
54
+ const effects: Record<Effect, EffectType> = {
55
+ ring: {
56
+ label: "",
57
+ input:
58
+ "relative before:content[''] before:absolute before:top-2/4 before:left-2/4 before:block before:h-12 before:w-12 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full before:opacity-0 before:transition-opacity hover:before:opacity-10",
59
+ },
60
+ hover: {
61
+ label: "hover:bg-second transition-colors duration-200",
62
+ input: "",
63
+ },
64
+ };
65
+
66
+ function Radio({
67
+ children,
68
+ id,
69
+ name,
70
+ size = "md",
71
+ variant = "blue",
72
+ effect = "ring",
73
+ value = false,
74
+ reverse = false,
75
+ onChange,
76
+ onBlur,
77
+ onClick,
78
+ }: PropsWithChildren<RadioProps>) {
79
+ return (
80
+ <label
81
+ className={`disable-highlight flex cursor-pointer items-center rounded-md ${
82
+ reverse ? "flex-row-reverse justify-between" : ""
83
+ } ${effects[effect].label} `}
84
+ htmlFor={id}
85
+ >
86
+ <label
87
+ className="relative flex items-center p-2 rounded-full cursor-pointer disable-highlight"
88
+ htmlFor={id}
89
+ >
90
+ <input
91
+ id={id}
92
+ name={name}
93
+ type="radio"
94
+ className={`disable-highlight !bg-none peer cursor-pointer appearance-none rounded-full border dark:border-zink-400 bg-inherit transition-all dark:bg-inherit ${variants[variant]} ${sizes[size]} ${effects[effect].input}`}
95
+ checked={value}
96
+ onChange={(e) => onChange?.(e.target.checked)}
97
+ onBlur={(e) => onBlur?.(e.target.checked)}
98
+ onClick={() => onClick?.(!value)}
99
+ />
100
+ <div
101
+ className={`pointer-events-none absolute left-2/4 top-2/4 flex -translate-x-2/4 -translate-y-2/4 items-center justify-center opacity-0 transition-opacity peer-checked:opacity-100 ${svgVariants[variant]}`}
102
+ >
103
+ <Icon.Check className={iconSizes[size]} />
104
+ </div>
105
+ </label>
106
+ <label
107
+ className={
108
+ "disable-highlight flex cursor-pointer select-none items-center font-light text-gray-500 dark:text-gray-400"
109
+ }
110
+ htmlFor={id}
111
+ >
112
+ {children}
113
+ </label>
114
+ </label>
115
+ );
116
+ }
117
+
118
+ type GroupProps = {
119
+ label: string;
120
+ id: string;
121
+ required?: boolean;
122
+ className?: string;
123
+ };
124
+
125
+ Radio.Group = function RadioGroup({
126
+ label,
127
+ id,
128
+ className,
129
+ required,
130
+ children,
131
+ }: PropsWithChildren<GroupProps>) {
132
+ return (
133
+ <div className={className}>
134
+ <label htmlFor={id} className="inline-block mb-2 text-base font-medium">
135
+ {label} {required && <span className="text-red-500">*</span>}
136
+ </label>
137
+
138
+ <div className="col-span-12 flex items-center gap-4 p-2">{children}</div>
139
+ </div>
140
+ );
141
+ };
142
+
143
+ export default Radio;
@@ -0,0 +1,54 @@
1
+ import type { ChangeEvent, PropsWithChildren } from "react";
2
+
3
+ interface Props {
4
+ label: string;
5
+ id: string;
6
+ value?: string;
7
+ placeholder?: string;
8
+ multiple?: boolean;
9
+ name?: string;
10
+ required?: boolean;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ autoComplete?: string;
14
+ autoFocus?: boolean;
15
+ error?: string;
16
+ min?: number;
17
+ max?: number;
18
+ onChange?: (e: ChangeEvent<HTMLSelectElement>) => void;
19
+ }
20
+
21
+ export default function Select({
22
+ label,
23
+ id,
24
+ value = "",
25
+ className,
26
+ placeholder,
27
+ children,
28
+ required,
29
+ error,
30
+ ...props
31
+ }: PropsWithChildren<Props>) {
32
+ return (
33
+ <div className={className ? className : ""}>
34
+ {label && (
35
+ <label htmlFor={id} className="inline-block mb-2 text-base font-medium">
36
+ {label} {required && <span className="text-red-500">*</span>}
37
+ </label>
38
+ )}
39
+ <select
40
+ id={id}
41
+ aria-placeholder={placeholder}
42
+ value={value}
43
+ aria-label={label}
44
+ className="bg-light form-select dark:bg-zink-700 dark:border-zink-500 transition-colors duraiton-200 border text-gray-900 dark:text-gray-200 sm:text-sm rounded-md focus:ring-emerald-500 focus-visible:ring-0 focus-visible:shadow-none outline-none focus-visible:outline-none focus-visible:border-emerald-500 focus:border-emerald-500 block w-full px-2 py-2 placeholder-fuchsia-600 caret-emerald-500"
45
+ onChange={props.onChange}
46
+ required={required}
47
+ {...props}
48
+ >
49
+ {children}
50
+ </select>
51
+ {!!error && <p className="mt-1 text-sm text-red-500">{error}</p>}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,68 @@
1
+ import classNames from "classnames";
2
+ import cn from "classnames";
3
+ import type { ChangeEvent } from "react";
4
+
5
+ type Props = {
6
+ id: string;
7
+ label: string;
8
+ value?: string;
9
+ name?: string;
10
+ rows?: number;
11
+ type?: string;
12
+ className?: string;
13
+ noMargin?: boolean;
14
+ onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void;
15
+ onBlur?: (e: ChangeEvent<HTMLTextAreaElement>) => void;
16
+ autoComplete?: string;
17
+ required?: boolean;
18
+ disabled?: boolean;
19
+ readOnly?: boolean;
20
+ placeholder?: string;
21
+ pattern?: string;
22
+ error?: string;
23
+ min?: number;
24
+ max?: number;
25
+ minLength?: number;
26
+ maxLength?: number;
27
+ ariaLabel?: string;
28
+ ariaDescribedBy?: string;
29
+ ariaInvalid?: boolean;
30
+ suffix?: string | JSX.Element;
31
+ prefix?: string | JSX.Element;
32
+ };
33
+
34
+ export default function Textarea({
35
+ id,
36
+ label,
37
+ suffix,
38
+ prefix,
39
+ className,
40
+ noMargin,
41
+ rows = 3,
42
+ type = "text",
43
+ required = false,
44
+ error,
45
+ ariaInvalid,
46
+ ...props
47
+ }: Props) {
48
+ return (
49
+ <div className={classNames({ "mb-3": !noMargin }, className)}>
50
+ <label htmlFor={id} className="inline-block mb-2 text-base font-medium">
51
+ {label}
52
+ </label>
53
+ <textarea
54
+ className={cn(
55
+ "form-input border-slate-200 dark:border-zink-500 focus:outline-none focus:border-custom-500 disabled:bg-slate-100 dark:disabled:bg-zink-600 disabled:border-slate-300 dark:disabled:border-zink-500 dark:disabled:text-zink-200 disabled:text-slate-500 dark:text-zink-100 dark:bg-zink-700 dark:focus:border-custom-800 placeholder:text-slate-400 dark:placeholder:text-zink-200 valid:border-green-500 invalid:border-red-500 dark:valid:border-green-800 dark:invalid:border-red-800 invalid:text-red-500 dark:invalid:text-red-800",
56
+ {
57
+ "!border-red-500 !dark:border-red-800 !text-red-500 !dark:text-red-800":
58
+ !!error,
59
+ },
60
+ )}
61
+ id={id}
62
+ aria-invalid={ariaInvalid}
63
+ {...props}
64
+ ></textarea>
65
+ {!!error && <p className="text-red-500 mt-1 text-sm">{error}</p>}
66
+ </div>
67
+ );
68
+ }