@rxdrag/website-lib-core 0.0.63 → 0.0.65

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxdrag/website-lib-core",
3
- "version": "0.0.63",
3
+ "version": "0.0.65",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.ts"
@@ -24,9 +24,9 @@
24
24
  "@types/react-dom": "^19.1.0",
25
25
  "eslint": "^7.32.0",
26
26
  "typescript": "^5",
27
- "@rxdrag/eslint-config-custom": "0.2.12",
28
27
  "@rxdrag/slate-preview": "1.2.58",
29
- "@rxdrag/tsconfig": "0.2.0"
28
+ "@rxdrag/tsconfig": "0.2.0",
29
+ "@rxdrag/eslint-config-custom": "0.2.12"
30
30
  },
31
31
  "dependencies": {
32
32
  "clsx": "^2.1.0",
@@ -35,8 +35,8 @@
35
35
  "lodash-es": "^4.17.21",
36
36
  "react": "^19.1.0",
37
37
  "react-dom": "^19.1.0",
38
- "@rxdrag/entify-lib": "0.0.17",
39
- "@rxdrag/rxcms-models": "0.3.88"
38
+ "@rxdrag/entify-lib": "0.0.18",
39
+ "@rxdrag/rxcms-models": "0.3.89"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "astro": "^4.0.0 || ^5.0.0"
@@ -1,6 +1,6 @@
1
1
  import { forwardRef, useState } from "react";
2
2
  import { Submit } from "./Submit";
3
- import { type UploadedFile } from "./FileUpload";
3
+ import { type UploadedFile } from "./FileUpload2";
4
4
  import clsx from "clsx";
5
5
  import { modal } from "../../../controller";
6
6
  import { encrypt } from "./funcs";
@@ -43,7 +43,7 @@ export type FileUploadProps = {
43
43
  maxSize?: number; // 最大文件大小(字节),默认 5MB
44
44
  };
45
45
 
46
- export const FileUpload = forwardRef<HTMLInputElement, FileUploadProps>(
46
+ export const FileUpload2 = forwardRef<HTMLInputElement, FileUploadProps>(
47
47
  (props, ref) => {
48
48
  const {
49
49
  label,
@@ -320,7 +320,7 @@ export const FileUpload = forwardRef<HTMLInputElement, FileUploadProps>(
320
320
  ref={labelRef}
321
321
  htmlFor={name}
322
322
  className={clsx(
323
- "absolute left-3 top-1/2 -translate-y-1/2 mt-1 text-gray-600 text-sm pointer-events-none whitespace-nowrap z-10",
323
+ "absolute left-3 top-1/2 -translate-y-1/2 mt-1 opacity-70 text-sm pointer-events-none whitespace-nowrap z-10",
324
324
  labelClassName
325
325
  )}
326
326
  >
@@ -36,7 +36,7 @@ export const Input2 = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
36
36
  ref={labelRef}
37
37
  htmlFor={name}
38
38
  className={clsx(
39
- "absolute left-3 top-1/2 -translate-y-1/2 mt-1 text-gray-400 opacity-70 text-sm pointer-events-none whitespace-nowrap",
39
+ "absolute left-3 top-1/2 -translate-y-1/2 mt-1 opacity-70 text-sm pointer-events-none whitespace-nowrap",
40
40
  labelClassName
41
41
  )}
42
42
  >
@@ -32,7 +32,10 @@ export const Submit: React.FC<SubmitProps> = (props) => {
32
32
  >
33
33
  {submitting && spinner}
34
34
  {rawHtml ? (
35
- <div dangerouslySetInnerHTML={{ __html: rawHtml }} />
35
+ <div
36
+ style={{ display: "contents" }}
37
+ dangerouslySetInnerHTML={{ __html: rawHtml }}
38
+ />
36
39
  ) : (
37
40
  title
38
41
  )}
@@ -0,0 +1,215 @@
1
+ import clsx from "clsx";
2
+ import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
3
+ import { CountryDial } from "./countryDialCodes";
4
+ import { useTelControl } from "./hooks/useTelControl";
5
+
6
+ export interface TelInputProps
7
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
8
+ label?: string;
9
+ name: string;
10
+ required?: boolean;
11
+ requiredClassName?: string;
12
+ className?: string;
13
+ labelClassName?: string;
14
+ inputClassName?: string;
15
+ countries?: CountryDial[];
16
+ }
17
+
18
+ export const TelInput = forwardRef<HTMLInputElement, TelInputProps>(
19
+ (props, ref) => {
20
+ const {
21
+ label,
22
+ name,
23
+ required,
24
+ requiredClassName,
25
+ className,
26
+ labelClassName,
27
+ inputClassName,
28
+ countries,
29
+ placeholder,
30
+ value,
31
+ onChange,
32
+ } = props;
33
+
34
+ const { allCountries, selected, localNumber, onSelectCountry, onChangeLocal } =
35
+ useTelControl({ countries, name, value, onChange });
36
+
37
+ const [open, setOpen] = useState(false);
38
+ const [query, setQuery] = useState("");
39
+ const panelRef = useRef<HTMLDivElement | null>(null);
40
+ const buttonRef = useRef<HTMLButtonElement | null>(null);
41
+
42
+ useEffect(() => {
43
+ const onDocClick = (e: MouseEvent) => {
44
+ const target = e.target as Node;
45
+ if (
46
+ open &&
47
+ panelRef.current &&
48
+ !panelRef.current.contains(target) &&
49
+ buttonRef.current &&
50
+ !buttonRef.current.contains(target)
51
+ ) {
52
+ setOpen(false);
53
+ }
54
+ };
55
+ document.addEventListener("mousedown", onDocClick);
56
+ return () => document.removeEventListener("mousedown", onDocClick);
57
+ }, [open]);
58
+
59
+ // 使用 useTelControl 负责解析与变更回传
60
+
61
+ const filtered: CountryDial[] = useMemo(() => {
62
+ if (!query) return allCountries;
63
+ const q = query.toLowerCase();
64
+ return allCountries.filter(
65
+ (c: CountryDial) =>
66
+ c.name.toLowerCase().includes(q) ||
67
+ (c.region && c.region.toLowerCase().includes(q)) ||
68
+ c.dialCode.replace("+", "").includes(q.replace("+", ""))
69
+ );
70
+ }, [allCountries, query]);
71
+
72
+ return (
73
+ <div className={clsx("w-full", className)}>
74
+ {label ? (
75
+ <label
76
+ htmlFor={name}
77
+ className={clsx(
78
+ "mb-1 inline-flex items-center text-sm text-gray-600",
79
+ labelClassName
80
+ )}
81
+ >
82
+ {label}
83
+ {required ? <span className={requiredClassName}>*</span> : null}
84
+ </label>
85
+ ) : null}
86
+
87
+ <div
88
+ className={clsx(
89
+ "flex h-10 items-center gap-2 rounded-md focus-within:ring-2 focus-within:ring-primary-300 focus-within:ring-offset-0 bg-white",
90
+ inputClassName
91
+ )}
92
+ >
93
+ <div className="relative h-full">
94
+ <button
95
+ ref={buttonRef}
96
+ type="button"
97
+ aria-haspopup="listbox"
98
+ aria-expanded={open}
99
+ onClick={() => setOpen((v) => !v)}
100
+ className={
101
+ "h-full min-w-[200px] inline-flex items-center justify-between whitespace-nowrap rounded-md px-3 py-0 text-sm"
102
+ }
103
+ >
104
+ <span className="truncate text-left leading-tight">
105
+ {selected ? (
106
+ <>
107
+ <span className="font-medium">{selected.name}</span>
108
+ {selected.region ? (
109
+ <span className="text-gray-500">, {selected.region}</span>
110
+ ) : null}
111
+ <span className="ml-1 text-gray-700">
112
+ {selected.dialCode}
113
+ </span>
114
+ </>
115
+ ) : (
116
+ "Select Country/Region"
117
+ )}
118
+ </span>
119
+ <svg
120
+ className="ml-2 h-4 w-4 text-gray-500"
121
+ viewBox="0 0 20 20"
122
+ fill="currentColor"
123
+ aria-hidden="true"
124
+ >
125
+ <path
126
+ fillRule="evenodd"
127
+ d="M5.23 7.21a.75.75 0 011.06.02L10 11.085l3.71-3.855a.75.75 0 111.08 1.04l-4.24 4.41a.75.75 0 01-1.08 0L5.25 8.27a.75.75 0 01-.02-1.06z"
128
+ clipRule="evenodd"
129
+ />
130
+ </svg>
131
+ </button>
132
+
133
+ {open ? (
134
+ <div
135
+ ref={panelRef}
136
+ className="absolute z-50 mt-2 w-[320px] rounded-md border border-gray-200 bg-white p-2 shadow-lg"
137
+ >
138
+ <div className="flex items-center gap-2 rounded-md border border-gray-200 px-2 py-1.5">
139
+ <input
140
+ value={query}
141
+ onChange={(e) => setQuery(e.target.value)}
142
+ placeholder="Enter Country/Region"
143
+ className="h-8 w-full bg-transparent text-sm outline-none"
144
+ />
145
+ <svg
146
+ className="h-4 w-4 text-gray-500"
147
+ viewBox="0 0 20 20"
148
+ fill="currentColor"
149
+ aria-hidden="true"
150
+ >
151
+ <path
152
+ fillRule="evenodd"
153
+ d="M8.5 3.5a5 5 0 013.996 8.1l3.202 3.203a.75.75 0 11-1.06 1.06l-3.203-3.202A5 5 0 118.5 3.5zm0 1.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z"
154
+ clipRule="evenodd"
155
+ />
156
+ </svg>
157
+ </div>
158
+
159
+ <div className="mt-2 max-h-64 overflow-auto pr-1">
160
+ {filtered.map((c: CountryDial) => (
161
+ <button
162
+ key={c.code}
163
+ type="button"
164
+ onClick={() => {
165
+ onSelectCountry(c);
166
+ setOpen(false);
167
+ }}
168
+ className="flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-gray-50"
169
+ >
170
+ <span
171
+ className={clsx(
172
+ "inline-block h-3 w-3 rounded-full border",
173
+ selected?.code === c.code
174
+ ? "border-primary-500 ring-4 ring-primary-200"
175
+ : "border-gray-400"
176
+ )}
177
+ ></span>
178
+ <span className="flex-1 truncate">
179
+ {c.name}
180
+ {c.region ? (
181
+ <span className="text-gray-500">, {c.region}</span>
182
+ ) : null}
183
+ </span>
184
+ <span className="text-gray-700">{c.dialCode}</span>
185
+ </button>
186
+ ))}
187
+ </div>
188
+ </div>
189
+ ) : null}
190
+ </div>
191
+
192
+ <input
193
+ ref={ref}
194
+ id={name}
195
+ name={name}
196
+ required={required}
197
+ placeholder={placeholder || "Phone number"}
198
+ inputMode="tel"
199
+ className={clsx("h-10 w-full px-3 py-0 text-sm leading-tight ring-0 outline-none rounded-md")}
200
+ value={localNumber}
201
+ onChange={(e) => {
202
+ const next = e.target.value;
203
+ onChangeLocal(next);
204
+ }}
205
+ autoComplete={name}
206
+ autoCorrect="off"
207
+ spellCheck={false}
208
+ />
209
+ </div>
210
+ </div>
211
+ );
212
+ }
213
+ );
214
+
215
+ export default TelInput;
@@ -0,0 +1,213 @@
1
+ import clsx from "clsx";
2
+ import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
3
+ import type { CountryDial } from "./countryDialCodes";
4
+ import { useInlineLabelPadding } from "./hooks/useInlineLabelPadding";
5
+ import { useTelControl } from "./hooks/useTelControl";
6
+
7
+ export interface TelInputProps
8
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
9
+ label?: string;
10
+ name: string;
11
+ required?: boolean;
12
+ requiredClassName?: string;
13
+ className?: string;
14
+ labelClassName?: string;
15
+ inputClassName?: string;
16
+ countries?: CountryDial[];
17
+ selectPlaceholder?: string;
18
+ }
19
+
20
+ export const TelInput2 = forwardRef<HTMLInputElement, TelInputProps>(
21
+ (props, ref) => {
22
+ const {
23
+ label,
24
+ name,
25
+ required,
26
+ requiredClassName,
27
+ className,
28
+ labelClassName,
29
+ inputClassName,
30
+ countries,
31
+ placeholder,
32
+ selectPlaceholder,
33
+ value,
34
+ onChange,
35
+ } = props;
36
+
37
+ const { labelRef, paddingLeft } = useInlineLabelPadding(16);
38
+ const {
39
+ allCountries,
40
+ selected,
41
+ localNumber,
42
+ onSelectCountry,
43
+ onChangeLocal,
44
+ } = useTelControl({ countries, name, value, onChange });
45
+
46
+ const [open, setOpen] = useState(false);
47
+ const [query, setQuery] = useState("");
48
+ const panelRef = useRef<HTMLDivElement | null>(null);
49
+ const buttonRef = useRef<HTMLButtonElement | null>(null);
50
+
51
+ const filtered = useMemo(() => {
52
+ if (!query) return allCountries;
53
+ const q = query.toLowerCase();
54
+ return allCountries.filter(
55
+ (c) =>
56
+ c.name.toLowerCase().includes(q) ||
57
+ (c.region && c.region.toLowerCase().includes(q)) ||
58
+ c.dialCode.replace("+", "").includes(q.replace("+", ""))
59
+ );
60
+ }, [allCountries, query]);
61
+
62
+ useEffect(() => {
63
+ const onDocClick = (e: MouseEvent) => {
64
+ const target = e.target as Node;
65
+ if (
66
+ open &&
67
+ panelRef.current &&
68
+ !panelRef.current.contains(target) &&
69
+ buttonRef.current &&
70
+ !buttonRef.current.contains(target)
71
+ ) {
72
+ setOpen(false);
73
+ }
74
+ };
75
+ document.addEventListener("mousedown", onDocClick);
76
+ return () => document.removeEventListener("mousedown", onDocClick);
77
+ }, [open]);
78
+
79
+ return (
80
+ <div className={clsx("relative w-full", className)}>
81
+ <label
82
+ ref={labelRef}
83
+ htmlFor={name}
84
+ className={clsx(
85
+ "absolute left-3 top-1/2 -translate-y-1/2 mt-1 opacity-70 text-sm pointer-events-none whitespace-nowrap",
86
+ labelClassName
87
+ )}
88
+ >
89
+ {label}
90
+ {required ? <span className={requiredClassName}>*</span> : null}
91
+ </label>
92
+
93
+ <div
94
+ className={clsx(
95
+ "flex h-10 items-center gap-2 rounded-md shadow-sm focus-within:ring-2 focus-within:ring-primary-300",
96
+ inputClassName
97
+ )}
98
+ style={{ paddingLeft }}
99
+ >
100
+ <div className="relative h-full">
101
+ <button
102
+ ref={buttonRef}
103
+ type="button"
104
+ aria-haspopup="listbox"
105
+ aria-expanded={open}
106
+ onClick={() => setOpen((v) => !v)}
107
+ className={
108
+ "h-full min-w-[100px] inline-flex items-center justify-between whitespace-nowrap rounded-md px-3 py-0 text-sm"
109
+ }
110
+ >
111
+ <span className="truncate text-left leading-tight">
112
+ {selected ? (
113
+ <span className="font-medium">{selected.dialCode}</span>
114
+ ) : (
115
+ <span className="text-gray-500">+ Code</span>
116
+ )}
117
+ </span>
118
+ <svg
119
+ className="ml-2 h-4 w-4 text-gray-500"
120
+ viewBox="0 0 20 20"
121
+ fill="currentColor"
122
+ aria-hidden="true"
123
+ >
124
+ <path
125
+ fillRule="evenodd"
126
+ d="M5.23 7.21a.75.75 0 011.06.02L10 11.085l3.71-3.855a.75.75 0 111.08 1.04l-4.24 4.41a.75.75 0 01-1.08 0L5.25 8.27a.75.75 0 01-.02-1.06z"
127
+ clipRule="evenodd"
128
+ />
129
+ </svg>
130
+ </button>
131
+
132
+ {open ? (
133
+ <div
134
+ ref={panelRef}
135
+ className="absolute z-50 mt-2 w-[320px] rounded-md border border-gray-200 bg-white p-2 shadow-lg"
136
+ >
137
+ <div className="flex items-center gap-2 rounded-md border border-gray-200 px-2 py-1.5">
138
+ <input
139
+ value={query}
140
+ onChange={(e) => setQuery(e.target.value)}
141
+ placeholder={selectPlaceholder || "Enter Country/Region"}
142
+ className="h-8 w-full bg-transparent text-sm outline-none"
143
+ />
144
+ <svg
145
+ className="h-4 w-4 text-gray-500"
146
+ viewBox="0 0 20 20"
147
+ fill="currentColor"
148
+ aria-hidden="true"
149
+ >
150
+ <path
151
+ fillRule="evenodd"
152
+ d="M8.5 3.5a5 5 0 013.996 8.1l3.202 3.203a.75.75 0 11-1.06 1.06l-3.203-3.202A5 5 0 118.5 3.5zm0 1.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z"
153
+ clipRule="evenodd"
154
+ />
155
+ </svg>
156
+ </div>
157
+
158
+ <div className="mt-2 max-h-64 overflow-auto pr-1">
159
+ {filtered.map((c) => (
160
+ <button
161
+ key={c.code}
162
+ type="button"
163
+ onClick={() => {
164
+ onSelectCountry(c);
165
+ setOpen(false);
166
+ }}
167
+ className="flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-gray-50"
168
+ >
169
+ <span
170
+ className={clsx(
171
+ "inline-block h-3 w-3 rounded-full border",
172
+ selected?.code === c.code
173
+ ? "border-primary-500 ring-4 ring-primary-200"
174
+ : "border-gray-400"
175
+ )}
176
+ ></span>
177
+ <span className="flex-1 truncate">
178
+ {c.name}
179
+ {c.region ? (
180
+ <span className="text-gray-500">, {c.region}</span>
181
+ ) : null}
182
+ </span>
183
+ <span className="text-gray-700">{c.dialCode}</span>
184
+ </button>
185
+ ))}
186
+ </div>
187
+ </div>
188
+ ) : null}
189
+ </div>
190
+
191
+ <input
192
+ ref={ref}
193
+ id={name}
194
+ name={name}
195
+ required={required}
196
+ placeholder={placeholder || "Phone number"}
197
+ inputMode="tel"
198
+ className={clsx(
199
+ "h-10 w-full appearance-none rounded-md border-0 bg-transparent px-3 py-0 text-sm leading-tight focus:outline-none"
200
+ )}
201
+ value={localNumber}
202
+ onChange={(e) => onChangeLocal(e.target.value)}
203
+ autoComplete={name}
204
+ autoCorrect="off"
205
+ spellCheck={false}
206
+ />
207
+ </div>
208
+ </div>
209
+ );
210
+ }
211
+ );
212
+
213
+ export default TelInput2;
@@ -34,7 +34,7 @@ export const Textarea2 = forwardRef<HTMLTextAreaElement, TextareaProps>(
34
34
  <label
35
35
  ref={labelRef}
36
36
  htmlFor={name}
37
- className={`absolute left-3 top-2 text-gray-400 opacity-70 text-sm pointer-events-none ${
37
+ className={`absolute left-3 top-2 opacity-70 text-sm pointer-events-none ${
38
38
  labelClassName || ""
39
39
  }`}
40
40
  >