@rxdrag/website-lib-react 0.0.4 → 0.0.7
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/dist/ReactModalTrigger-9207e763.js +26 -0
- package/dist/ReactModalTrigger-9207e763.js.map +1 -0
- package/dist/components/ContactForm/ContactForm.d.ts +2 -1
- package/dist/components/Icon/index.d.ts +2 -1
- package/dist/components/RichTextOutline/parseOutline.d.ts +5 -0
- package/dist/components/all.d.ts +0 -21
- package/dist/components/index.d.ts +0 -5
- package/dist/forms.d.ts +1 -0
- package/dist/forms.mjs +1649 -0
- package/dist/forms.mjs.map +1 -0
- package/dist/index.mjs +9 -3918
- package/dist/index.mjs.map +1 -1
- package/dist/jsx-runtime-c02cc059.js +325 -0
- package/dist/jsx-runtime-c02cc059.js.map +1 -0
- package/dist/media.d.ts +1 -0
- package/dist/media.mjs +613 -0
- package/dist/media.mjs.map +1 -0
- package/dist/richtext.d.ts +1 -0
- package/dist/richtext.mjs +191 -0
- package/dist/richtext.mjs.map +1 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.mjs +687 -0
- package/dist/ui.mjs.map +1 -0
- package/dist/video.d.ts +2 -0
- package/dist/video.mjs +426 -0
- package/dist/video.mjs.map +1 -0
- package/forms.ts +1 -0
- package/index.ts +1 -0
- package/media.ts +1 -0
- package/package.json +40 -5
- package/richtext.ts +1 -0
- package/src/components/Analytics/eventHandlers.ts +173 -0
- package/src/components/Analytics/index.tsx +21 -0
- package/src/components/Analytics/singleton.ts +214 -0
- package/src/components/Analytics/tracking.ts +221 -0
- package/src/components/Analytics/types.ts +60 -0
- package/src/components/Analytics/utils.ts +95 -0
- package/src/components/AttachmentIcon/index.tsx +53 -0
- package/src/components/BackgroundHlsVideoPlayer.tsx +97 -0
- package/src/components/BackgroundVideoPlayer.tsx +32 -0
- package/src/components/Bulletin.tsx +30 -0
- package/src/components/ContactForm/ContactForm.tsx +296 -0
- package/src/components/ContactForm/FileUpload2.tsx +423 -0
- package/src/components/ContactForm/Input.tsx +48 -0
- package/src/components/ContactForm/Input2.tsx +59 -0
- package/src/components/ContactForm/Submit.tsx +48 -0
- package/src/components/ContactForm/TelInput.tsx +215 -0
- package/src/components/ContactForm/TelInput2.tsx +213 -0
- package/src/components/ContactForm/Textarea.tsx +48 -0
- package/src/components/ContactForm/Textarea2.tsx +89 -0
- package/src/components/ContactForm/countryDialCodes.ts +243 -0
- package/src/components/ContactForm/factory.tsx +60 -0
- package/src/components/ContactForm/funcs.ts +64 -0
- package/src/components/ContactForm/hooks/useInlineLabelPadding.ts +43 -0
- package/src/components/ContactForm/hooks/useTelControl.ts +81 -0
- package/src/components/ContactForm/index.ts +7 -0
- package/src/components/ContactForm/types.ts +68 -0
- package/src/components/Icon/index.tsx +20 -0
- package/src/components/Medias/MainMedia.tsx +257 -0
- package/src/components/Medias/Thumbnail.tsx +62 -0
- package/src/components/Medias/VideoPlayer.tsx +114 -0
- package/src/components/Medias/index.tsx +271 -0
- package/src/components/ProductCard/ProductCard.tsx +24 -0
- package/src/components/ProductCard/ProductCta/index.tsx +28 -0
- package/src/components/ProductCard/ProductCta/style.css +4 -0
- package/src/components/ProductCard/ProductDescription/index.tsx +13 -0
- package/src/components/ProductCard/ProductDescription/style.css +6 -0
- package/src/components/ProductCard/ProductMedia/index.tsx +35 -0
- package/src/components/ProductCard/ProductMedia/style.css +6 -0
- package/src/components/ProductCard/ProductTitle/index.tsx +7 -0
- package/src/components/ProductCard/ProductTitle/style.css +4 -0
- package/src/components/ProductCard/ProductView.tsx +36 -0
- package/src/components/ProductCard/index.ts +5 -0
- package/src/components/ProductCard/useQueryProduct.ts +32 -0
- package/src/components/ReactModalTrigger.tsx +28 -0
- package/src/components/ReactVideoPlayer.tsx +332 -0
- package/src/components/RichTextOutline/index.tsx +74 -0
- package/src/components/RichTextOutline/parseOutline.ts +63 -0
- package/src/components/RichTextOutline/useAcitviedHeading.ts +142 -0
- package/src/components/RichTextOutline/useAnchorScroll.ts +24 -0
- package/src/components/Scroller.tsx +39 -0
- package/src/components/SearchInput.tsx +21 -0
- package/src/components/Share/index.tsx +86 -0
- package/src/components/Share/socials.tsx +80 -0
- package/src/components/Share//350/265/204/346/226/231.md +7 -0
- package/src/components/ToTop.tsx +72 -0
- package/src/components/VideoPlayIcon.tsx +43 -0
- package/src/components/all.ts +25 -0
- package/src/components/index.ts +12 -0
- package/src/forms.ts +1 -0
- package/src/index.ts +1 -0
- package/src/media.ts +1 -0
- package/src/richtext.ts +1 -0
- package/src/types/view-model.ts +37 -0
- package/src/ui.ts +10 -0
- package/src/video.ts +2 -0
- package/ui.ts +1 -0
- package/video.ts +1 -0
- package/dist/style.css +0 -17
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type RichTextOutlineItem = {
|
|
2
|
+
key: string;
|
|
3
|
+
text: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
function slugify(value: string) {
|
|
7
|
+
return value
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.trim()
|
|
10
|
+
.replace(/<[^>]+>/g, "")
|
|
11
|
+
.replace(/&[a-z0-9#]+;/gi, "")
|
|
12
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, "")
|
|
13
|
+
.replace(/\s+/g, "-")
|
|
14
|
+
.replace(/-+/g, "-")
|
|
15
|
+
.replace(/^-|-$/g, "");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function decodeInlineText(value: string) {
|
|
19
|
+
return value
|
|
20
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
21
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
22
|
+
.replace(/[`*_~>#]/g, "")
|
|
23
|
+
.replace(/<[^>]+>/g, "")
|
|
24
|
+
.replace(/ /gi, " ")
|
|
25
|
+
.replace(/&/gi, "&")
|
|
26
|
+
.replace(/</gi, "<")
|
|
27
|
+
.replace(/>/gi, ">")
|
|
28
|
+
.replace(/\s+/g, " ")
|
|
29
|
+
.trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parseOutline(value?: string): RichTextOutlineItem[] {
|
|
33
|
+
if (!value) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const lines = value.split(/\r?\n/);
|
|
38
|
+
const counts = new Map<string, number>();
|
|
39
|
+
const items: RichTextOutlineItem[] = [];
|
|
40
|
+
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
43
|
+
if (!match) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const text = decodeInlineText(match[2]);
|
|
48
|
+
if (!text) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const baseKey = slugify(text) || "section";
|
|
53
|
+
const count = counts.get(baseKey) ?? 0;
|
|
54
|
+
counts.set(baseKey, count + 1);
|
|
55
|
+
|
|
56
|
+
items.push({
|
|
57
|
+
key: count === 0 ? baseKey : `${baseKey}-${count + 1}`,
|
|
58
|
+
text,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return items;
|
|
63
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState, useRef, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
type ThrottledFunction<T extends (...args: never[]) => void> = ((...args: Parameters<T>) => void) & {
|
|
4
|
+
cancel: () => void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function throttle<T extends (...args: never[]) => void>(
|
|
8
|
+
fn: T,
|
|
9
|
+
wait: number,
|
|
10
|
+
options: { leading?: boolean; trailing?: boolean } = {}
|
|
11
|
+
): ThrottledFunction<T> {
|
|
12
|
+
const { leading = true, trailing = true } = options;
|
|
13
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
14
|
+
let lastInvokeTime = 0;
|
|
15
|
+
let lastArgs: Parameters<T> | null = null;
|
|
16
|
+
|
|
17
|
+
const invoke = (time: number, args: Parameters<T>) => {
|
|
18
|
+
lastInvokeTime = time;
|
|
19
|
+
fn(...args);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const throttled = ((...args: Parameters<T>) => {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
|
|
25
|
+
if (!lastInvokeTime && !leading) {
|
|
26
|
+
lastInvokeTime = now;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const remaining = wait - (now - lastInvokeTime);
|
|
30
|
+
lastArgs = args;
|
|
31
|
+
|
|
32
|
+
if (remaining <= 0 || remaining > wait) {
|
|
33
|
+
if (timeoutId) {
|
|
34
|
+
clearTimeout(timeoutId);
|
|
35
|
+
timeoutId = null;
|
|
36
|
+
}
|
|
37
|
+
invoke(now, args);
|
|
38
|
+
lastArgs = null;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!timeoutId && trailing) {
|
|
43
|
+
timeoutId = setTimeout(() => {
|
|
44
|
+
timeoutId = null;
|
|
45
|
+
if (lastArgs) {
|
|
46
|
+
invoke(leading ? Date.now() : 0, lastArgs);
|
|
47
|
+
lastArgs = null;
|
|
48
|
+
}
|
|
49
|
+
}, remaining);
|
|
50
|
+
}
|
|
51
|
+
}) as ThrottledFunction<T>;
|
|
52
|
+
|
|
53
|
+
throttled.cancel = () => {
|
|
54
|
+
if (timeoutId) {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
timeoutId = null;
|
|
57
|
+
}
|
|
58
|
+
lastArgs = null;
|
|
59
|
+
lastInvokeTime = 0;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return throttled;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useAcitviedHeading(yOffset = 200) {
|
|
66
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
67
|
+
const anchorElementsRef = useRef<HTMLAnchorElement[]>([]);
|
|
68
|
+
const lastScrollTopRef = useRef(0);
|
|
69
|
+
const activeIdRef = useRef<string | null>(null);
|
|
70
|
+
|
|
71
|
+
// 同步 activeId 到 ref
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
activeIdRef.current = activeId;
|
|
74
|
+
}, [activeId]);
|
|
75
|
+
|
|
76
|
+
// 初始化时获取所有锚点元素
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
anchorElementsRef.current = Array.from(
|
|
79
|
+
document.querySelectorAll('a[href^="#"]')
|
|
80
|
+
);
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
const handleScroll = useCallback(() => {
|
|
84
|
+
const scrollTop = window.scrollY;
|
|
85
|
+
lastScrollTopRef.current = scrollTop;
|
|
86
|
+
|
|
87
|
+
let closestId = null;
|
|
88
|
+
let closestDistance = Infinity;
|
|
89
|
+
|
|
90
|
+
anchorElementsRef.current.forEach((element) => {
|
|
91
|
+
const id = element.getAttribute("href")?.slice(1) || "";
|
|
92
|
+
const targetElement = document.getElementById(id);
|
|
93
|
+
|
|
94
|
+
if (targetElement) {
|
|
95
|
+
const { top } = targetElement.getBoundingClientRect();
|
|
96
|
+
const distance = Math.abs(top);
|
|
97
|
+
if (top <= yOffset && distance < closestDistance) {
|
|
98
|
+
closestId = id;
|
|
99
|
+
closestDistance = distance;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (activeIdRef.current !== closestId) {
|
|
105
|
+
setActiveId(closestId);
|
|
106
|
+
}
|
|
107
|
+
}, [yOffset]); // 移除 activeId 依赖,因为我们只需要比较当前值
|
|
108
|
+
|
|
109
|
+
// 使用节流函数包装handleScroll
|
|
110
|
+
const throttledHandleScroll = useMemo(
|
|
111
|
+
() => throttle(handleScroll, 100, { leading: true, trailing: true }),
|
|
112
|
+
[handleScroll]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const handleHashChange = () => {
|
|
117
|
+
const hash = window.location.hash.substring(1);
|
|
118
|
+
setActiveId(hash);
|
|
119
|
+
|
|
120
|
+
const element = document.getElementById(hash);
|
|
121
|
+
if (element) {
|
|
122
|
+
const y =
|
|
123
|
+
element.getBoundingClientRect().top + window.scrollY - yOffset;
|
|
124
|
+
window.scrollTo({ top: y, behavior: "smooth" });
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
129
|
+
window.addEventListener("scroll", throttledHandleScroll);
|
|
130
|
+
|
|
131
|
+
// 初始化时检查当前哈希值
|
|
132
|
+
handleHashChange();
|
|
133
|
+
|
|
134
|
+
return () => {
|
|
135
|
+
window.removeEventListener("hashchange", handleHashChange);
|
|
136
|
+
window.removeEventListener("scroll", throttledHandleScroll);
|
|
137
|
+
throttledHandleScroll.cancel(); // 清理节流函数
|
|
138
|
+
};
|
|
139
|
+
}, [handleScroll, throttledHandleScroll, yOffset]);
|
|
140
|
+
|
|
141
|
+
return activeId;
|
|
142
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useAnchorScroll() {
|
|
4
|
+
const handleAnchorClick = useCallback((event: MouseEvent): void => {
|
|
5
|
+
const target = event.target as HTMLAnchorElement;
|
|
6
|
+
if (target.tagName === 'A' && target.getAttribute('href')?.startsWith('#')) {
|
|
7
|
+
event.preventDefault();
|
|
8
|
+
const id = target.getAttribute('href')?.slice(1);
|
|
9
|
+
const element = document.getElementById(id || '');
|
|
10
|
+
if (element) {
|
|
11
|
+
const yOffset = -100; // 假设你想要元素距离顶部100px
|
|
12
|
+
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
|
13
|
+
window.scrollTo({ top: y, behavior: 'smooth' });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
document.addEventListener('click', handleAnchorClick);
|
|
20
|
+
return () => document.removeEventListener('click', handleAnchorClick);
|
|
21
|
+
}, [handleAnchorClick]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default useAnchorScroll;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const defaultThreshold = 10;
|
|
4
|
+
|
|
5
|
+
export type ScrollerProps = {
|
|
6
|
+
threshold?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function Scroller(props: ScrollerProps) {
|
|
10
|
+
const { threshold = defaultThreshold } = props;
|
|
11
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
12
|
+
const [win, setWin] = useState<Window | null>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (ref.current) {
|
|
16
|
+
setWin(ref.current.ownerDocument.defaultView);
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const onScroll = useCallback(() => {
|
|
21
|
+
if (!win) return;
|
|
22
|
+
const scrolled = win.scrollY > threshold;
|
|
23
|
+
const doc = win.document;
|
|
24
|
+
|
|
25
|
+
if (scrolled) {
|
|
26
|
+
doc.body.classList.add("scrolled");
|
|
27
|
+
} else {
|
|
28
|
+
doc.body.classList.remove("scrolled");
|
|
29
|
+
}
|
|
30
|
+
}, [threshold, win]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!win) return;
|
|
34
|
+
win.addEventListener("scroll", onScroll);
|
|
35
|
+
return () => win.removeEventListener("scroll", onScroll);
|
|
36
|
+
}, [onScroll, win]);
|
|
37
|
+
|
|
38
|
+
return <div ref={ref} style={{ display: "none" }} />;
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { forwardRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type SearchProps = React.InputHTMLAttributes<HTMLInputElement>;
|
|
4
|
+
|
|
5
|
+
export const SearchInput = forwardRef<HTMLInputElement, SearchProps>(
|
|
6
|
+
(props, ref) => {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
const { children, ...rest } = props;
|
|
9
|
+
const [keyword, setKeyword] = useState<string>();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<input
|
|
13
|
+
ref={ref}
|
|
14
|
+
name="q"
|
|
15
|
+
value={keyword}
|
|
16
|
+
onChange={(e) => setKeyword(e.target.value)}
|
|
17
|
+
{...rest}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useState } from "react";
|
|
2
|
+
import type { IconListType } from "./socials";
|
|
3
|
+
import { iconList } from "./socials";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
|
|
6
|
+
export type ShareProps = {
|
|
7
|
+
socials?: string[];
|
|
8
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
9
|
+
className?: string;
|
|
10
|
+
classNames?: {
|
|
11
|
+
item?: string;
|
|
12
|
+
itemIcon?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function getPageDetails() {
|
|
17
|
+
if (typeof window === "undefined") {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const details = {
|
|
21
|
+
url: window?.location?.href,
|
|
22
|
+
title: document?.title || "null",
|
|
23
|
+
description: "null", // 默认为空字符串
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const descriptionMetaTag = document.querySelector('meta[name="description"]');
|
|
27
|
+
if (descriptionMetaTag) {
|
|
28
|
+
details.description = (descriptionMetaTag as HTMLMetaElement).content;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return details;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const Share = forwardRef<HTMLDivElement, ShareProps>(
|
|
35
|
+
(props: ShareProps, ref) => {
|
|
36
|
+
const { className, classNames, ...rest } = props;
|
|
37
|
+
const [socialList, setSolicalList] = useState<IconListType>();
|
|
38
|
+
|
|
39
|
+
const details = getPageDetails();
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setSolicalList(iconList);
|
|
43
|
+
}, [props.socials]);
|
|
44
|
+
|
|
45
|
+
// 生成每个社交媒体的分享链接
|
|
46
|
+
const generateLink = (key: string) => {
|
|
47
|
+
const social = iconList[key];
|
|
48
|
+
if (!social) return "#";
|
|
49
|
+
return social.url(
|
|
50
|
+
details?.url || "",
|
|
51
|
+
details?.title,
|
|
52
|
+
details?.description
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div ref={ref} className={clsx("flex items-center", className)} {...rest}>
|
|
58
|
+
<div className="flex space-x-3">
|
|
59
|
+
{Object.keys(socialList || {}).map((key) => (
|
|
60
|
+
<a
|
|
61
|
+
key={key}
|
|
62
|
+
href={generateLink(key)}
|
|
63
|
+
target="_blank"
|
|
64
|
+
rel="noopener noreferrer"
|
|
65
|
+
title={`Share on ${iconList[key].title}`}
|
|
66
|
+
className={clsx(
|
|
67
|
+
"flex h-6 w-6 items-center justify-center ",
|
|
68
|
+
classNames?.item
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
<svg
|
|
72
|
+
className={clsx("w-5 h-5", classNames?.itemIcon)}
|
|
73
|
+
fill="currentColor"
|
|
74
|
+
viewBox="0 0 24 24"
|
|
75
|
+
focusable="false"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
>
|
|
78
|
+
{iconList[key].path}
|
|
79
|
+
</svg>
|
|
80
|
+
</a>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface IconListType {
|
|
4
|
+
[key: string]: {
|
|
5
|
+
title: string;
|
|
6
|
+
path: React.ReactElement;
|
|
7
|
+
url: (l: string, t?: string, ti?: string) => string;
|
|
8
|
+
color: string;
|
|
9
|
+
viewBox?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const iconList: IconListType = {
|
|
14
|
+
linkedin: {
|
|
15
|
+
title: "LinkedIn",
|
|
16
|
+
path: (
|
|
17
|
+
<path d="M6.5 21.5h-5v-13h5v13zM4 6.5C2.5 6.5 1.5 5.3 1.5 4s1-2.4 2.5-2.4c1.6 0 2.5 1 2.6 2.5 0 1.4-1 2.5-2.6 2.5zm11.5 6c-1 0-2 1-2 2v7h-5v-13h5V10s1.6-1.5 4-1.5c3 0 5 2.2 5 6.3v6.7h-5v-7c0-1-1-2-2-2z" />
|
|
18
|
+
),
|
|
19
|
+
color: "#0073b1",
|
|
20
|
+
url: (l, t, ti) =>
|
|
21
|
+
`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(l)}&title=${encodeURIComponent(ti || "")}&summary=${encodeURIComponent(t || "")}`,
|
|
22
|
+
},
|
|
23
|
+
facebook: {
|
|
24
|
+
title: "Facebook",
|
|
25
|
+
path: (
|
|
26
|
+
<path d="M24 12a12 12 0 10-13.9 11.9v-8.4h-3V12h3V9.4c0-3 1.8-4.7 4.6-4.7l2.6.2v3h-1.5c-1.5 0-2 .9-2 1.8V12h3.4l-.5 3.5h-2.8v8.4A12 12 0 0024 12z" />
|
|
27
|
+
),
|
|
28
|
+
color: "#0076FB",
|
|
29
|
+
url: (l) => `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(l)}`,
|
|
30
|
+
},
|
|
31
|
+
twitter: {
|
|
32
|
+
title: "X",
|
|
33
|
+
path: (
|
|
34
|
+
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path>
|
|
35
|
+
),
|
|
36
|
+
color: "#0F1419",
|
|
37
|
+
url: (l, t) => `https://twitter.com/intent/tweet?text=${t}&url=${encodeURIComponent(l)}`,
|
|
38
|
+
},
|
|
39
|
+
whatsapp: {
|
|
40
|
+
title: "WhatsApp",
|
|
41
|
+
path: (
|
|
42
|
+
<path d="M17.5 14.4l-2-1c-.3 0-.5-.1-.7.2l-1 1.1c-.1.2-.3.3-.6.1s-1.3-.5-2.4-1.5a9 9 0 01-1.7-2c-.1-.3 0-.5.2-.6l.4-.6c.2-.1.2-.3.3-.5v-.5L9 7c-.2-.6-.4-.5-.6-.5h-.6c-.2 0-.5 0-.8.4-.2.3-1 1-1 2.5s1 2.8 1.2 3c.2.2 2.1 3.2 5.1 4.5l1.7.6a4 4 0 001.9.2c.5-.1 1.7-.8 2-1.5.2-.6.2-1.2.1-1.4l-.5-.3M12 21.8a9.9 9.9 0 01-5-1.4l-.4-.2-3.7 1 1-3.7-.2-.3a9.9 9.9 0 01-1.5-5.3 9.9 9.9 0 0116.8-7 9.8 9.8 0 013 7 9.9 9.9 0 01-10 9.9m8.4-18.3A11.8 11.8 0 0012.1 0 12 12 0 001.8 17.8L0 24l6.4-1.6a11.9 11.9 0 005.6 1.4 12 12 0 0012-11.9 11.8 11.8 0 00-3.5-8.4z" />
|
|
43
|
+
),
|
|
44
|
+
color: "#25D366",
|
|
45
|
+
url: (l, t) => `https://api.whatsapp.com/send?text=${encodeURIComponent(t || "")} ${encodeURIComponent(l)}`,
|
|
46
|
+
},
|
|
47
|
+
reddit: {
|
|
48
|
+
title: "Reddit",
|
|
49
|
+
path: (
|
|
50
|
+
<path d="M12 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0zm5.01 4.74c.69 0 1.25.56 1.25 1.25a1.25 1.25 0 01-2.5.06l-2.6-.55-.8 3.75c1.83.07 3.48.63 4.68 1.49.3-.31.73-.5 1.2-.5.97 0 1.76.8 1.76 1.76 0 .72-.43 1.33-1.01 1.61a3.11 3.11 0 01.04.52c0 2.7-3.13 4.87-7 4.87-3.88 0-7-2.17-7-4.87 0-.18 0-.36.04-.53A1.75 1.75 0 014.03 12a1.75 1.75 0 012.96-1.26 8.52 8.52 0 014.74-1.5l.89-4.17a.34.34 0 01.14-.2.35.35 0 01.24-.04l2.9.62a1.21 1.21 0 011.11-.7zM9.25 12a1.25 1.25 0 101.25 1.25c0-.69-.56-1.25-1.25-1.25zm5.5 0a1.25 1.25 0 000 2.5 1.25 1.25 0 000-2.5zm-5.47 3.99a.33.33 0 00-.23.1.33.33 0 000 .46c.84.84 2.49.91 2.96.91.48 0 2.1-.06 2.96-.91a.36.36 0 00.03-.47.33.33 0 00-.46 0c-.55.54-1.68.73-2.51.73-.83 0-1.98-.2-2.51-.73a.33.33 0 00-.24-.1z" />
|
|
51
|
+
),
|
|
52
|
+
color: "#FF4500",
|
|
53
|
+
url: (l, t) => `https://www.reddit.com/submit?url=${encodeURIComponent(l)}&title=${encodeURIComponent(t || "")}`,
|
|
54
|
+
},
|
|
55
|
+
// telegram: {
|
|
56
|
+
// title: "Telegram",
|
|
57
|
+
// path: (
|
|
58
|
+
// <path d="M23.91 3.79L20.3 20.84c-.25 1.21-.98 1.5-2 .94l-5.5-4.07-2.66 2.57c-.3.3-.55.56-1.1.56-.72 0-.6-.27-.84-.95L6.3 13.7.85 12c-1.18-.35-1.19-1.16.26-1.75l21.26-8.2c.97-.43 1.9.24 1.53 1.73z" />
|
|
59
|
+
// ),
|
|
60
|
+
// color: "#0088CC",
|
|
61
|
+
// url: (l, t) => `https://telegram.me/share/msg?url=${encodeURIComponent(l)}&text=${encodeURIComponent(t || "")}`,
|
|
62
|
+
// },
|
|
63
|
+
|
|
64
|
+
// pinterest: {
|
|
65
|
+
// title: "Pinterest",
|
|
66
|
+
// path: (
|
|
67
|
+
// <path d="M0 12C0 17.123 3.211 21.497 7.73 23.218C7.62 22.281 7.503 20.736 7.755 19.652C7.972 18.72 9.156 13.714 9.156 13.714C9.156 13.714 8.799 12.999 8.799 11.94C8.799 10.28 9.761 9.04 10.96 9.04C11.98 9.04 12.472 9.805 12.472 10.722C12.472 11.747 11.819 13.279 11.482 14.7C11.201 15.889 12.079 16.859 13.251 16.859C15.374 16.859 17.007 14.62 17.007 11.388C17.007 8.527 14.951 6.528 12.016 6.528C8.618 6.528 6.623 9.077 6.623 11.712C6.623 12.739 7.018 13.839 7.512 14.438C7.55412 14.4832 7.58387 14.5386 7.59841 14.5986C7.61295 14.6587 7.61177 14.7215 7.595 14.781C7.504 15.159 7.302 15.97 7.263 16.136C7.21 16.354 7.09 16.401 6.863 16.295C5.371 15.601 4.439 13.42 4.439 11.668C4.439 7.899 7.176 4.439 12.331 4.439C16.475 4.439 19.696 7.392 19.696 11.338C19.696 15.455 17.101 18.769 13.497 18.769C12.286 18.769 11.149 18.139 10.759 17.396C10.759 17.396 10.16 19.678 10.015 20.236C9.733 21.32 8.951 22.692 8.466 23.471C9.584 23.815 10.77 24 12 24C18.627 24 24 18.627 24 12C24 5.373 18.627 0 12 0C5.373 0 0 5.373 0 12Z" />
|
|
68
|
+
// ),
|
|
69
|
+
// color: "#c8232c",
|
|
70
|
+
// url: (l) => `http://pinterest.com/pin/create/link/?url=${encodeURIComponent(l)}`,
|
|
71
|
+
// },
|
|
72
|
+
// email: {
|
|
73
|
+
// title: "Email",
|
|
74
|
+
// path: (
|
|
75
|
+
// <path d="M20 4H4a2 2 0 00-2 2v12c0 1.1.9 2 2 2h16a2 2 0 002-2V6a2 2 0 00-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
|
76
|
+
// ),
|
|
77
|
+
// color: "#E53E3E",
|
|
78
|
+
// url: (l, t) => `mailto:?body=${encodeURIComponent(l)}&subject=${encodeURIComponent(t || "")}`,
|
|
79
|
+
// },
|
|
80
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
Facebook调试:https://developers.facebook.com/tools/debug/
|
|
3
|
+
|
|
4
|
+
https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started
|
|
5
|
+
https://developers.facebook.com/docs/sharing/webmasters/
|
|
6
|
+
https://developers.pinterest.com/docs/rich-pins/rich-pins/
|
|
7
|
+
https://www.linkedin.com/help/linkedin/answer/a521928/making-your-website-shareable-on-linkedin?lang=en
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { forwardRef, useRef, useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
export type ToTopProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
svg?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const topIcon = () => (
|
|
10
|
+
<svg
|
|
11
|
+
className="h-6 w-6"
|
|
12
|
+
width="1.5rem"
|
|
13
|
+
height="1.5rem"
|
|
14
|
+
fill="none"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
>
|
|
18
|
+
<path
|
|
19
|
+
strokeLinecap="round"
|
|
20
|
+
strokeLinejoin="round"
|
|
21
|
+
strokeWidth={2}
|
|
22
|
+
d="M5 15l7-7 7 7"
|
|
23
|
+
/>
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const ToTop = forwardRef<HTMLDivElement, ToTopProps>((props, ref) => {
|
|
28
|
+
const { className, svg, ...rest } = props;
|
|
29
|
+
const [win, setWin] = useState<Window | null>(null);
|
|
30
|
+
const innerRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
|
|
32
|
+
// 合并外部传入的ref和内部ref
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!innerRef.current) return;
|
|
35
|
+
|
|
36
|
+
// 获取当前元素所在的window
|
|
37
|
+
const currentWin = innerRef.current.ownerDocument.defaultView;
|
|
38
|
+
setWin(currentWin);
|
|
39
|
+
|
|
40
|
+
// 如果外部传入的是ref对象,则设置其current属性
|
|
41
|
+
if (ref && typeof ref === "object") {
|
|
42
|
+
ref.current = innerRef.current;
|
|
43
|
+
}
|
|
44
|
+
}, [ref]);
|
|
45
|
+
|
|
46
|
+
const handleClick = () => {
|
|
47
|
+
if (win) {
|
|
48
|
+
win.scrollTo({ top: 0, behavior: "smooth" });
|
|
49
|
+
} else if (typeof window !== "undefined") {
|
|
50
|
+
// 降级处理:如果没有获取到特定window,使用全局window
|
|
51
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
ref={innerRef}
|
|
58
|
+
className={clsx(
|
|
59
|
+
"fixed bottom-4 right-4 hidden user-select-none shadow-lg scrolled:flex cursor-pointer transition duration-300 ease-in-out z-50",
|
|
60
|
+
className
|
|
61
|
+
)}
|
|
62
|
+
{...rest}
|
|
63
|
+
onClick={handleClick}
|
|
64
|
+
>
|
|
65
|
+
{svg ? (
|
|
66
|
+
<div className="contents" dangerouslySetInnerHTML={{ __html: svg }} />
|
|
67
|
+
) : (
|
|
68
|
+
topIcon()
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { clsx } from "clsx";
|
|
2
|
+
|
|
3
|
+
export function VideoPlayIcon(props: {
|
|
4
|
+
classNames?: {
|
|
5
|
+
playButton?: string;
|
|
6
|
+
playButtonOuter?: string;
|
|
7
|
+
playButtonInner?: string;
|
|
8
|
+
playIcon?: string;
|
|
9
|
+
};
|
|
10
|
+
}) {
|
|
11
|
+
const { classNames } = props;
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={clsx(
|
|
15
|
+
"flex items-center justify-center w-14 h-14 lg:w-[130px] lg:h-[130px] bg-white/15 rounded-full backdrop-blur-sm hover:shadow-md transition-all duration-300 hover:scale-110 group",
|
|
16
|
+
classNames?.playButtonOuter,
|
|
17
|
+
)}
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
className={clsx(
|
|
21
|
+
"w-10 h-10 lg:w-[90px] lg:h-[90px] bg-white relative overflow-hidden rounded-full",
|
|
22
|
+
classNames?.playButtonInner,
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<div className="absolute inset-0 cross-gradient box-shadow-1">
|
|
26
|
+
<div className="w-full h-full flex items-center justify-center">
|
|
27
|
+
<svg
|
|
28
|
+
className={clsx(
|
|
29
|
+
"size-4 lg:size-8 text-gray-700",
|
|
30
|
+
classNames?.playIcon,
|
|
31
|
+
)}
|
|
32
|
+
viewBox="0 0 30 32"
|
|
33
|
+
fill="currentColor"
|
|
34
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
35
|
+
>
|
|
36
|
+
<path d="M27.2003 19.4972C29.945 17.9735 29.945 14.0264 27.2003 12.5027L6.44148 0.978573C3.77538 -0.501503 0.5 1.42643 0.5 4.47581V27.5241C0.5 30.5735 3.77537 32.5014 6.44147 31.0214L27.2003 19.4972Z" />
|
|
37
|
+
</svg>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Analytics } from "./Analytics";
|
|
2
|
+
import { AttachmentIcon } from "./AttachmentIcon";
|
|
3
|
+
import { BackgroundVideoPlayer } from "./BackgroundVideoPlayer";
|
|
4
|
+
import { Bulletin } from "./Bulletin";
|
|
5
|
+
import { ContactForm } from "./ContactForm";
|
|
6
|
+
import { Icon } from "./Icon";
|
|
7
|
+
import { ReactModalTrigger } from "./ReactModalTrigger";
|
|
8
|
+
import { Scroller } from "./Scroller";
|
|
9
|
+
import { SearchInput } from "./SearchInput";
|
|
10
|
+
import { Share } from "./Share";
|
|
11
|
+
import { ToTop } from "./ToTop";
|
|
12
|
+
|
|
13
|
+
export const allCoreComponents = {
|
|
14
|
+
Analytics,
|
|
15
|
+
AttachmentIcon,
|
|
16
|
+
BackgroundVideoPlayer,
|
|
17
|
+
Bulletin,
|
|
18
|
+
ContactForm,
|
|
19
|
+
Icon,
|
|
20
|
+
ReactModalTrigger,
|
|
21
|
+
Scroller,
|
|
22
|
+
SearchInput,
|
|
23
|
+
Share,
|
|
24
|
+
ToTop,
|
|
25
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./Analytics";
|
|
2
|
+
export * from "./AttachmentIcon";
|
|
3
|
+
export * from "./ContactForm";
|
|
4
|
+
export * from "./Icon";
|
|
5
|
+
export * from "./Share";
|
|
6
|
+
export * from "./Scroller";
|
|
7
|
+
export * from "./SearchInput";
|
|
8
|
+
export * from "./ToTop";
|
|
9
|
+
export * from "./BackgroundVideoPlayer";
|
|
10
|
+
export * from "./Bulletin";
|
|
11
|
+
export * from "./ReactModalTrigger";
|
|
12
|
+
export * from "./all";
|
package/src/forms.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components/ContactForm";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components";
|
package/src/media.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components/Medias";
|
package/src/richtext.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components/RichTextOutline";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Media, PageMeta } from "@rxdrag/rxcms-models";
|
|
2
|
+
|
|
3
|
+
export type TMedia = Media & {
|
|
4
|
+
alt?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type TProductCategory = {
|
|
8
|
+
id?: string | null;
|
|
9
|
+
slug?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
children?: TProductCategory[];
|
|
12
|
+
media?: TMedia;
|
|
13
|
+
parent?: TProductCategory;
|
|
14
|
+
products?: TProduct[];
|
|
15
|
+
description?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TProduct = {
|
|
19
|
+
id?: string | null;
|
|
20
|
+
slug?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
shortTitle?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
features?: string;
|
|
25
|
+
medias?: TMedia[];
|
|
26
|
+
cover?: Media;
|
|
27
|
+
content?: string;
|
|
28
|
+
coverUrl?: string;
|
|
29
|
+
price?: string;
|
|
30
|
+
publishedAt?: Date;
|
|
31
|
+
createdAt?: Date;
|
|
32
|
+
updatedAt?: Date;
|
|
33
|
+
category?: TProductCategory;
|
|
34
|
+
related?: TProduct[];
|
|
35
|
+
attachments?: Media[];
|
|
36
|
+
meta?: PageMeta;
|
|
37
|
+
};
|