@coffic/cosy-ui 0.1.28 → 0.2.0
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/README.md +67 -23
- package/dist/app.css +1 -0
- package/dist/assets/logo-rounded.png +0 -0
- package/dist/assets/logo.png +0 -0
- package/dist/components/base/Alert.astro +186 -0
- package/dist/components/base/Button.astro +103 -0
- package/dist/components/base/Image.astro +291 -0
- package/dist/components/base/Link.astro +131 -0
- package/dist/components/base/README.md +53 -0
- package/dist/components/base/index.ts +6 -0
- package/dist/components/containers/Container.astro +103 -0
- package/dist/components/containers/Main.astro +167 -0
- package/dist/components/containers/Section.astro +145 -0
- package/dist/components/containers/index.ts +3 -0
- package/dist/components/data-display/Blog.astro +195 -0
- package/dist/components/data-display/README.md +37 -0
- package/dist/components/data-display/TeamMember.astro +135 -0
- package/dist/components/data-display/TeamMembers.astro +101 -0
- package/dist/components/data-display/index.ts +3 -0
- package/dist/components/display/Banner.astro +57 -0
- package/dist/components/display/Card.astro +135 -0
- package/dist/components/display/CodeBlock.astro +147 -0
- package/dist/components/display/CodeExample.astro +330 -0
- package/dist/components/display/Hero.astro +119 -0
- package/dist/components/display/Modal.astro +115 -0
- package/dist/components/display/README.md +32 -0
- package/dist/components/display/index.ts +6 -0
- package/dist/components/icons/AlertTriangle.astro +35 -0
- package/dist/components/icons/CalendarIcon.astro +38 -0
- package/dist/components/icons/CheckCircle.astro +36 -0
- package/dist/components/icons/CheckIcon.astro +38 -0
- package/dist/components/icons/ClipboardIcon.astro +39 -0
- package/dist/components/icons/CloseIcon.astro +38 -0
- package/dist/components/icons/ErrorIcon.astro +35 -0
- package/dist/components/icons/GithubIcon.astro +31 -0
- package/dist/components/icons/InfoCircle.astro +37 -0
- package/dist/components/icons/InfoIcon.astro +38 -0
- package/dist/components/icons/LinkIcon.astro +39 -0
- package/dist/components/icons/LinkedinIcon.astro +31 -0
- package/dist/components/icons/MenuIcon.astro +41 -0
- package/dist/components/icons/SearchIcon.astro +40 -0
- package/dist/components/icons/SocialIcon.astro +100 -0
- package/dist/components/icons/SuccessIcon.astro +35 -0
- package/dist/components/icons/SunCloudyIcon.astro +45 -0
- package/dist/components/icons/TwitterIcon.astro +31 -0
- package/dist/components/icons/UserIcon.astro +35 -0
- package/dist/components/icons/WarningIcon.astro +38 -0
- package/dist/components/icons/XCircle.astro +37 -0
- package/dist/components/icons/index.ts +23 -0
- package/dist/components/layouts/BaseLayout.astro +144 -0
- package/dist/components/layouts/DashboardLayout.astro +660 -0
- package/dist/components/layouts/DefaultLayout.astro +170 -0
- package/dist/components/layouts/DocumentationLayout.astro +469 -0
- package/dist/components/layouts/Flex.astro +138 -0
- package/dist/components/layouts/Footer.astro +284 -0
- package/dist/components/layouts/Grid.astro +182 -0
- package/dist/components/layouts/Header.astro +114 -0
- package/dist/components/layouts/LandingLayout.astro +388 -0
- package/dist/components/layouts/README.md +37 -0
- package/dist/components/layouts/Stack.astro +149 -0
- package/dist/components/layouts/index.ts +6 -0
- package/dist/components/navigation/LanguageSwitcher.astro +81 -0
- package/dist/components/navigation/README.md +24 -0
- package/dist/components/navigation/TableOfContents.astro +352 -0
- package/dist/components/navigation/ThemeSwitcher.astro +89 -0
- package/dist/components/navigation/index.ts +3 -0
- package/dist/components/typography/Article.astro +144 -0
- package/dist/components/typography/Heading.astro +205 -0
- package/dist/components/typography/README.md +29 -0
- package/dist/components/typography/Text.astro +187 -0
- package/dist/components/typography/index.ts +3 -0
- package/dist/index.ts +9 -0
- package/dist/integration.ts +14 -0
- package/dist/style.ts +1 -0
- package/{src → dist}/types/footer.ts +11 -7
- package/{src → dist}/utils/social.ts +5 -12
- package/dist/utils/theme.ts +55 -0
- package/package.json +65 -59
- package/index.ts +0 -18
- package/src/components/Alert.astro +0 -78
- package/src/components/Article.astro +0 -11
- package/src/components/Banner.astro +0 -49
- package/src/components/Blog.astro +0 -115
- package/src/components/Button.astro +0 -49
- package/src/components/Card.astro +0 -113
- package/src/components/CodeBlock.astro +0 -186
- package/src/components/Footer.astro +0 -130
- package/src/components/Header.astro +0 -305
- package/src/components/Hero.astro +0 -69
- package/src/components/Image.astro +0 -251
- package/src/components/Link.astro +0 -82
- package/src/components/Modal.astro +0 -67
- package/src/components/SocialIcon.astro +0 -36
- package/src/components/TeamMember.astro +0 -68
- package/src/components/TeamMembers.astro +0 -43
- package/src/env.d.ts +0 -0
- /package/{src/components → dist/components/base}/ThemeItem.astro +0 -0
@@ -1,186 +0,0 @@
|
|
1
|
-
---
|
2
|
-
interface Props {
|
3
|
-
code: string;
|
4
|
-
lang?: string;
|
5
|
-
title?: string;
|
6
|
-
showLineNumbers?: boolean;
|
7
|
-
}
|
8
|
-
|
9
|
-
const {
|
10
|
-
code,
|
11
|
-
lang = 'plaintext',
|
12
|
-
title,
|
13
|
-
showLineNumbers = true,
|
14
|
-
} = Astro.props;
|
15
|
-
|
16
|
-
// 移除代码字符串开头和结尾的空行
|
17
|
-
const trimmedCode = code.trim();
|
18
|
-
|
19
|
-
// 生成行号
|
20
|
-
const lines = trimmedCode.split('\n');
|
21
|
-
const lineNumbers = Array.from({ length: lines.length }, (_, i) => i + 1);
|
22
|
-
---
|
23
|
-
|
24
|
-
<div class="code-block not-prose rounded-lg border border-base-300 bg-base-200 my-4">
|
25
|
-
{/* 标题栏 */}
|
26
|
-
{title && (
|
27
|
-
<div class="flex items-center justify-between px-4 py-2 border-b border-base-300">
|
28
|
-
<span class="text-sm font-medium">{title}</span>
|
29
|
-
<button
|
30
|
-
class="copy-button btn btn-ghost btn-xs gap-1"
|
31
|
-
data-code={trimmedCode}
|
32
|
-
title="复制代码"
|
33
|
-
>
|
34
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
35
|
-
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
36
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
37
|
-
</svg>
|
38
|
-
<span class="copy-text">复制</span>
|
39
|
-
</button>
|
40
|
-
</div>
|
41
|
-
)}
|
42
|
-
|
43
|
-
{/* 代码区域 */}
|
44
|
-
<div class="relative">
|
45
|
-
{/* 行号 */}
|
46
|
-
{showLineNumbers && (
|
47
|
-
<div class="hidden md:block absolute left-0 top-0 bottom-0 px-4 py-4 select-none text-base-content/50 bg-base-300 border-r border-base-300">
|
48
|
-
{lineNumbers.map(num => (
|
49
|
-
<div class="text-right leading-6 text-sm">{num}</div>
|
50
|
-
))}
|
51
|
-
</div>
|
52
|
-
)}
|
53
|
-
|
54
|
-
{/* 代码内容 */}
|
55
|
-
<pre class={`language-${lang} ${showLineNumbers ? 'pl-[3.5rem]' : 'px-4'} py-4 m-0 overflow-x-auto`}><code class={`language-${lang}`} set:html={trimmedCode} /></pre>
|
56
|
-
</div>
|
57
|
-
</div>
|
58
|
-
|
59
|
-
<script>
|
60
|
-
function initializeCopyButtons() {
|
61
|
-
const copyButtons = document.querySelectorAll('.copy-button');
|
62
|
-
|
63
|
-
copyButtons.forEach(button => {
|
64
|
-
button.addEventListener('click', async () => {
|
65
|
-
const code = button.getAttribute('data-code') || '';
|
66
|
-
const copyText = button.querySelector('.copy-text');
|
67
|
-
|
68
|
-
try {
|
69
|
-
await navigator.clipboard.writeText(code);
|
70
|
-
if (copyText) {
|
71
|
-
copyText.textContent = '已复制!';
|
72
|
-
setTimeout(() => {
|
73
|
-
copyText.textContent = '复制';
|
74
|
-
}, 2000);
|
75
|
-
}
|
76
|
-
} catch (err) {
|
77
|
-
console.error('复制失败:', err);
|
78
|
-
if (copyText) {
|
79
|
-
copyText.textContent = '复制失败';
|
80
|
-
setTimeout(() => {
|
81
|
-
copyText.textContent = '复制';
|
82
|
-
}, 2000);
|
83
|
-
}
|
84
|
-
}
|
85
|
-
});
|
86
|
-
});
|
87
|
-
}
|
88
|
-
|
89
|
-
// 在页面加载时初始化
|
90
|
-
initializeCopyButtons();
|
91
|
-
|
92
|
-
// 在 Astro 页面切换时重新初始化
|
93
|
-
document.addEventListener('astro:page-load', initializeCopyButtons);
|
94
|
-
</script>
|
95
|
-
|
96
|
-
<style is:global>
|
97
|
-
/* Prism.js 暗色主题自定义 */
|
98
|
-
.code-block pre {
|
99
|
-
background: transparent !important;
|
100
|
-
}
|
101
|
-
|
102
|
-
.code-block code {
|
103
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
104
|
-
font-size: 0.875rem;
|
105
|
-
line-height: 1.5rem;
|
106
|
-
}
|
107
|
-
|
108
|
-
/* 代码高亮颜色 */
|
109
|
-
.token.comment,
|
110
|
-
.token.prolog,
|
111
|
-
.token.doctype,
|
112
|
-
.token.cdata {
|
113
|
-
color: #636f88;
|
114
|
-
}
|
115
|
-
|
116
|
-
.token.punctuation {
|
117
|
-
color: #81a1c1;
|
118
|
-
}
|
119
|
-
|
120
|
-
.namespace {
|
121
|
-
opacity: 0.7;
|
122
|
-
}
|
123
|
-
|
124
|
-
.token.property,
|
125
|
-
.token.tag,
|
126
|
-
.token.constant,
|
127
|
-
.token.symbol,
|
128
|
-
.token.deleted {
|
129
|
-
color: #81a1c1;
|
130
|
-
}
|
131
|
-
|
132
|
-
.token.number {
|
133
|
-
color: #b48ead;
|
134
|
-
}
|
135
|
-
|
136
|
-
.token.boolean {
|
137
|
-
color: #81a1c1;
|
138
|
-
}
|
139
|
-
|
140
|
-
.token.selector,
|
141
|
-
.token.attr-name,
|
142
|
-
.token.string,
|
143
|
-
.token.char,
|
144
|
-
.token.builtin,
|
145
|
-
.token.inserted {
|
146
|
-
color: #a3be8c;
|
147
|
-
}
|
148
|
-
|
149
|
-
.token.operator,
|
150
|
-
.token.entity,
|
151
|
-
.token.url,
|
152
|
-
.language-css .token.string,
|
153
|
-
.style .token.string,
|
154
|
-
.token.variable {
|
155
|
-
color: #81a1c1;
|
156
|
-
}
|
157
|
-
|
158
|
-
.token.atrule,
|
159
|
-
.token.attr-value,
|
160
|
-
.token.function,
|
161
|
-
.token.class-name {
|
162
|
-
color: #88c0d0;
|
163
|
-
}
|
164
|
-
|
165
|
-
.token.keyword {
|
166
|
-
color: #81a1c1;
|
167
|
-
}
|
168
|
-
|
169
|
-
.token.regex,
|
170
|
-
.token.important {
|
171
|
-
color: #ebcb8b;
|
172
|
-
}
|
173
|
-
|
174
|
-
.token.important,
|
175
|
-
.token.bold {
|
176
|
-
font-weight: bold;
|
177
|
-
}
|
178
|
-
|
179
|
-
.token.italic {
|
180
|
-
font-style: italic;
|
181
|
-
}
|
182
|
-
|
183
|
-
.token.entity {
|
184
|
-
cursor: help;
|
185
|
-
}
|
186
|
-
</style>
|
@@ -1,130 +0,0 @@
|
|
1
|
-
---
|
2
|
-
import { processSocialLink } from '../utils/social';
|
3
|
-
import Link from './Link.astro';
|
4
|
-
import Banner from './Banner.astro';
|
5
|
-
import SocialIcon from './SocialIcon.astro';
|
6
|
-
import type { FooterProps } from '../types/footer';
|
7
|
-
|
8
|
-
interface Props extends FooterProps {}
|
9
|
-
|
10
|
-
const {
|
11
|
-
siteName,
|
12
|
-
homeLink,
|
13
|
-
slogan,
|
14
|
-
company,
|
15
|
-
copyright,
|
16
|
-
inspirationalSlogan,
|
17
|
-
icp,
|
18
|
-
logo,
|
19
|
-
products = [],
|
20
|
-
aboutLink,
|
21
|
-
contactLink,
|
22
|
-
termsLink,
|
23
|
-
privacyLink,
|
24
|
-
socialLinks = [],
|
25
|
-
}: Props = Astro.props;
|
26
|
-
|
27
|
-
const currentYear = new Date().getFullYear();
|
28
|
-
|
29
|
-
// 处理社交链接
|
30
|
-
const processedSocialLinks = socialLinks.map((link) => processSocialLink(link));
|
31
|
-
---
|
32
|
-
|
33
|
-
<footer class="bg-base-200/50 dark:bg-base-300/50 z-50 backdrop-blur-md">
|
34
|
-
<div class="footer sm:footer-horizontal p-10 text-base-content">
|
35
|
-
<aside class="flex flex-col items-center sm:items-start gap-2 place-self-center">
|
36
|
-
<Link href={homeLink}>
|
37
|
-
<div class="flex items-center gap-2 rounded-2xl">
|
38
|
-
{
|
39
|
-
logo && (
|
40
|
-
<img
|
41
|
-
src={logo.src}
|
42
|
-
alt={logo.alt}
|
43
|
-
class="h-6 w-6 fill-current rounded-2xl"
|
44
|
-
width="32"
|
45
|
-
height="32"
|
46
|
-
/>
|
47
|
-
)
|
48
|
-
}
|
49
|
-
<span class="text-xl font-bold">{siteName}</span>
|
50
|
-
</div>
|
51
|
-
</Link>
|
52
|
-
<p class="text-sm opacity-70 self-center">{slogan}</p>
|
53
|
-
|
54
|
-
<div class="flex gap-4 mt-4 sm:mt-0">
|
55
|
-
{
|
56
|
-
processedSocialLinks.map((link) => (
|
57
|
-
<Link
|
58
|
-
href={link.url}
|
59
|
-
external={true}
|
60
|
-
class="btn btn-ghost btn-sm p-1 hover:text-primary"
|
61
|
-
>
|
62
|
-
<SocialIcon platform={link.platform} />
|
63
|
-
<span class="sr-only">{link.name}</span>
|
64
|
-
</Link>
|
65
|
-
))
|
66
|
-
}
|
67
|
-
</div>
|
68
|
-
</aside>
|
69
|
-
|
70
|
-
{
|
71
|
-
products.length > 0 && (
|
72
|
-
<nav class="place-self-center text-center flex flex-col items-center min-h-[200px]">
|
73
|
-
<h6 class="footer-title">产品</h6>
|
74
|
-
<div class="flex flex-col gap-2">
|
75
|
-
{products.map((product) => (
|
76
|
-
<Link href={product.href} external={product.external}>
|
77
|
-
{product.name}
|
78
|
-
</Link>
|
79
|
-
))}
|
80
|
-
</div>
|
81
|
-
</nav>
|
82
|
-
)
|
83
|
-
}
|
84
|
-
|
85
|
-
{
|
86
|
-
(aboutLink || contactLink) && (
|
87
|
-
<nav class="place-self-center text-center flex flex-col items-center min-h-[200px]">
|
88
|
-
<h6 class="footer-title">关于</h6>
|
89
|
-
<div class="flex flex-col gap-2">
|
90
|
-
{aboutLink && <Link href={aboutLink}>关于我们</Link>}
|
91
|
-
{contactLink && <Link href={contactLink}>联系我们</Link>}
|
92
|
-
</div>
|
93
|
-
</nav>
|
94
|
-
)
|
95
|
-
}
|
96
|
-
|
97
|
-
{
|
98
|
-
(termsLink || privacyLink) && (
|
99
|
-
<nav class="place-self-center text-center flex flex-col items-center min-h-[200px]">
|
100
|
-
<h6 class="footer-title">法律</h6>
|
101
|
-
<div class="flex flex-col gap-2">
|
102
|
-
{termsLink && <Link href={termsLink}>服务条款</Link>}
|
103
|
-
{privacyLink && <Link href={privacyLink}>隐私政策</Link>}
|
104
|
-
</div>
|
105
|
-
</nav>
|
106
|
-
)
|
107
|
-
}
|
108
|
-
</div>
|
109
|
-
|
110
|
-
<Banner>
|
111
|
-
{inspirationalSlogan}
|
112
|
-
</Banner>
|
113
|
-
|
114
|
-
<div class="flex flex-col sm:flex-col justify-center items-center p-4 text-sm opacity-70">
|
115
|
-
<div class="flex items-center gap-2">
|
116
|
-
<p>
|
117
|
-
© {currentYear}
|
118
|
-
{company} - {copyright}
|
119
|
-
</p>
|
120
|
-
</div>
|
121
|
-
|
122
|
-
{
|
123
|
-
icp && (
|
124
|
-
<div class="text-center py-4">
|
125
|
-
<p>{icp}</p>
|
126
|
-
</div>
|
127
|
-
)
|
128
|
-
}
|
129
|
-
</div>
|
130
|
-
</footer>
|
@@ -1,305 +0,0 @@
|
|
1
|
-
---
|
2
|
-
import { Image } from 'astro:assets';
|
3
|
-
import { RiSearch2Line, RiMenuLine, RiSunCloudyLine } from '@remixicon/vue';
|
4
|
-
import ThemeItem from './ThemeItem.astro';
|
5
|
-
import Link from './Link.astro';
|
6
|
-
import Button from './Button.astro';
|
7
|
-
|
8
|
-
interface Props {
|
9
|
-
logo: ImageMetadata;
|
10
|
-
languages?: Array<{ code: string; name: string }>;
|
11
|
-
currentLocale?: string;
|
12
|
-
navItems?: Array<{
|
13
|
-
href: string;
|
14
|
-
label: string;
|
15
|
-
match: (path: string) => boolean;
|
16
|
-
}>;
|
17
|
-
socialLinks?: Array<{
|
18
|
-
name: string;
|
19
|
-
url: string;
|
20
|
-
icon: any;
|
21
|
-
}>;
|
22
|
-
}
|
23
|
-
|
24
|
-
const {
|
25
|
-
logo,
|
26
|
-
languages = [
|
27
|
-
{ code: 'zh-cn', name: '简体中文' },
|
28
|
-
{ code: 'en', name: 'English' },
|
29
|
-
],
|
30
|
-
currentLocale = 'zh-cn',
|
31
|
-
navItems = [],
|
32
|
-
} = Astro.props;
|
33
|
-
|
34
|
-
const currentLanguageName =
|
35
|
-
languages.find((lang) => lang.code === currentLocale)?.name || '简体中文';
|
36
|
-
|
37
|
-
const themes = [
|
38
|
-
{ id: 'default', name: 'Default' },
|
39
|
-
{ id: 'light', name: 'Light' },
|
40
|
-
{ id: 'dark', name: 'Dark' },
|
41
|
-
{ id: 'pastel', name: 'Pastel' },
|
42
|
-
{ id: 'lemonade', name: 'Lemonade' },
|
43
|
-
{ id: 'cupcake', name: 'Cupcake' },
|
44
|
-
{ id: 'nord', name: 'Nord' },
|
45
|
-
{ id: 'business', name: 'Business' },
|
46
|
-
{ id: 'luxury', name: 'Luxury' },
|
47
|
-
];
|
48
|
-
|
49
|
-
const currentPath = Astro.url.pathname;
|
50
|
-
|
51
|
-
// 为每个语言生成对应的URL
|
52
|
-
function generateLanguageUrl(langCode: string): string {
|
53
|
-
const pathParts = currentPath.split('/').filter(Boolean);
|
54
|
-
const firstPathPart = pathParts[0];
|
55
|
-
const supportedLanguages = languages.map((lang) => lang.code);
|
56
|
-
const isFirstPartLang = supportedLanguages.includes(firstPathPart);
|
57
|
-
|
58
|
-
if (isFirstPartLang) {
|
59
|
-
pathParts[0] = langCode;
|
60
|
-
return '/' + pathParts.join('/');
|
61
|
-
} else {
|
62
|
-
return '/' + langCode + (currentPath === '/' ? '' : currentPath);
|
63
|
-
}
|
64
|
-
}
|
65
|
-
---
|
66
|
-
|
67
|
-
<header
|
68
|
-
class="bg-gradient-to-r from-neutral-900/80 via-neutral-800/80 to-neutral-900/80
|
69
|
-
dark:from-neutral-800/80 dark:via-neutral-700/80 dark:to-neutral-800/80 backdrop-blur
|
70
|
-
flex flex-row justify-between
|
71
|
-
text-neutral-200 dark:text-neutral-300
|
72
|
-
px-4
|
73
|
-
rounded-lg h-12 mt-1 mx-4 fixed top-0 left-0 right-0 z-50
|
74
|
-
border border-white/5 dark:border-black/5"
|
75
|
-
>
|
76
|
-
<div class="flex flex-row justify-start items-center">
|
77
|
-
<Link href="/" transition:persist>
|
78
|
-
<div class="h-8 w-8 flex flex-col items-center justify-center">
|
79
|
-
<Image src={logo} alt="logo" class="fill-current" />
|
80
|
-
</div>
|
81
|
-
</Link>
|
82
|
-
|
83
|
-
<!-- 移动端菜单按钮 -->
|
84
|
-
<Button variant="ghost" size="sm" class="lg:hidden ml-2" onClick="mobile_menu.showModal()">
|
85
|
-
<RiMenuLine class="w-5 h-5" slot="icon-left" />
|
86
|
-
</Button>
|
87
|
-
|
88
|
-
<!-- 桌面端导航 -->
|
89
|
-
<div class="hidden lg:flex flex-row gap-4 items-center ml-4" transition:animate="fade">
|
90
|
-
{navItems.map((item) => <Link href={item.href}>{item.label}</Link>)}
|
91
|
-
</div>
|
92
|
-
</div>
|
93
|
-
|
94
|
-
<div class="h-12 flex flex-row justify-end gap-2 items-center">
|
95
|
-
<div class="dropdown dropdown-end">
|
96
|
-
<Button variant="ghost" size="sm" class="p-1">
|
97
|
-
{currentLanguageName}
|
98
|
-
</Button>
|
99
|
-
<ul
|
100
|
-
tabindex={0}
|
101
|
-
class="dropdown-content menu bg-slate-900 dark:bg-slate-800 rounded-box z-[1] w-40 p-2 shadow-lg"
|
102
|
-
>
|
103
|
-
{
|
104
|
-
languages.map((lang) => (
|
105
|
-
<li>
|
106
|
-
<Link href={generateLanguageUrl(lang.code)}>{lang.name}</Link>
|
107
|
-
</li>
|
108
|
-
))
|
109
|
-
}
|
110
|
-
</ul>
|
111
|
-
</div>
|
112
|
-
<div class="dropdown dropdown-end">
|
113
|
-
<Button variant="ghost" size="sm" class="p-1">
|
114
|
-
<RiSunCloudyLine class="w-5 h-5" slot="icon-left" />
|
115
|
-
</Button>
|
116
|
-
<ul
|
117
|
-
tabindex={0}
|
118
|
-
class="dropdown-content menu bg-neutral-900 dark:bg-neutral-800 rounded-box z-[1] w-56 p-2 shadow-lg"
|
119
|
-
>
|
120
|
-
{themes.map((theme) => <ThemeItem theme={theme.id} label={theme.name} />)}
|
121
|
-
</ul>
|
122
|
-
</div>
|
123
|
-
<Button variant="ghost" size="sm" class="p-1" onClick="my_modal_1.showModal()">
|
124
|
-
<RiSearch2Line class="w-5 h-5" slot="icon-left" />
|
125
|
-
</Button>
|
126
|
-
</div>
|
127
|
-
</header>
|
128
|
-
|
129
|
-
<!-- 移动端菜单模态框 -->
|
130
|
-
<dialog id="mobile_menu" class="modal">
|
131
|
-
<div class="modal-box bg-neutral-900 dark:bg-neutral-800 text-neutral-200 dark:text-neutral-300">
|
132
|
-
<h3 class="font-bold text-lg mb-4">
|
133
|
-
{currentLocale === 'zh-cn' ? '菜单' : 'Menu'}
|
134
|
-
</h3>
|
135
|
-
<div class="flex flex-col gap-2">
|
136
|
-
{
|
137
|
-
navItems.map((item) => (
|
138
|
-
<Link
|
139
|
-
href={item.href}
|
140
|
-
class:list={[
|
141
|
-
'btn btn-sm w-full text-left justify-start',
|
142
|
-
{
|
143
|
-
'btn-ghost': !item.match(currentPath),
|
144
|
-
'btn-primary': item.match(currentPath),
|
145
|
-
},
|
146
|
-
]}
|
147
|
-
>
|
148
|
-
{item.label}
|
149
|
-
</Link>
|
150
|
-
))
|
151
|
-
}
|
152
|
-
</div>
|
153
|
-
<div class="modal-action">
|
154
|
-
<form method="dialog">
|
155
|
-
<Button>
|
156
|
-
{currentLocale === 'zh-cn' ? '关闭' : 'Close'}
|
157
|
-
</Button>
|
158
|
-
</form>
|
159
|
-
</div>
|
160
|
-
</div>
|
161
|
-
<form method="dialog" class="modal-backdrop bg-black/20 backdrop-blur-sm">
|
162
|
-
<Button variant="ghost">关闭</Button>
|
163
|
-
</form>
|
164
|
-
</dialog>
|
165
|
-
|
166
|
-
<!-- 搜索模态框 -->
|
167
|
-
<!-- <dialog id="my_modal_1" class="modal">
|
168
|
-
<div
|
169
|
-
class="modal-box bg-neutral-900 dark:bg-neutral-800 text-neutral-200 dark:text-neutral-300">
|
170
|
-
<Search />
|
171
|
-
<div class="modal-action">
|
172
|
-
<form method="dialog">
|
173
|
-
<button class="btn">
|
174
|
-
{currentLocale === 'zh-cn' ? '关闭' : 'Close'}
|
175
|
-
</button>
|
176
|
-
</form>
|
177
|
-
</div>
|
178
|
-
</div>
|
179
|
-
<form method="dialog" class="modal-backdrop bg-black/20 backdrop-blur-sm">
|
180
|
-
<button>关闭</button>
|
181
|
-
</form>
|
182
|
-
</dialog> -->
|
183
|
-
|
184
|
-
<style is:global>
|
185
|
-
.pagefind-ui {
|
186
|
-
--pagefind-ui-scale: 0.75;
|
187
|
-
--pagefind-ui-primary: navy;
|
188
|
-
--pagefind-ui-text: black;
|
189
|
-
--pagefind-ui-border: slategrey;
|
190
|
-
--pagefind-ui-border-width: 1px;
|
191
|
-
--pagefind-ui-border-radius: 0.25rem;
|
192
|
-
--pagefind-ui-font: sans-serif;
|
193
|
-
width: 100%;
|
194
|
-
}
|
195
|
-
|
196
|
-
.pagefind-ui.yellow {
|
197
|
-
--pagefind-ui-background: lightyellow;
|
198
|
-
}
|
199
|
-
|
200
|
-
.pagefind-ui.red {
|
201
|
-
--pagefind-ui-background: peachpuff;
|
202
|
-
width: 100%;
|
203
|
-
}
|
204
|
-
|
205
|
-
.pagefind-ui .pagefind-ui__drawer:not(.pagefind-ui__hidden) {
|
206
|
-
position: relative;
|
207
|
-
padding: 0;
|
208
|
-
box-shadow: none;
|
209
|
-
background-color: transparent;
|
210
|
-
}
|
211
|
-
|
212
|
-
.pagefind-ui .pagefind-ui__result-link {
|
213
|
-
color: var(--pagefind-ui-primary);
|
214
|
-
}
|
215
|
-
|
216
|
-
.pagefind-ui .pagefind-ui__result-excerpt {
|
217
|
-
color: var(--pagefind-ui-text);
|
218
|
-
}
|
219
|
-
|
220
|
-
/* 响应式调整 */
|
221
|
-
@media (max-width: 1023px) {
|
222
|
-
.modal-box {
|
223
|
-
max-height: 80vh;
|
224
|
-
overflow-y: auto;
|
225
|
-
}
|
226
|
-
}
|
227
|
-
|
228
|
-
/* 模态框动画 */
|
229
|
-
.modal {
|
230
|
-
transition-duration: 200ms;
|
231
|
-
}
|
232
|
-
|
233
|
-
.modal-box {
|
234
|
-
transition-duration: 300ms;
|
235
|
-
transform-origin: center;
|
236
|
-
}
|
237
|
-
|
238
|
-
@keyframes slideUp {
|
239
|
-
from {
|
240
|
-
transform: translateY(1rem);
|
241
|
-
opacity: 0;
|
242
|
-
}
|
243
|
-
to {
|
244
|
-
transform: translateY(0);
|
245
|
-
opacity: 1;
|
246
|
-
}
|
247
|
-
}
|
248
|
-
|
249
|
-
@keyframes fadeIn {
|
250
|
-
from {
|
251
|
-
opacity: 0;
|
252
|
-
}
|
253
|
-
to {
|
254
|
-
opacity: 1;
|
255
|
-
}
|
256
|
-
}
|
257
|
-
|
258
|
-
.modal[open] .modal-box {
|
259
|
-
animation: slideUp 0.3s ease-out forwards;
|
260
|
-
}
|
261
|
-
|
262
|
-
.modal[open] .modal-backdrop {
|
263
|
-
animation: fadeIn 0.2s ease-out forwards;
|
264
|
-
}
|
265
|
-
</style>
|
266
|
-
|
267
|
-
<script>
|
268
|
-
function initializeThemeSwitch() {
|
269
|
-
const themeItems = document.querySelectorAll('[data-set-theme]');
|
270
|
-
const updateActiveTheme = (currentTheme) => {
|
271
|
-
themeItems.forEach((item) => {
|
272
|
-
const themeId = item.getAttribute('data-set-theme');
|
273
|
-
const isActive = themeId === currentTheme;
|
274
|
-
item.classList.toggle('bg-primary', isActive);
|
275
|
-
item.classList.toggle('text-primary-content', isActive);
|
276
|
-
});
|
277
|
-
};
|
278
|
-
|
279
|
-
themeItems.forEach((item) => {
|
280
|
-
// 移除可能存在的旧事件监听器
|
281
|
-
item.removeEventListener('click', handleThemeClick);
|
282
|
-
// 添加新的事件监听器
|
283
|
-
item.addEventListener('click', handleThemeClick);
|
284
|
-
});
|
285
|
-
|
286
|
-
function handleThemeClick(event) {
|
287
|
-
const item = event.currentTarget;
|
288
|
-
const theme = item.getAttribute('data-set-theme');
|
289
|
-
document.documentElement.setAttribute('data-theme', theme ?? 'default');
|
290
|
-
localStorage.setItem('theme', theme ?? 'default');
|
291
|
-
updateActiveTheme(theme ?? 'default');
|
292
|
-
}
|
293
|
-
|
294
|
-
// 从本地存储中获取主题并更新激活状态
|
295
|
-
const savedTheme = localStorage.getItem('theme') || 'default';
|
296
|
-
document.documentElement.setAttribute('data-theme', savedTheme);
|
297
|
-
updateActiveTheme(savedTheme);
|
298
|
-
}
|
299
|
-
|
300
|
-
// 初始加载时初始化
|
301
|
-
document.addEventListener('DOMContentLoaded', initializeThemeSwitch);
|
302
|
-
|
303
|
-
// Astro view transitions 后重新初始化
|
304
|
-
document.addEventListener('astro:after-swap', initializeThemeSwitch);
|
305
|
-
</script>
|
@@ -1,69 +0,0 @@
|
|
1
|
-
---
|
2
|
-
import Link from './Link.astro';
|
3
|
-
|
4
|
-
interface Link {
|
5
|
-
text: string;
|
6
|
-
href: string;
|
7
|
-
}
|
8
|
-
|
9
|
-
interface Props {
|
10
|
-
title: string;
|
11
|
-
description: string;
|
12
|
-
image?: {
|
13
|
-
src: string;
|
14
|
-
alt: string;
|
15
|
-
};
|
16
|
-
links: Link[];
|
17
|
-
}
|
18
|
-
|
19
|
-
const { title, description, image, links = [] } = Astro.props;
|
20
|
-
---
|
21
|
-
|
22
|
-
<div class="py-16 px-8 text-center w-full min-h-screen relative overflow-hidden">
|
23
|
-
<div class="relative z-10 rounded-lg w-full h-full">
|
24
|
-
{image && <img src={image.src} alt={image.alt} class="h-1/2 mx-auto mb-8 drop-shadow-xl" />}
|
25
|
-
|
26
|
-
<h2 class="text-4xl mb-4 animate-fade-up">{title}</h2>
|
27
|
-
<p class="text-lg mb-12 text-center max-w-2xl mx-auto">
|
28
|
-
{description}
|
29
|
-
</p>
|
30
|
-
|
31
|
-
<div class="my-12 w-full">
|
32
|
-
<slot name="app" />
|
33
|
-
</div>
|
34
|
-
|
35
|
-
<div class="flex flex-row justify-center gap-8 mx-auto w-full">
|
36
|
-
{
|
37
|
-
links.map((link) => (
|
38
|
-
<Link
|
39
|
-
href={link.href}
|
40
|
-
external
|
41
|
-
variant="cta"
|
42
|
-
animation="hover-lift"
|
43
|
-
size="lg"
|
44
|
-
>
|
45
|
-
{link.text}
|
46
|
-
</Link>
|
47
|
-
))
|
48
|
-
}
|
49
|
-
</div>
|
50
|
-
</div>
|
51
|
-
</div>
|
52
|
-
|
53
|
-
<style>
|
54
|
-
@keyframes fade-up {
|
55
|
-
from {
|
56
|
-
opacity: 0;
|
57
|
-
transform: translateY(-20px);
|
58
|
-
}
|
59
|
-
|
60
|
-
to {
|
61
|
-
opacity: 1;
|
62
|
-
transform: translateY(0);
|
63
|
-
}
|
64
|
-
}
|
65
|
-
|
66
|
-
.animate-fade-up {
|
67
|
-
animation: fade-up 0.8s ease-out forwards;
|
68
|
-
}
|
69
|
-
</style>
|