@rxdrag/website-lib 0.0.123 → 0.0.124
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/components/CookieConsent.astro +41 -0
- package/components/CookieConsent.css +52 -0
- package/components/CookieConsentConfig.ts +208 -0
- package/components/Heading.astro +5 -2
- package/components/Image.astro +66 -10
- package/components/Image.md +15 -0
- package/components/Image.type.ts +226 -0
- package/components/TabButton.astro +16 -0
- package/components/TabPanel.astro +19 -0
- package/components/Tabs.astro +147 -0
- package/index.ts +1 -8
- package/package.json +6 -5
- package/components/Icon.astro +0 -30
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "vanilla-cookieconsent/dist/cookieconsent.css";
|
|
3
|
+
import "./CookieConsent.css";
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
import { run } from "vanilla-cookieconsent";
|
|
8
|
+
import { config } from "./CookieConsentConfig";
|
|
9
|
+
|
|
10
|
+
// 异步初始化Cookie同意逻辑
|
|
11
|
+
async function initializeCookieConsent() {
|
|
12
|
+
try {
|
|
13
|
+
// 始终显示Cookie同意UI
|
|
14
|
+
document.body.classList.add("cc--elegant-black");
|
|
15
|
+
run(config);
|
|
16
|
+
|
|
17
|
+
// 显示偏好设置按钮
|
|
18
|
+
const preferencesBtn = document.getElementById("show-preferences-btn");
|
|
19
|
+
if (preferencesBtn) {
|
|
20
|
+
preferencesBtn.style.display = "block";
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error("Cookie同意初始化失败:", error);
|
|
24
|
+
// 出错时默认显示同意UI(安全策略)
|
|
25
|
+
document.body.classList.add("cc--elegant-black");
|
|
26
|
+
run(config);
|
|
27
|
+
|
|
28
|
+
const preferencesBtn = document.getElementById("show-preferences-btn");
|
|
29
|
+
if (preferencesBtn) {
|
|
30
|
+
preferencesBtn.style.display = "block";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 页面加载完成后初始化
|
|
36
|
+
if (document.readyState === "loading") {
|
|
37
|
+
document.addEventListener("DOMContentLoaded", initializeCookieConsent);
|
|
38
|
+
} else {
|
|
39
|
+
initializeCookieConsent();
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.cc--elegant-black {
|
|
2
|
+
--cc-bg: #000;
|
|
3
|
+
--cc-primary-color: rgb(239, 244, 246);
|
|
4
|
+
--cc-secondary-color: #b1bdc3 !important;
|
|
5
|
+
|
|
6
|
+
--cc-btn-primary-bg: #ffffff;
|
|
7
|
+
--cc-btn-primary-color: #000;
|
|
8
|
+
--cc-btn-primary-hover-bg: #ccd4d8;
|
|
9
|
+
--cc-btn-primary-hover-color: #000;
|
|
10
|
+
--cc-btn-primary-border-color: var(--cc-btn-primary-bg);
|
|
11
|
+
|
|
12
|
+
--cc-btn-secondary-bg: rgba(255, 255, 255, 0.039);
|
|
13
|
+
--cc-btn-secondary-color: var(--cc-primary-color);
|
|
14
|
+
--cc-btn-secondary-border-color: #252729;
|
|
15
|
+
--cc-btn-secondary-hover-bg: #252729;
|
|
16
|
+
--cc-btn-secondary-hover-color: #fff;
|
|
17
|
+
--cc-btn-secondary-hover-border-color: #252729;
|
|
18
|
+
|
|
19
|
+
--cc-cookie-category-block-bg: #101111;
|
|
20
|
+
--cc-cookie-category-block-border: #1d1e1f;
|
|
21
|
+
--cc-cookie-category-block-hover-bg: #151516;
|
|
22
|
+
--cc-cookie-category-block-hover-border: #1d1e1f;
|
|
23
|
+
--cc-cookie-category-expanded-block-hover-bg: #1d1e1f;
|
|
24
|
+
--cc-cookie-category-expanded-block-bg: #101111;
|
|
25
|
+
--cc-toggle-readonly-bg: #2f3132;
|
|
26
|
+
--cc-overlay-bg: rgba(0, 0, 0, 0.9) !important;
|
|
27
|
+
|
|
28
|
+
--cc-toggle-on-bg: var(--cc-btn-primary-bg);
|
|
29
|
+
--cc-toggle-off-bg: #525f6b;
|
|
30
|
+
--cc-toggle-on-knob-bg: var(--cc-bg);
|
|
31
|
+
--cc-toggle-off-knob-bg: var(--cc-btn-primary-color);
|
|
32
|
+
--cc-toggle-readonly-knob-bg: var(--cc-cookie-category-block-bg);
|
|
33
|
+
--cc-toggle-enabled-icon-color: var(--cc-btn-primary-color);
|
|
34
|
+
--cc-toggle-disabled-icon-color: var(--cc-btn-primary-color);
|
|
35
|
+
--cc-toggle-readonly-knob-icon-color: var(--cc-toggle-readonly-bg);
|
|
36
|
+
--cc-section-category-border: #1e2428;
|
|
37
|
+
|
|
38
|
+
--cc-webkit-scrollbar-bg: var(--cc-section-category-border);
|
|
39
|
+
--cc-webkit-scrollbar-hover-bg: var(--cc-btn-primary-hover-bg);
|
|
40
|
+
|
|
41
|
+
--cc-separator-border-color: #252729;
|
|
42
|
+
|
|
43
|
+
--cc-footer-bg: #000;
|
|
44
|
+
--cc-footer-color: var(--cc-secondary-color);
|
|
45
|
+
--cc-footer-border-color: #212529;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.cc--elegant-black #cc-main .cm,
|
|
49
|
+
.cc--elegant-black #cc-main .pm {
|
|
50
|
+
color-scheme: dark;
|
|
51
|
+
border: 1px solid var(--cc-separator-border-color);
|
|
52
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { CookieConsentConfig } from "vanilla-cookieconsent";
|
|
2
|
+
|
|
3
|
+
const GA_MEASUREMENT_ID = (import.meta.env.PUBLIC_GA_MEASUREMENT_ID as string) || "";
|
|
4
|
+
const ANONYMIZE_IP = (import.meta.env.PUBLIC_GA_ANONYMIZE_IP as string) === "false" ? false : true;
|
|
5
|
+
const COOKIE_FLAGS = (import.meta.env.PUBLIC_GA_COOKIE_FLAGS as string) || "SameSite=None;Secure";
|
|
6
|
+
|
|
7
|
+
// 声明全局 gtag 函数类型
|
|
8
|
+
declare global {
|
|
9
|
+
function gtag(...args: any[]): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function updateConsent(consent: {
|
|
13
|
+
analytics_storage: "granted" | "denied";
|
|
14
|
+
ad_storage: "granted" | "denied";
|
|
15
|
+
}) {
|
|
16
|
+
console.log("[CookieConsent] updateConsent", consent);
|
|
17
|
+
if (typeof gtag !== "undefined") {
|
|
18
|
+
gtag("consent", "update", consent);
|
|
19
|
+
} else {
|
|
20
|
+
console.log("gtag 未定义,无法更新 consent", consent);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 加载 Google Analytics 脚本
|
|
25
|
+
function loadGA4() {
|
|
26
|
+
if (!GA_MEASUREMENT_ID) {
|
|
27
|
+
console.log("GA_MEASUREMENT_ID 为空,跳过 GA4 config");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log("[CookieConsent] loadGA4 GA_MEASUREMENT_ID:", GA_MEASUREMENT_ID);
|
|
32
|
+
|
|
33
|
+
console.log("Loading Google Analytics with ID:", GA_MEASUREMENT_ID);
|
|
34
|
+
|
|
35
|
+
// 复用 HeadCode.astro 中的全局 gtag(不要再创建第二套 dataLayer/gtag)
|
|
36
|
+
if (typeof gtag !== "undefined") {
|
|
37
|
+
gtag("config", GA_MEASUREMENT_ID, {
|
|
38
|
+
anonymize_ip: ANONYMIZE_IP,
|
|
39
|
+
cookie_flags: COOKIE_FLAGS,
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
console.log("gtag 未定义,跳过 GA4 config");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 直接加载 Google Analytics(非欧洲地区使用)
|
|
47
|
+
export function loadGA4Directly() {
|
|
48
|
+
if (!GA_MEASUREMENT_ID) {
|
|
49
|
+
console.log("GA_MEASUREMENT_ID 为空,跳过直接 GA4 config");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(
|
|
54
|
+
"[CookieConsent] loadGA4Directly GA_MEASUREMENT_ID:",
|
|
55
|
+
GA_MEASUREMENT_ID
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
console.log("直接加载 Google Analytics (无需用户同意):", GA_MEASUREMENT_ID);
|
|
59
|
+
|
|
60
|
+
// 复用 HeadCode.astro 中的全局 gtag(不要再创建第二套 dataLayer/gtag)
|
|
61
|
+
if (typeof gtag !== "undefined") {
|
|
62
|
+
gtag("config", GA_MEASUREMENT_ID, {
|
|
63
|
+
anonymize_ip: ANONYMIZE_IP,
|
|
64
|
+
cookie_flags: COOKIE_FLAGS,
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
console.log("gtag 未定义,跳过 GA4 direct config");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 清除 Google Analytics cookies
|
|
72
|
+
function clearGA4Cookies() {
|
|
73
|
+
const gaCookies = document.cookie
|
|
74
|
+
.split(";")
|
|
75
|
+
.filter((cookie) => cookie.trim().startsWith("_ga"));
|
|
76
|
+
|
|
77
|
+
gaCookies.forEach((cookie) => {
|
|
78
|
+
const cookieName = cookie.split("=")[0].trim();
|
|
79
|
+
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${window.location.hostname}`;
|
|
80
|
+
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const config: CookieConsentConfig = {
|
|
85
|
+
guiOptions: {
|
|
86
|
+
consentModal: {
|
|
87
|
+
layout: "box inline",
|
|
88
|
+
position: "bottom left",
|
|
89
|
+
},
|
|
90
|
+
preferencesModal: {
|
|
91
|
+
layout: "box",
|
|
92
|
+
position: "right",
|
|
93
|
+
equalWeightButtons: true,
|
|
94
|
+
flipButtons: false,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
categories: {
|
|
98
|
+
necessary: {
|
|
99
|
+
readOnly: true,
|
|
100
|
+
},
|
|
101
|
+
functionality: {},
|
|
102
|
+
analytics: {
|
|
103
|
+
services: {
|
|
104
|
+
ga4: {
|
|
105
|
+
label:
|
|
106
|
+
'<a href="https://marketingplatform.google.com/about/analytics/terms/us/" target="_blank">Google Analytics 4</a>',
|
|
107
|
+
onAccept: () => {
|
|
108
|
+
console.log("Google Analytics 4 accepted");
|
|
109
|
+
console.log(
|
|
110
|
+
"[CookieConsent] onAccept, GA_MEASUREMENT_ID:",
|
|
111
|
+
GA_MEASUREMENT_ID
|
|
112
|
+
);
|
|
113
|
+
updateConsent({
|
|
114
|
+
analytics_storage: "granted",
|
|
115
|
+
ad_storage: "granted",
|
|
116
|
+
});
|
|
117
|
+
loadGA4();
|
|
118
|
+
},
|
|
119
|
+
onReject: () => {
|
|
120
|
+
console.log("Google Analytics 4 rejected");
|
|
121
|
+
console.log(
|
|
122
|
+
"[CookieConsent] onReject, GA_MEASUREMENT_ID:",
|
|
123
|
+
GA_MEASUREMENT_ID
|
|
124
|
+
);
|
|
125
|
+
clearGA4Cookies();
|
|
126
|
+
|
|
127
|
+
// 禁用 Google Analytics
|
|
128
|
+
updateConsent({
|
|
129
|
+
analytics_storage: "denied",
|
|
130
|
+
ad_storage: "denied",
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
cookies: [
|
|
134
|
+
{
|
|
135
|
+
name: /^_ga/,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: /^_gid/,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: /^_gat/,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
another: {
|
|
146
|
+
label: "Another one (dummy)",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
language: {
|
|
152
|
+
default: "en",
|
|
153
|
+
autoDetect: "browser",
|
|
154
|
+
translations: {
|
|
155
|
+
en: {
|
|
156
|
+
consentModal: {
|
|
157
|
+
title: "Your Privacy Matters",
|
|
158
|
+
description:
|
|
159
|
+
"We use cookies to enhance your browsing experience, provide personalized content, and analyze our traffic. You can manage your preferences or accept all cookies. For more information, please see our Privacy Policy.",
|
|
160
|
+
acceptAllBtn: "Accept all",
|
|
161
|
+
acceptNecessaryBtn: "Reject all",
|
|
162
|
+
showPreferencesBtn: "Manage preferences",
|
|
163
|
+
footer:
|
|
164
|
+
'<a href="/privacy-policy" target="_blank" rel="noopener">Privacy Policy</a>\n<a href="/terms" target="_blank" rel="noopener">Terms and Conditions</a>',
|
|
165
|
+
},
|
|
166
|
+
preferencesModal: {
|
|
167
|
+
title: "Consent Preferences Center",
|
|
168
|
+
acceptAllBtn: "Accept all",
|
|
169
|
+
acceptNecessaryBtn: "Reject all",
|
|
170
|
+
savePreferencesBtn: "Save preferences",
|
|
171
|
+
closeIconLabel: "Close modal",
|
|
172
|
+
serviceCounterLabel: "Service|Services",
|
|
173
|
+
sections: [
|
|
174
|
+
{
|
|
175
|
+
title: "How We Use Cookies",
|
|
176
|
+
description:
|
|
177
|
+
"Cookies help us provide, protect and improve our services. You can choose which categories you want to allow. Some cookies are necessary for the website to function and cannot be switched off.",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
title:
|
|
181
|
+
'Strictly Necessary Cookies <span class="pm__badge">Always Enabled</span>',
|
|
182
|
+
description:
|
|
183
|
+
"These cookies are essential for the website to operate properly. They enable basic functions like page navigation and access to secure areas of the website. The website cannot function properly without these cookies.",
|
|
184
|
+
linkedCategory: "necessary",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
title: "Functionality Cookies",
|
|
188
|
+
description:
|
|
189
|
+
"Functionality cookies allow the website to remember choices you make (such as your language or the region you are in) and provide enhanced, more personal features.",
|
|
190
|
+
linkedCategory: "functionality",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: "Analytics Cookies",
|
|
194
|
+
description:
|
|
195
|
+
"Analytics cookies help us understand how visitors interact with our website by collecting and reporting information anonymously. This helps us improve our services and your experience.",
|
|
196
|
+
linkedCategory: "analytics",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
title: "More Information",
|
|
200
|
+
description:
|
|
201
|
+
'For any query in relation to my policy on cookies and your choices, please <a class="cc__link" href="#yourdomain.com">contact me</a>.',
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
package/components/Heading.astro
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { headingLevelToTypographyClassName } from "@rxdrag/website-lib-core";
|
|
3
4
|
|
|
4
5
|
interface Props extends HTMLAttributes<"h1" | "h2" | "h3" | "h4" | "h5" | "h6"> {
|
|
5
6
|
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
7
|
+
customSize?: boolean;
|
|
6
8
|
className?: string;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
const { level, className, class: classAttr, ...rest } = Astro.props;
|
|
11
|
+
const { level, customSize = false, className, class: classAttr, ...rest } = Astro.props;
|
|
10
12
|
|
|
11
13
|
if (typeof level !== "number" || level < 1 || level > 6) {
|
|
12
14
|
console.log("[Heading] invalid level", level);
|
|
@@ -14,8 +16,9 @@ if (typeof level !== "number" || level < 1 || level > 6) {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const Tag = `h${level}`;
|
|
19
|
+
const baseClassName = customSize ? undefined : headingLevelToTypographyClassName[level];
|
|
17
20
|
---
|
|
18
21
|
|
|
19
|
-
<Tag class:list={[className, classAttr]} {...rest}>
|
|
22
|
+
<Tag class:list={[baseClassName, className, classAttr]} {...rest}>
|
|
20
23
|
<slot />
|
|
21
24
|
</Tag>
|
package/components/Image.astro
CHANGED
|
@@ -1,18 +1,74 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { Image as AstroImage, Picture as AstroPicture } from "astro:assets";
|
|
3
|
+
import { type ImageProps, SIZES_MAP } from "./Image.type";
|
|
4
4
|
|
|
5
5
|
interface Props extends ImageProps {}
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
src,
|
|
9
|
+
alt = "",
|
|
10
|
+
layout,
|
|
11
|
+
formats,
|
|
12
|
+
loading,
|
|
13
|
+
priority,
|
|
14
|
+
quality,
|
|
15
|
+
sizes = "auto",
|
|
16
|
+
class: className,
|
|
17
|
+
...restProps
|
|
18
|
+
} = Astro.props;
|
|
8
19
|
|
|
9
|
-
|
|
20
|
+
// 转换 sizes 预设为实际值
|
|
21
|
+
const actualSizes = SIZES_MAP[sizes];
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
// 判断是否使用 Picture(有 formats 时使用)
|
|
24
|
+
const usePicture = formats && formats.length > 0;
|
|
25
|
+
|
|
26
|
+
// 判断是否为字符串路径(远程/CMS/public)
|
|
27
|
+
const isStringSrc = typeof src === "string";
|
|
28
|
+
|
|
29
|
+
// 检测本地资产路径错误用法
|
|
30
|
+
if (isStringSrc && (src.startsWith("/src/") || src.startsWith("src/"))) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Image src="${src}" 是本地资产路径,必须使用 import 导入。\n` +
|
|
33
|
+
`正确用法:\n` +
|
|
34
|
+
` import myImage from "${src}";\n` +
|
|
35
|
+
` <Image src={myImage} />`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
12
38
|
---
|
|
13
39
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
40
|
+
{
|
|
41
|
+
isStringSrc ? (
|
|
42
|
+
<img
|
|
43
|
+
src={src}
|
|
44
|
+
alt={alt}
|
|
45
|
+
loading={loading}
|
|
46
|
+
class={className}
|
|
47
|
+
{...restProps}
|
|
48
|
+
/>
|
|
49
|
+
) : usePicture ? (
|
|
50
|
+
<AstroPicture
|
|
51
|
+
src={src}
|
|
52
|
+
alt={alt}
|
|
53
|
+
formats={formats}
|
|
54
|
+
quality={quality}
|
|
55
|
+
loading={priority ? "eager" : loading}
|
|
56
|
+
sizes={actualSizes}
|
|
57
|
+
class={className}
|
|
58
|
+
{...(layout ? { layout } : {})}
|
|
59
|
+
{...restProps}
|
|
60
|
+
/>
|
|
61
|
+
) : (
|
|
62
|
+
<AstroImage
|
|
63
|
+
src={src}
|
|
64
|
+
alt={alt}
|
|
65
|
+
quality={quality}
|
|
66
|
+
loading={loading}
|
|
67
|
+
sizes={actualSizes}
|
|
68
|
+
class={className}
|
|
69
|
+
{...(priority ? { priority } : {})}
|
|
70
|
+
{...(layout ? { layout } : {})}
|
|
71
|
+
{...restProps}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
| 中文名 | key | 说明 |
|
|
2
|
+
| ---- | ---------- | --------- |
|
|
3
|
+
| 图片 | src | 必填 |
|
|
4
|
+
| 替代文本 | alt | SEO / 无障碍 |
|
|
5
|
+
| 使用场景 | scene | 下拉 |
|
|
6
|
+
| 响应式 | responsive | 下拉 |
|
|
7
|
+
| 优先加载 | priority | LCP |
|
|
8
|
+
| 懒加载 | loading | 默认 lazy |
|
|
9
|
+
| 装饰图片 | decorative | 自动 aria |
|
|
10
|
+
|
|
11
|
+
## 最后一句总结(你可以记下来)
|
|
12
|
+
|
|
13
|
+
Tailwind 决定“看起来多大”
|
|
14
|
+
sizes 决定“下载多大”
|
|
15
|
+
这两件事发生在不同时间、不同层级
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "astro/types";
|
|
2
|
+
import type { ImageMetadata } from "astro";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 响应式布局模式(Astro 原生值)
|
|
6
|
+
* @see https://docs.astro.build/zh-cn/guides/images/
|
|
7
|
+
*/
|
|
8
|
+
export type ImageLayout =
|
|
9
|
+
| "responsive" // 跟随父容器,最大不超过原图(推荐)
|
|
10
|
+
| "full-width" // 填满父容器(100% 宽度)
|
|
11
|
+
| "fixed" // 固定尺寸,不响应
|
|
12
|
+
| "none"; // 不使用响应式
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 图片输出格式
|
|
16
|
+
*/
|
|
17
|
+
export type ImageFormat = "webp" | "avif" | "png" | "jpg" | "gif" | "svg";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 图片在页面中的布局位置(用于优化下载尺寸)
|
|
21
|
+
*/
|
|
22
|
+
export type ImageSizePreset =
|
|
23
|
+
| "auto" // 自动处理(让 layout 决定)
|
|
24
|
+
| "full" // 全宽内容
|
|
25
|
+
| "half" // 两栏布局(约 50%)
|
|
26
|
+
| "third" // 三栏布局(约 33%)
|
|
27
|
+
| "quarter" // 四栏布局(约 25%)
|
|
28
|
+
| "sidebar"; // 侧边栏内图片
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 布局预设 → sizes 属性映射
|
|
32
|
+
*/
|
|
33
|
+
export const SIZES_MAP: Record<ImageSizePreset, string | undefined> = {
|
|
34
|
+
auto: undefined,
|
|
35
|
+
full: "100vw",
|
|
36
|
+
half: "(max-width: 768px) 100vw, 50vw",
|
|
37
|
+
third: "(max-width: 768px) 100vw, 33vw",
|
|
38
|
+
quarter: "(max-width: 768px) 100vw, 25vw",
|
|
39
|
+
sidebar: "(max-width: 768px) 100vw, 300px",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* ============================================================
|
|
44
|
+
* 图片组件属性
|
|
45
|
+
* ============================================================
|
|
46
|
+
*
|
|
47
|
+
* 设计原则:
|
|
48
|
+
* 1. Props 只处理图片核心功能(来源、加载、优化)
|
|
49
|
+
* 2. 样式相关属性通过 Tailwind class 设置
|
|
50
|
+
*
|
|
51
|
+
* ============================================================
|
|
52
|
+
* 样式设置指南(通过 class 属性使用 Tailwind)
|
|
53
|
+
* ============================================================
|
|
54
|
+
*
|
|
55
|
+
* 【尺寸】
|
|
56
|
+
* - 宽度:w-full, w-1/2, w-64, w-[300px]
|
|
57
|
+
* - 高度:h-auto, h-64, h-screen, h-[200px]
|
|
58
|
+
* - 最大宽度:max-w-md, max-w-lg, max-w-full
|
|
59
|
+
*
|
|
60
|
+
* 【宽高比】
|
|
61
|
+
* - 正方形:aspect-square
|
|
62
|
+
* - 视频:aspect-video (16:9)
|
|
63
|
+
* - 自定义:aspect-[4/3], aspect-[3/2]
|
|
64
|
+
*
|
|
65
|
+
* 【填充模式】
|
|
66
|
+
* - 裁剪填满:object-cover(推荐用于背景图、卡片图)
|
|
67
|
+
* - 完整显示:object-contain(推荐用于 logo)
|
|
68
|
+
* - 拉伸填满:object-fill
|
|
69
|
+
* - 原始尺寸:object-none
|
|
70
|
+
*
|
|
71
|
+
* 【位置(配合 object-cover/contain 使用)】
|
|
72
|
+
* - 居中:object-center(默认)
|
|
73
|
+
* - 顶部:object-top
|
|
74
|
+
* - 底部:object-bottom
|
|
75
|
+
* - 左右:object-left, object-right
|
|
76
|
+
*
|
|
77
|
+
* 【圆角】
|
|
78
|
+
* - 小:rounded-sm
|
|
79
|
+
* - 中:rounded-md, rounded-lg
|
|
80
|
+
* - 大:rounded-xl, rounded-2xl
|
|
81
|
+
* - 圆形:rounded-full
|
|
82
|
+
*
|
|
83
|
+
* 【阴影】
|
|
84
|
+
* - 小:shadow-sm
|
|
85
|
+
* - 中:shadow, shadow-md
|
|
86
|
+
* - 大:shadow-lg, shadow-xl
|
|
87
|
+
*
|
|
88
|
+
* 【边框】
|
|
89
|
+
* - 细边框:border
|
|
90
|
+
* - 边框颜色:border-gray-200, border-white
|
|
91
|
+
*
|
|
92
|
+
* 【滤镜效果】
|
|
93
|
+
* - 灰度:grayscale
|
|
94
|
+
* - 模糊:blur-sm, blur-md
|
|
95
|
+
* - 亮度:brightness-50, brightness-150
|
|
96
|
+
* - 悬停效果:hover:scale-105, hover:brightness-110
|
|
97
|
+
*
|
|
98
|
+
* 使用示例:
|
|
99
|
+
* ```astro
|
|
100
|
+
* <Image
|
|
101
|
+
* src="/hero.jpg"
|
|
102
|
+
* alt="英雄图"
|
|
103
|
+
* class="w-full aspect-video object-cover rounded-lg shadow-lg"
|
|
104
|
+
* />
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export interface ImageProps extends Omit<HTMLAttributes<"img">, "src" | "width" | "height"> {
|
|
108
|
+
/**
|
|
109
|
+
* 图片来源
|
|
110
|
+
* - 本地图片:使用 import 导入
|
|
111
|
+
* - 远程图片:使用完整 URL
|
|
112
|
+
* - CMS 图片:使用媒体库路径
|
|
113
|
+
*/
|
|
114
|
+
src: string | ImageMetadata;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 图片描述(用于无障碍访问和 SEO)
|
|
118
|
+
* - 内容图片:填写描述文字
|
|
119
|
+
* - 装饰性图片:设为空字符串 alt="",屏幕阅读器会忽略
|
|
120
|
+
*/
|
|
121
|
+
alt?: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 响应式布局模式(Astro 原生值)
|
|
125
|
+
* - responsive: 跟随容器宽度,最大不超过原图尺寸(推荐)
|
|
126
|
+
* - full-width: 填满父容器(100% 宽度)
|
|
127
|
+
* - fixed: 固定尺寸,不响应屏幕变化
|
|
128
|
+
* - none: 不使用 Astro 响应式处理
|
|
129
|
+
*/
|
|
130
|
+
layout?: ImageLayout;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 输出格式(用于 Picture 组件)
|
|
134
|
+
* - 浏览器会自动选择最优格式
|
|
135
|
+
* - 推荐:['avif', 'webp'] 获得最佳压缩
|
|
136
|
+
* @example ['avif', 'webp']
|
|
137
|
+
*/
|
|
138
|
+
formats?: ImageFormat[];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 加载策略
|
|
142
|
+
* - lazy: 延迟加载,首屏外的图片使用
|
|
143
|
+
* - eager: 立即加载,首屏图片使用
|
|
144
|
+
* @default "lazy"
|
|
145
|
+
*/
|
|
146
|
+
loading?: "lazy" | "eager";
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 是否为重要图片(LCP 优化,Astro 原生支持)
|
|
150
|
+
* - 设为 true 会自动设置 loading="eager" 和 fetchpriority="high"
|
|
151
|
+
* - 仅对首屏最大的图片使用
|
|
152
|
+
*/
|
|
153
|
+
priority?: boolean;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 图片质量(1-100)
|
|
157
|
+
* - 数值越高质量越好,文件越大
|
|
158
|
+
* - 推荐 80-90 平衡质量和大小
|
|
159
|
+
* 备注:这个可以先不提供可视化支持,小白理解不了
|
|
160
|
+
* @default 80
|
|
161
|
+
*/
|
|
162
|
+
quality?: number;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 图片在页面中的布局位置(用于优化下载尺寸)
|
|
166
|
+
* - auto: 自动处理(推荐,让 layout 决定)
|
|
167
|
+
* - full: 全宽内容
|
|
168
|
+
* - half: 两栏布局
|
|
169
|
+
* - third: 三栏布局
|
|
170
|
+
* - quarter: 四栏布局
|
|
171
|
+
* - sidebar: 侧边栏内图片
|
|
172
|
+
* @default "auto"
|
|
173
|
+
*/
|
|
174
|
+
sizes?: ImageSizePreset;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 样式类(使用 Tailwind)
|
|
178
|
+
*
|
|
179
|
+
* 【避免布局偏移】
|
|
180
|
+
* 设置宽高比可预留空间,防止图片加载时页面跳动:
|
|
181
|
+
* - aspect-square(1:1)
|
|
182
|
+
* - aspect-video(16:9)
|
|
183
|
+
* - aspect-[4/3]、aspect-[3/2] 等自定义比例
|
|
184
|
+
*
|
|
185
|
+
* 【常用宽高比】
|
|
186
|
+
* - 1:1 - 正方形,电商产品、头像、社交卡片
|
|
187
|
+
* - 4:3 - 传统屏幕,博客卡片、新闻缩略图
|
|
188
|
+
* - 3:2 - 单反照片,摄影作品、画廊
|
|
189
|
+
* - 16:9 - 宽屏视频,英雄图、视频封面
|
|
190
|
+
* - 21:9 - 超宽屏,电影海报、横幅广告
|
|
191
|
+
* - 4:5 - 竖版,Instagram、社交媒体
|
|
192
|
+
* - 9:16 - 竖屏视频,Stories、短视频封面
|
|
193
|
+
*
|
|
194
|
+
* 【快捷模板】
|
|
195
|
+
* - 英雄图:w-full aspect-video object-cover
|
|
196
|
+
* - 博客卡片:w-full aspect-[16/9] object-cover rounded-lg
|
|
197
|
+
* - 新闻卡片:w-full aspect-[4/3] object-cover rounded-lg
|
|
198
|
+
* - 电商卡片:w-full aspect-square object-cover rounded-lg
|
|
199
|
+
* - 社交卡片:w-full aspect-[4/5] object-cover rounded-lg
|
|
200
|
+
* - 缩略图:w-32 aspect-square object-cover rounded
|
|
201
|
+
* - 头像(小):w-8 h-8 object-cover rounded-full
|
|
202
|
+
* - 头像(中):w-12 h-12 object-cover rounded-full
|
|
203
|
+
* - 头像(大):w-16 h-16 object-cover rounded-full
|
|
204
|
+
* - Logo:h-8 w-auto object-contain
|
|
205
|
+
* - 产品图:w-full aspect-square object-contain bg-white
|
|
206
|
+
* - 背景图:w-full h-full object-cover absolute inset-0
|
|
207
|
+
* - 画廊图:w-full aspect-[3/2] object-cover hover:scale-105 transition
|
|
208
|
+
* - 横幅广告:w-full aspect-[21/9] object-cover
|
|
209
|
+
*
|
|
210
|
+
* 【常用样式】
|
|
211
|
+
* - 尺寸:w-full, w-1/2, w-32, h-64, max-w-lg, max-w-screen-xl
|
|
212
|
+
* - 填充:object-cover(裁剪填满), object-contain(完整显示), object-fill(拉伸)
|
|
213
|
+
* - 位置:object-center, object-top, object-bottom, object-left, object-right
|
|
214
|
+
* - 圆角:rounded-sm, rounded, rounded-md, rounded-lg, rounded-xl, rounded-full
|
|
215
|
+
* - 阴影:shadow-sm, shadow, shadow-md, shadow-lg, shadow-xl
|
|
216
|
+
* - 边框:border, border-2, border-white, border-gray-200
|
|
217
|
+
* - 滤镜:grayscale, blur-sm, brightness-75, contrast-125
|
|
218
|
+
* - 过渡:transition, hover:scale-105, hover:brightness-110, hover:shadow-xl
|
|
219
|
+
*
|
|
220
|
+
* @example "w-full aspect-video object-cover rounded-lg"
|
|
221
|
+
*/
|
|
222
|
+
class?: string;
|
|
223
|
+
//兼容react组件
|
|
224
|
+
className?: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { class: className, className: className2, ...props } = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<button
|
|
11
|
+
class:list={["tab-btn", className, className2]}
|
|
12
|
+
data-tabs-btn="true"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</button>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
active?: boolean | string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { class: className, active, ...props } = Astro.props;
|
|
8
|
+
const isActive =
|
|
9
|
+
active === true ||
|
|
10
|
+
(typeof active === "string" && (active === "" || active === "true"));
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
data-tabs-panel=""
|
|
15
|
+
class:list={["tab-content", className, isActive ? "block" : "hidden"]}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
const rootId = crypto.randomUUID();
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
class?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { class: className, className: className2, ...props } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div data-tabs-root id={rootId} class:list={[className, className2]} {...props}>
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<script is:inline define:vars={{ rootId }}>
|
|
17
|
+
const defaultIndex = 0;
|
|
18
|
+
const activeBtnClassName = "active";
|
|
19
|
+
const inactiveBtnClassName = "";
|
|
20
|
+
const showPanelClassName = "block";
|
|
21
|
+
const hidePanelClassName = "hidden";
|
|
22
|
+
|
|
23
|
+
const getState = () => {
|
|
24
|
+
const w = window;
|
|
25
|
+
const all = (w.__tabs__ ||= {});
|
|
26
|
+
return (all[rootId] ||= { cleanup: null });
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const initTabs = () => {
|
|
30
|
+
const root = document.getElementById(rootId);
|
|
31
|
+
if (!root) return () => {};
|
|
32
|
+
|
|
33
|
+
const toClassList = (className) =>
|
|
34
|
+
String(className || "")
|
|
35
|
+
.trim()
|
|
36
|
+
.split(/\s+/)
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
|
|
39
|
+
const activeBtnClasses = toClassList(activeBtnClassName);
|
|
40
|
+
const inactiveBtnClasses = toClassList(inactiveBtnClassName);
|
|
41
|
+
const showPanelClasses = toClassList(showPanelClassName);
|
|
42
|
+
const hidePanelClasses = toClassList(hidePanelClassName);
|
|
43
|
+
|
|
44
|
+
const tabBtns = root.querySelectorAll("[data-tabs-btn]");
|
|
45
|
+
const tabPanels = root.querySelectorAll("[data-tabs-panel]");
|
|
46
|
+
|
|
47
|
+
if (tabBtns.length === 0 || tabPanels.length === 0) {
|
|
48
|
+
return () => {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pairCount = Math.min(tabBtns.length, tabPanels.length);
|
|
52
|
+
for (let i = 0; i < pairCount; i++) {
|
|
53
|
+
const btn = tabBtns[i];
|
|
54
|
+
const panel = tabPanels[i];
|
|
55
|
+
const key = `${rootId}-${i}`;
|
|
56
|
+
|
|
57
|
+
const btnKey = btn.getAttribute("data-tabs-btn");
|
|
58
|
+
if (!btnKey) btn.setAttribute("data-tabs-btn", key);
|
|
59
|
+
|
|
60
|
+
const panelKey = panel.getAttribute("data-tabs-panel");
|
|
61
|
+
if (!panelKey) panel.setAttribute("data-tabs-panel", key);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ac = new AbortController();
|
|
65
|
+
const { signal } = ac;
|
|
66
|
+
|
|
67
|
+
const setActiveBtnA11y = (btn, active) => {
|
|
68
|
+
btn.setAttribute("role", "tab");
|
|
69
|
+
btn.setAttribute("aria-selected", active ? "true" : "false");
|
|
70
|
+
btn.tabIndex = active ? 0 : -1;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const setActivePanelA11y = (panel, active) => {
|
|
74
|
+
panel.setAttribute("role", "tabpanel");
|
|
75
|
+
panel.setAttribute("aria-hidden", active ? "false" : "true");
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const activate = (btn) => {
|
|
79
|
+
const targetId = btn.getAttribute("data-tabs-btn");
|
|
80
|
+
if (!targetId) return;
|
|
81
|
+
|
|
82
|
+
tabBtns.forEach((b) => {
|
|
83
|
+
if (inactiveBtnClasses.length) b.classList.add(...inactiveBtnClasses);
|
|
84
|
+
if (activeBtnClasses.length) b.classList.remove(...activeBtnClasses);
|
|
85
|
+
setActiveBtnA11y(b, false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
tabPanels.forEach((p) => {
|
|
89
|
+
if (showPanelClasses.length) p.classList.remove(...showPanelClasses);
|
|
90
|
+
if (hidePanelClasses.length) p.classList.add(...hidePanelClasses);
|
|
91
|
+
setActivePanelA11y(p, false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (inactiveBtnClasses.length)
|
|
95
|
+
btn.classList.remove(...inactiveBtnClasses);
|
|
96
|
+
if (activeBtnClasses.length) btn.classList.add(...activeBtnClasses);
|
|
97
|
+
setActiveBtnA11y(btn, true);
|
|
98
|
+
|
|
99
|
+
let panel = root.querySelector(
|
|
100
|
+
`[data-tabs-panel="${CSS.escape(targetId)}"]`
|
|
101
|
+
);
|
|
102
|
+
if (!panel) {
|
|
103
|
+
const index = Array.from(tabBtns).indexOf(btn);
|
|
104
|
+
panel = tabPanels[index] || null;
|
|
105
|
+
}
|
|
106
|
+
if (panel) {
|
|
107
|
+
if (hidePanelClasses.length)
|
|
108
|
+
panel.classList.remove(...hidePanelClasses);
|
|
109
|
+
if (showPanelClasses.length) panel.classList.add(...showPanelClasses);
|
|
110
|
+
setActivePanelA11y(panel, true);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
document.addEventListener(
|
|
115
|
+
"click",
|
|
116
|
+
(event) => {
|
|
117
|
+
const el = event.target;
|
|
118
|
+
const btn = el?.closest?.("[data-tabs-btn]");
|
|
119
|
+
if (!btn) return;
|
|
120
|
+
if (!root.contains(btn)) return;
|
|
121
|
+
activate(btn);
|
|
122
|
+
},
|
|
123
|
+
{ signal }
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const initBtn = tabBtns[defaultIndex] || tabBtns[0];
|
|
127
|
+
if (initBtn) activate(initBtn);
|
|
128
|
+
|
|
129
|
+
return () => ac.abort();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const boot = () => {
|
|
133
|
+
const state = getState();
|
|
134
|
+
state.cleanup?.();
|
|
135
|
+
state.cleanup = initTabs();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (document.readyState === "loading") {
|
|
139
|
+
document.addEventListener("DOMContentLoaded", boot, { once: true });
|
|
140
|
+
} else {
|
|
141
|
+
boot();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
document.addEventListener("astro:page-load", boot);
|
|
145
|
+
document.addEventListener("astro:after-swap", boot);
|
|
146
|
+
window.addEventListener("astro:before-swap", () => getState().cleanup?.());
|
|
147
|
+
</script>
|
package/index.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
// Do not write code directly here, instead use the `src` folder!
|
|
2
2
|
// Then, use this file to export everything you want your user to access.
|
|
3
3
|
|
|
4
|
-
export { default as Link } from "./components/Link.astro";
|
|
5
|
-
export { default as Meta } from "./components/Meta.astro";
|
|
6
|
-
export { default as Dialog } from "./components/Dialog.astro";
|
|
7
|
-
export { default as DialogTrigger } from "./components/DialogTrigger.astro";
|
|
8
|
-
export { default as ModalTrigger } from "./components/DialogTrigger.astro";
|
|
9
4
|
export { default as Document } from "./components/Document.astro";
|
|
10
|
-
|
|
11
|
-
export { default as RichTextView } from "./components/RichTextView.astro";
|
|
12
|
-
export { default as AnimationNumber } from "./components/AnimationNumber.astro";
|
|
5
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rxdrag/website-lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.124",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.ts",
|
|
@@ -26,19 +26,20 @@
|
|
|
26
26
|
"eslint": "^9.39.2",
|
|
27
27
|
"gsap": "^3.12.7",
|
|
28
28
|
"typescript": "^5",
|
|
29
|
+
"@rxdrag/tiptap-preview": "0.0.3",
|
|
29
30
|
"@rxdrag/rxcms-models": "0.3.97",
|
|
30
31
|
"@rxdrag/entify-hooks": "0.2.78",
|
|
31
32
|
"@rxdrag/eslint-config-custom": "0.2.13",
|
|
32
|
-
"@rxdrag/tsconfig": "0.2.1"
|
|
33
|
-
"@rxdrag/tiptap-preview": "0.0.2"
|
|
33
|
+
"@rxdrag/tsconfig": "0.2.1"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"aos": "3.0.0-beta.6",
|
|
37
37
|
"clsx": "^2.1.0",
|
|
38
38
|
"react": "^19.1.0",
|
|
39
39
|
"react-dom": "^19.1.0",
|
|
40
|
-
"
|
|
41
|
-
"@rxdrag/rxcms-models": "0.3.97"
|
|
40
|
+
"vanilla-cookieconsent": "3.1.0",
|
|
41
|
+
"@rxdrag/rxcms-models": "0.3.97",
|
|
42
|
+
"@rxdrag/website-lib-core": "0.0.119"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"astro": "^4.0.0 || ^5.0.0"
|
package/components/Icon.astro
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import {
|
|
3
|
-
Icon as CoreIcon,
|
|
4
|
-
Entify,
|
|
5
|
-
type Locals,
|
|
6
|
-
} from "@rxdrag/website-lib-core";
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
className?: string;
|
|
10
|
-
icon?: string;
|
|
11
|
-
svg?: string;
|
|
12
|
-
}
|
|
13
|
-
const { env, imageSizes } = Astro.locals as Locals;
|
|
14
|
-
const { className, icon, svg, ...rest } = Astro.props;
|
|
15
|
-
|
|
16
|
-
const localIconName = icon?.startsWith("local:") ? icon?.split(":")[1] : null;
|
|
17
|
-
|
|
18
|
-
const rx = Entify.getInstance(env, imageSizes);
|
|
19
|
-
|
|
20
|
-
const svgIcon =
|
|
21
|
-
!!rx && localIconName ? await rx.getIcon(localIconName) : undefined;
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
<CoreIcon
|
|
25
|
-
className={className}
|
|
26
|
-
icon={localIconName ? undefined : icon}
|
|
27
|
-
svg={svgIcon?.code || svg}
|
|
28
|
-
{...rest}
|
|
29
|
-
client:only="react"
|
|
30
|
-
/>
|