@coffic/cosy-ui 0.8.21 → 0.8.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/app.css +1 -1
  2. package/dist/index-astro.ts +2 -0
  3. package/dist/src/assets/iconData.ts +5 -1
  4. package/dist/src-astro/banner/Banner.astro +55 -3
  5. package/dist/src-astro/banner/index.ts +1 -52
  6. package/dist/src-astro/button/Button.astro +40 -1
  7. package/dist/src-astro/code-container/ButtonCodeToggle.astro +55 -0
  8. package/dist/src-astro/code-container/ButtonCopyCode.astro +74 -0
  9. package/dist/src-astro/code-container/CodeContainer.astro +14 -289
  10. package/dist/src-astro/code-container/CodePanel.astro +14 -0
  11. package/dist/src-astro/code-container/CodeToolbar.astro +100 -0
  12. package/dist/src-astro/code-container/Description.astro +17 -0
  13. package/dist/src-astro/code-container/Preview.astro +93 -0
  14. package/dist/src-astro/code-panel/CodePanel.astro +109 -0
  15. package/dist/src-astro/code-panel/index.ts +2 -0
  16. package/dist/src-astro/code-panel/types.ts +17 -0
  17. package/dist/src-astro/footer/Footer.astro +23 -20
  18. package/dist/src-astro/footer/FooterSection.astro +7 -9
  19. package/dist/src-astro/grid/index.ts +1 -9
  20. package/dist/src-astro/heading/index.ts +1 -9
  21. package/dist/src-astro/hero/Hero.astro +72 -8
  22. package/dist/src-astro/icons/CodeIcon.astro +22 -0
  23. package/dist/src-astro/icons/index.ts +2 -1
  24. package/dist/src-astro/install-tabs/ButtonCopyInstall.astro +102 -0
  25. package/dist/src-astro/install-tabs/InstallTabs.astro +174 -0
  26. package/dist/src-astro/install-tabs/index.ts +2 -0
  27. package/dist/src-astro/install-tabs/types.ts +8 -0
  28. package/dist/src-astro/language-switcher/LanguageSwitcher.astro +6 -2
  29. package/dist/src-astro/layout-app/AppLayout.astro +1 -10
  30. package/dist/src-astro/layout-basic/BaseLayout.astro +20 -4
  31. package/dist/src-astro/link/Link.astro +50 -3
  32. package/dist/src-astro/types/meta.ts +65 -53
  33. package/package.json +3 -2
  34. package/dist/src-astro/banner/BannerAllAnimations.astro +0 -10
  35. package/dist/src-astro/banner/BannerBasic.astro +0 -5
  36. package/dist/src-astro/banner/BannerCustomStyle.astro +0 -8
  37. package/dist/src-astro/banner/BannerDanger.astro +0 -5
  38. package/dist/src-astro/banner/BannerFadeIn.astro +0 -5
  39. package/dist/src-astro/banner/BannerInfo.astro +0 -5
  40. package/dist/src-astro/banner/BannerPrimary.astro +0 -5
  41. package/dist/src-astro/banner/BannerSecondary.astro +0 -5
  42. package/dist/src-astro/banner/BannerSlideUp.astro +0 -5
  43. package/dist/src-astro/banner/BannerSuccess.astro +0 -5
  44. package/dist/src-astro/banner/BannerWarning.astro +0 -5
  45. package/dist/src-astro/grid/GridBasic.astro +0 -21
  46. package/dist/src-astro/heading/HeadingBasic.astro +0 -10
@@ -7,6 +7,8 @@ export * from './src-astro/card';
7
7
  export * from './src-astro/code-block';
8
8
  export * from './src-astro/code-container';
9
9
  export * from './src-astro/code-example';
10
+ export * from './src-astro/code-panel';
11
+ export * from './src-astro/install-tabs';
10
12
  export * from './src-astro/contact';
11
13
  export * from './src-astro/container';
12
14
  export * from './src-astro/errors';
@@ -230,4 +230,8 @@ export const iconData: Record<string, IconData> = {
230
230
  path: 'M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM8.82258 15.3427L8.03226 16.7137C7.80645 17.1089 7.30242 17.2419 6.90726 17.0161C6.5121 16.7903 6.37903 16.2863 6.60484 15.8911L7.19355 14.875C7.85484 14.6694 8.39516 14.8266 8.82258 15.3427ZM13.2097 8.66129L15.6331 12.8548H17.7742C18.2298 12.8548 18.5968 13.2218 18.5968 13.6774C18.5968 14.1331 18.2298 14.5 17.7742 14.5H16.5847L17.3871 15.8911C17.6129 16.2863 17.4798 16.7863 17.0847 17.0161C16.6895 17.2419 16.1895 17.1089 15.9597 16.7137L12.9194 11.4476C12.2298 10.2581 12.7218 9.06452 13.2097 8.66129ZM13.4879 5.61694C13.8831 5.84274 14.0161 6.34677 13.7903 6.74194L10.2621 12.8508H12.8145C13.6411 12.8508 14.1048 13.8226 13.746 14.496H6.26613C5.81048 14.496 5.44355 14.129 5.44355 13.6734C5.44355 13.2177 5.81048 12.8508 6.26613 12.8508H8.3629L11.0484 8.19758L10.2097 6.74194C9.98387 6.34677 10.1169 5.84677 10.5121 5.61694C10.9073 5.39113 11.4073 5.52419 11.6371 5.91935L11.996 6.55242L12.3629 5.91935C12.5887 5.52419 13.0927 5.39113 13.4879 5.61694Z',
231
231
  viewBox: '0 0 24 24',
232
232
  },
233
- };
233
+
234
+ code: {
235
+ path: 'M8 6L2 12L8 18M16 6L22 12L16 18M10 16L14 8',
236
+ },
237
+ };
@@ -56,7 +56,23 @@ interface Props {
56
56
  | 'info'
57
57
  | 'success'
58
58
  | 'warning'
59
- | 'error';
59
+ | 'error'
60
+ // 大自然灵感渐变
61
+ | 'gradient-sky'
62
+ | 'gradient-sunset'
63
+ | 'gradient-forest'
64
+ | 'gradient-ocean'
65
+ | 'gradient-mountain'
66
+ | 'gradient-flower'
67
+ // 水果灵感渐变
68
+ | 'gradient-watermelon'
69
+ | 'gradient-lemon'
70
+ | 'gradient-grape'
71
+ | 'gradient-blueberry'
72
+ | 'gradient-mango'
73
+ | 'gradient-kiwi'
74
+ | 'gradient-pitaya'
75
+ | 'gradient-banana';
60
76
 
61
77
  /**
62
78
  * 文本颜色,默认根据背景色自动设置
@@ -101,7 +117,43 @@ const getTextColorClass = () => {
101
117
  : 'cosy:text-gray-800';
102
118
  };
103
119
 
104
- // 组合CSS类
120
+ // 渐变色背景映射
121
+ const gradientBgClassMap = {
122
+ 'gradient-sky': 'cosy:bg-gradient-to-r cosy:from-sky-400 cosy:to-indigo-500',
123
+ 'gradient-watermelon':
124
+ 'cosy:bg-gradient-to-r cosy:from-green-300 cosy:via-pink-400 cosy:to-red-500',
125
+ 'gradient-lemon':
126
+ 'cosy:bg-gradient-to-r cosy:from-yellow-200 cosy:via-yellow-400 cosy:to-yellow-600',
127
+ 'gradient-grape':
128
+ 'cosy:bg-gradient-to-r cosy:from-purple-400 cosy:via-indigo-500 cosy:to-purple-700',
129
+ 'gradient-mango':
130
+ 'cosy:bg-gradient-to-r cosy:from-yellow-300 cosy:via-orange-400 cosy:to-orange-600',
131
+ 'gradient-forest':
132
+ 'cosy:bg-gradient-to-r cosy:from-green-700 cosy:to-lime-300',
133
+ 'gradient-ocean': 'cosy:bg-gradient-to-r cosy:from-cyan-400 cosy:to-blue-700',
134
+ 'gradient-sunset':
135
+ 'cosy:bg-gradient-to-r cosy:from-orange-400 cosy:via-pink-500 cosy:to-red-500',
136
+ 'gradient-flower':
137
+ 'cosy:bg-gradient-to-r cosy:from-pink-300 cosy:via-purple-400 cosy:to-fuchsia-500',
138
+ 'gradient-pitaya':
139
+ 'cosy:bg-gradient-to-r cosy:from-pink-200 cosy:via-fuchsia-400 cosy:to-lime-300',
140
+ 'gradient-banana':
141
+ 'cosy:bg-gradient-to-r cosy:from-yellow-100 cosy:via-yellow-300 cosy:to-yellow-500',
142
+ 'gradient-blueberry':
143
+ 'cosy:bg-gradient-to-r cosy:from-blue-400 cosy:via-blue-600 cosy:to-indigo-700',
144
+ 'gradient-kiwi':
145
+ 'cosy:bg-gradient-to-r cosy:from-lime-200 cosy:via-green-400 cosy:to-green-700',
146
+ };
147
+
148
+ const isGradient =
149
+ typeof bgColor === 'string' && bgColor.startsWith('gradient-');
150
+ let bgClass = '';
151
+ if (isGradient) {
152
+ bgClass =
153
+ gradientBgClassMap[bgColor as keyof typeof gradientBgClassMap] ?? '';
154
+ } else {
155
+ bgClass = `cosy:bg-${bgColor}`;
156
+ }
105
157
  const bannerClasses = [
106
158
  'cosy:w-full',
107
159
  'cosy:py-8',
@@ -119,7 +171,7 @@ const bannerClasses = [
119
171
  'cosy:shadow-md',
120
172
  'cosy:hover:shadow-lg',
121
173
  'cosy:hover:scale-[1.01]',
122
- `cosy:bg-${bgColor}`,
174
+ bgClass,
123
175
  getTextColorClass(),
124
176
  className,
125
177
  ].join(' ');
@@ -1,54 +1,3 @@
1
1
  import Banner from './Banner.astro';
2
- import BannerBasic from './BannerBasic.astro';
3
- import BannerPrimary from './BannerPrimary.astro';
4
- import BannerSecondary from './BannerSecondary.astro';
5
- import BannerSuccess from './BannerSuccess.astro';
6
- import BannerWarning from './BannerWarning.astro';
7
- import BannerDanger from './BannerDanger.astro';
8
- import BannerInfo from './BannerInfo.astro';
9
- import BannerCustomStyle from './BannerCustomStyle.astro';
10
- import BannerFadeIn from './BannerFadeIn.astro';
11
- import BannerSlideUp from './BannerSlideUp.astro';
12
- import BannerAllAnimations from './BannerAllAnimations.astro';
13
- import BasicSourceCode from './BannerBasic.astro?raw';
14
- import PrimarySourceCode from './BannerPrimary.astro?raw';
15
- import SecondarySourceCode from './BannerSecondary.astro?raw';
16
- import SuccessSourceCode from './BannerSuccess.astro?raw';
17
- import WarningSourceCode from './BannerWarning.astro?raw';
18
- import DangerSourceCode from './BannerDanger.astro?raw';
19
- import InfoSourceCode from './BannerInfo.astro?raw';
20
- import CustomStyleSourceCode from './BannerCustomStyle.astro?raw';
21
- import FadeInSourceCode from './BannerFadeIn.astro?raw';
22
- import SlideUpSourceCode from './BannerSlideUp.astro?raw';
23
- import AllAnimationsSourceCode from './BannerAllAnimations.astro?raw';
24
- import { extractSimpleExample } from '../../src/utils/component';
25
2
 
26
- export {
27
- Banner,
28
- BannerBasic,
29
- BannerPrimary,
30
- BannerSecondary,
31
- BannerSuccess,
32
- BannerWarning,
33
- BannerDanger,
34
- BannerInfo,
35
- BannerCustomStyle,
36
- BannerFadeIn,
37
- BannerSlideUp,
38
- BannerAllAnimations,
39
- };
40
-
41
- // 导出示例源代码
42
- export const BannerExampleCodes = {
43
- Basic: extractSimpleExample(BasicSourceCode, 'Banner'),
44
- Primary: extractSimpleExample(PrimarySourceCode, 'Banner'),
45
- Secondary: extractSimpleExample(SecondarySourceCode, 'Banner'),
46
- Success: extractSimpleExample(SuccessSourceCode, 'Banner'),
47
- Warning: extractSimpleExample(WarningSourceCode, 'Banner'),
48
- Danger: extractSimpleExample(DangerSourceCode, 'Banner'),
49
- Info: extractSimpleExample(InfoSourceCode, 'Banner'),
50
- CustomStyle: extractSimpleExample(CustomStyleSourceCode, 'Banner'),
51
- FadeIn: extractSimpleExample(FadeInSourceCode, 'Banner'),
52
- SlideUp: extractSimpleExample(SlideUpSourceCode, 'Banner'),
53
- AllAnimations: extractSimpleExample(AllAnimationsSourceCode, 'Banner'),
54
- };
3
+ export { Banner };
@@ -69,7 +69,20 @@ interface Props {
69
69
  | 'ghost'
70
70
  | 'link'
71
71
  | 'outline'
72
- | 'neutral';
72
+ | 'neutral'
73
+ | 'gradient-sky'
74
+ | 'gradient-watermelon'
75
+ | 'gradient-lemon'
76
+ | 'gradient-grape'
77
+ | 'gradient-mango'
78
+ | 'gradient-forest'
79
+ | 'gradient-ocean'
80
+ | 'gradient-sunset'
81
+ | 'gradient-flower'
82
+ | 'gradient-pitaya'
83
+ | 'gradient-banana'
84
+ | 'gradient-blueberry'
85
+ | 'gradient-kiwi';
73
86
  size?: 'lg' | 'md' | 'sm' | 'xs';
74
87
  shape?: 'circle' | 'square';
75
88
  wide?: boolean;
@@ -119,6 +132,32 @@ const getButtonClasses = () => {
119
132
  link: 'cosy:btn-link',
120
133
  outline: 'cosy:btn-outline',
121
134
  neutral: 'cosy:btn-neutral',
135
+ 'gradient-sky':
136
+ 'cosy:bg-gradient-to-r cosy:from-sky-400 cosy:to-indigo-500 cosy:text-white hover:cosy:from-sky-500 hover:cosy:to-indigo-600',
137
+ 'gradient-watermelon':
138
+ 'cosy:bg-gradient-to-r cosy:from-green-300 cosy:via-pink-400 cosy:to-red-500 cosy:text-white hover:cosy:from-green-400 hover:cosy:to-red-600',
139
+ 'gradient-lemon':
140
+ 'cosy:bg-gradient-to-r cosy:from-yellow-200 cosy:via-yellow-400 cosy:to-yellow-600 cosy:text-gray-900 hover:cosy:from-yellow-300 hover:cosy:to-yellow-700',
141
+ 'gradient-grape':
142
+ 'cosy:bg-gradient-to-r cosy:from-purple-400 cosy:via-indigo-500 cosy:to-purple-700 cosy:text-white hover:cosy:from-purple-500 hover:cosy:to-purple-800',
143
+ 'gradient-mango':
144
+ 'cosy:bg-gradient-to-r cosy:from-yellow-300 cosy:via-orange-400 cosy:to-orange-600 cosy:text-white hover:cosy:from-yellow-400 hover:cosy:to-orange-700',
145
+ 'gradient-forest':
146
+ 'cosy:bg-gradient-to-r cosy:from-green-700 cosy:to-lime-300 cosy:text-white hover:cosy:from-green-800 hover:cosy:to-lime-400',
147
+ 'gradient-ocean':
148
+ 'cosy:bg-gradient-to-r cosy:from-cyan-400 cosy:to-blue-700 cosy:text-white hover:cosy:from-cyan-500 hover:cosy:to-blue-800',
149
+ 'gradient-sunset':
150
+ 'cosy:bg-gradient-to-r cosy:from-orange-400 cosy:via-pink-500 cosy:to-red-500 cosy:text-white hover:cosy:from-orange-500 hover:cosy:to-red-600',
151
+ 'gradient-flower':
152
+ 'cosy:bg-gradient-to-r cosy:from-pink-300 cosy:via-purple-400 cosy:to-fuchsia-500 cosy:text-white hover:cosy:from-pink-400 hover:cosy:to-fuchsia-600',
153
+ 'gradient-pitaya':
154
+ 'cosy:bg-gradient-to-r cosy:from-pink-200 cosy:via-fuchsia-400 cosy:to-lime-300 cosy:text-white hover:cosy:from-pink-300 hover:cosy:to-lime-400',
155
+ 'gradient-banana':
156
+ 'cosy:bg-gradient-to-r cosy:from-yellow-100 cosy:via-yellow-300 cosy:to-yellow-500 cosy:text-gray-900 hover:cosy:from-yellow-200 hover:cosy:to-yellow-600',
157
+ 'gradient-blueberry':
158
+ 'cosy:bg-gradient-to-r cosy:from-blue-400 cosy:via-blue-600 cosy:to-indigo-700 cosy:text-white hover:cosy:from-blue-500 hover:cosy:to-indigo-800',
159
+ 'gradient-kiwi':
160
+ 'cosy:bg-gradient-to-r cosy:from-lime-200 cosy:via-green-400 cosy:to-green-700 cosy:text-white hover:cosy:from-lime-300 hover:cosy:to-green-800',
122
161
  };
123
162
 
124
163
  // Size classes
@@ -0,0 +1,55 @@
1
+ ---
2
+ import { CodeIcon } from '../icons';
3
+ const { isCodeView = false } = Astro.props;
4
+ ---
5
+
6
+ <button
7
+ role="switch"
8
+ class={`cosy:btn cosy:btn-ghost cosy:btn-sm ${isCodeView ? 'cosy:btn-primary' : ''}`}
9
+ aria-checked={isCodeView ? 'true' : 'false'}
10
+ aria-label="切换代码/预览"
11
+ data-toggle="code">
12
+ <span class="cosy:code-icon"><CodeIcon /></span>
13
+ </button>
14
+
15
+ <script>
16
+ function initializeCodeToggle() {
17
+ console.log('CodeContainer: 初始化代码/预览切换按钮');
18
+ const codeToggles = document.querySelectorAll('[data-toggle="code"]');
19
+ codeToggles.forEach((toggle) => {
20
+ toggle.addEventListener('click', () => {
21
+ const container = toggle.closest('[data-role="code-container"]');
22
+ if (!container) {
23
+ console.error('CodeContainer: 无法找到父容器');
24
+ return;
25
+ }
26
+ const isChecked = toggle.getAttribute('aria-checked') === 'true';
27
+ toggle.setAttribute('aria-checked', !isChecked ? 'true' : 'false');
28
+ toggle.classList.toggle('cosy:btn-primary', !isChecked);
29
+ toggle.classList.toggle('cosy:btn-ghost', isChecked);
30
+ const activeExample = container.querySelector(
31
+ '.cosy\\:example-container:not(.cosy\\:hidden)'
32
+ );
33
+ if (!activeExample) {
34
+ console.error('CodeContainer: 无法找到活动示例');
35
+ return;
36
+ }
37
+ activeExample
38
+ .querySelectorAll('.cosy\\:code-example-panel')
39
+ .forEach((panel) => {
40
+ if (panel.getAttribute('data-panel') === 'code') {
41
+ panel.classList.toggle('cosy:hidden', isChecked);
42
+ panel.classList.toggle('cosy:block', !isChecked);
43
+ } else {
44
+ panel.classList.toggle('cosy:hidden', !isChecked);
45
+ panel.classList.toggle('cosy:block', isChecked);
46
+ }
47
+ });
48
+ });
49
+ });
50
+ }
51
+
52
+ document.addEventListener('astro:page-load', () => {
53
+ initializeCodeToggle();
54
+ });
55
+ </script>
@@ -0,0 +1,74 @@
1
+ ---
2
+ import { ClipboardIcon } from '../icons';
3
+ ---
4
+
5
+ <button
6
+ class="cosy:gap-2 cosy:btn cosy:btn-ghost cosy:btn-sm"
7
+ aria-label="复制代码"
8
+ id="cosy-copy-btn"
9
+ type="button"
10
+ style="position: relative;">
11
+ <span class="cosy:copy-icon"><ClipboardIcon /></span>
12
+ <span
13
+ id="copy-toast"
14
+ style="
15
+ display: none;
16
+ position: absolute;
17
+ top: -2.5rem;
18
+ left: 50%;
19
+ transform: translateX(-50%);
20
+ background: #22c55e;
21
+ color: #fff;
22
+ padding: 0.25rem 0.75rem;
23
+ border-radius: 0.5rem;
24
+ font-size: 0.875rem;
25
+ white-space: nowrap;
26
+ z-index: 10;
27
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
28
+ pointer-events: none;
29
+ transition: opacity 0.2s;
30
+ "
31
+ >复制成功</span
32
+ >
33
+ </button>
34
+
35
+ <script>
36
+ function initializeCopyCode() {
37
+ console.log('CodeContainer: 初始化复制代码按钮');
38
+
39
+ const copyButtons = document.querySelectorAll('[aria-label="复制代码"]');
40
+ copyButtons.forEach((button) => {
41
+ button.addEventListener('click', () => {
42
+ const container = button.closest('[data-role="code-container"]');
43
+ if (!container) return;
44
+ const activeExample = container.querySelector(
45
+ '.cosy\\:example-container:not(.cosy\\:hidden)'
46
+ );
47
+ if (!activeExample) return;
48
+ const codePanel = activeExample.querySelector('[data-panel="code"]');
49
+ if (!codePanel) return;
50
+ const codeElement = codePanel.querySelector('code');
51
+ if (!codeElement) return;
52
+ const code = codeElement.textContent || '';
53
+ navigator.clipboard.writeText(code).then(() => {
54
+ // 气泡提示
55
+ const toast = button.querySelector('#copy-toast');
56
+ if (toast && toast instanceof HTMLElement) {
57
+ toast.style.display = 'block';
58
+ toast.style.opacity = '1';
59
+ setTimeout(() => {
60
+ toast.style.opacity = '0';
61
+ setTimeout(() => {
62
+ toast.style.display = 'none';
63
+ }, 200);
64
+ }, 2000);
65
+ }
66
+ });
67
+ });
68
+ });
69
+ }
70
+
71
+ document.addEventListener('astro:page-load', () => {
72
+ initializeCopyCode();
73
+ });
74
+ </script>
@@ -34,7 +34,10 @@
34
34
  * ```
35
35
  */
36
36
 
37
- import { ClipboardIcon, CheckIcon, InfoIcon } from '../../index-astro';
37
+ import CodeToolbar from './CodeToolbar.astro';
38
+ import Description from './Description.astro';
39
+ import Preview from './Preview.astro';
40
+ import CodePanel from './CodePanel.astro';
38
41
  import '../../style.ts';
39
42
 
40
43
  interface Props {
@@ -66,318 +69,40 @@ const {
66
69
  ---
67
70
 
68
71
  <div data-role="code-container" class="cosy:mb-8 cosy:card" ignore-heading>
69
- <!-- 工具栏 -->
70
- <div
71
- class="cosy:flex cosy:justify-between cosy:items-center cosy:bg-base-200 cosy:px-4 cosy:rounded-t">
72
- <!-- 标签 -->
73
- <div class="cosy:flex cosy:items-center cosy:gap-4">
74
- <div role="tablist" class="cosy:tabs cosy:tabs-box">
75
- {
76
- codes.map((_, index) => (
77
- <button
78
- role="tab"
79
- class={`cosy:tab ${index === 0 ? 'cosy:tab-active' : ''}`}
80
- data-tab={`tab-${index + 1}`}>
81
- {titles[index] || `示例 ${index + 1}`}
82
- </button>
83
- ))
84
- }
85
- </div>
86
- </div>
87
-
88
- <!-- 工具按钮 -->
89
- <div class="cosy:flex cosy:items-center cosy:gap-2">
90
- <button
91
- role="switch"
92
- class="cosy:btn cosy:btn-ghost cosy:btn-sm"
93
- aria-checked="false"
94
- aria-label="切换代码/预览"
95
- data-toggle="code">
96
- <span class="cosy:code-icon"><InfoIcon /></span>
97
- </button>
98
- <button
99
- class="cosy:gap-2 cosy:btn cosy:btn-ghost cosy:btn-sm"
100
- aria-label="复制代码">
101
- <span class="cosy:copy-icon"><ClipboardIcon /></span>
102
- <span class="cosy:hidden cosy:check-icon"><CheckIcon /></span>
103
- </button>
104
- </div>
105
- </div>
72
+ <CodeToolbar
73
+ titles={titles.length ? titles : codes.map((_, i) => `示例 ${i + 1}`)}
74
+ activeTab={0}
75
+ isCodeView={false}
76
+ code={codes[0]}
77
+ />
106
78
 
107
79
  {
108
80
  codes.map((code, index) => (
109
81
  <div
110
82
  class={`cosy:example-container ${index === 0 ? 'cosy:block' : 'cosy:hidden'}`}
111
83
  data-example={`tab-${index + 1}`}>
112
- {/* 描述 */}
113
- {descriptions[index] && (
114
- <p
115
- class="cosy:px-6 cosy:py-2 cosy:text-sm not-prose
116
- cosy:bg-gradient-to-b cosy:from-blue-100/50 cosy:to-blue-100/90
117
- cosy:dark:from-blue-900/20 cosy:dark:to-blue-900/20">
118
- {descriptions[index]}
119
- </p>
120
- )}
84
+ <Description text={descriptions[index]} />
121
85
 
122
86
  <div class="cosy:relative cosy:p-4 cosy:bg-gradient-to-br cosy:from-red-100 cosy:to-green-300 cosy:dark:from-red-900/20 cosy:dark:to-green-900/20 cosy:rounded-b">
123
- <div class="cosy:block cosy:code-example-panel" data-panel="preview">
87
+ <Preview>
124
88
  <div
125
89
  class={`cosy:code-preview-area ${resetStyles ? 'cosy:reset-styles' : ''}`}
126
90
  data-preview-area={`tab-${index + 1}`}>
127
91
  <slot />
128
92
  </div>
129
- </div>
93
+ </Preview>
130
94
 
131
- <div class="cosy:hidden cosy:code-example-panel" data-panel="code">
132
- <pre class="cosy:overflow-x-auto" style="margin:0">
133
- <code class="cosy:language-astro">{code}</code>
134
- </pre>
135
- </div>
95
+ <CodePanel code={code} />
136
96
  </div>
137
97
  </div>
138
98
  ))
139
99
  }
140
100
  </div>
141
101
 
142
- <!-- 样式隔离和重置 -->
143
- <style>
144
- /* 预览区域样式重置,防止外部样式影响内部组件 */
145
- [data-role='code-container'] .cosy\:reset-styles {
146
- /* 重置所有可能影响布局的样式 */
147
- all: initial;
148
- font-family: inherit;
149
- color: inherit;
150
- line-height: 1.5;
151
-
152
- /* 恢复必要的基础样式 */
153
- display: block;
154
- box-sizing: border-box;
155
-
156
- /* 隔离样式作用域 */
157
- isolation: isolate;
158
- contain: layout style;
159
- }
160
-
161
- /* 重置内部所有元素的样式 */
162
- [data-role='code-container'] .cosy\:reset-styles * {
163
- all: unset;
164
- display: revert;
165
- box-sizing: border-box;
166
- }
167
-
168
- /* 恢复必要的文本样式 */
169
- [data-role='code-container'] .cosy\:reset-styles {
170
- font-family:
171
- system-ui,
172
- -apple-system,
173
- BlinkMacSystemFont,
174
- 'Segoe UI',
175
- Roboto,
176
- 'Helvetica Neue',
177
- Arial,
178
- sans-serif;
179
- font-size: 14px;
180
- line-height: 1.5;
181
- color: #374151;
182
- }
183
-
184
- /* 恢复基本的HTML元素样式 */
185
- [data-role='code-container'] .cosy\:reset-styles h1,
186
- [data-role='code-container'] .cosy\:reset-styles h2,
187
- [data-role='code-container'] .cosy\:reset-styles h3,
188
- [data-role='code-container'] .cosy\:reset-styles h4,
189
- [data-role='code-container'] .cosy\:reset-styles h5,
190
- [data-role='code-container'] .cosy\:reset-styles h6 {
191
- font-weight: bold;
192
- margin-bottom: 0.5em;
193
- }
194
-
195
- [data-role='code-container'] .cosy\:reset-styles p {
196
- margin-bottom: 1em;
197
- }
198
-
199
- [data-role='code-container'] .cosy\:reset-styles ul,
200
- [data-role='code-container'] .cosy\:reset-styles ol {
201
- padding-left: 1.5em;
202
- margin-bottom: 1em;
203
- }
204
-
205
- [data-role='code-container'] .cosy\:reset-styles li {
206
- margin-bottom: 0.25em;
207
- }
208
-
209
- /* 防止内部组件的样式泄漏到外部 */
210
- [data-role='code-container'] .cosy\:code-preview-area {
211
- position: relative;
212
- z-index: 0;
213
- }
214
-
215
- /* 确保预览区域的样式隔离 */
216
- [data-role='code-container'] .cosy\:code-preview-area::before {
217
- content: '';
218
- position: absolute;
219
- top: -1px;
220
- left: -1px;
221
- right: -1px;
222
- bottom: -1px;
223
- pointer-events: none;
224
- z-index: -1;
225
- }
226
- </style>
227
-
228
102
  <script>
229
103
  function initializeCodeContainer() {
230
104
  console.log('CodeContainer: 初始化代码容器');
231
105
 
232
- // 示例切换功能
233
- const exampleTabs = document.querySelectorAll(
234
- '[role="tab"][data-tab^="tab-"]'
235
- );
236
- exampleTabs.forEach((tab) => {
237
- tab.addEventListener('click', () => {
238
- console.log('CodeContainer: 切换示例', tab.getAttribute('data-tab'));
239
- const container = tab.closest('[data-role="code-container"]');
240
- if (!container) return;
241
-
242
- // 更新标签状态
243
- container
244
- .querySelectorAll('[role="tab"][data-tab^="tab-"]')
245
- .forEach((t) => {
246
- t.classList.remove('cosy:tab-active');
247
- });
248
- tab.classList.add('cosy:tab-active');
249
-
250
- // 获取当前标签对应的内容 id
251
- const targetId = tab.getAttribute('data-tab');
252
- if (!targetId) return;
253
-
254
- // 切换示例容器
255
- container
256
- .querySelectorAll('.cosy\\:example-container')
257
- .forEach((example) => {
258
- if (example.getAttribute('data-example') === targetId) {
259
- example.classList.remove('cosy:hidden');
260
- example.classList.add('cosy:block');
261
- } else {
262
- example.classList.add('cosy:hidden');
263
- example.classList.remove('cosy:block');
264
- }
265
- });
266
-
267
- // 更新对应内容的可见性
268
- const allContent = container.querySelectorAll('[id^="tab-"]');
269
- allContent.forEach((content) => {
270
- if (content.id === targetId) {
271
- content.classList.remove('cosy:hidden');
272
- content.classList.add('cosy:block');
273
- } else {
274
- content.classList.add('cosy:hidden');
275
- content.classList.remove('cosy:block');
276
- }
277
- });
278
- });
279
- });
280
-
281
- // 代码开关功能
282
- const codeToggles = document.querySelectorAll('[data-toggle="code"]');
283
- codeToggles.forEach((toggle) => {
284
- toggle.addEventListener('click', () => {
285
- console.log('code toggle clicked', toggle);
286
- const container = toggle.closest('[data-role="code-container"]');
287
- if (!container) {
288
- console.error('CodeContainer: 无法找到父容器');
289
- return;
290
- }
291
-
292
- // 切换开关状态
293
- const isChecked = toggle.getAttribute('aria-checked') === 'true';
294
- console.log('代码按钮 isChecked:', isChecked);
295
- toggle.setAttribute('aria-checked', !isChecked ? 'true' : 'false');
296
- toggle.classList.toggle('cosy:btn-primary', !isChecked);
297
- toggle.classList.toggle('cosy:btn-ghost', isChecked);
298
-
299
- // 找到当前显示的示例
300
- const activeExample = container.querySelector(
301
- '.cosy\\:example-container:not(.cosy\\:hidden)'
302
- );
303
- if (!activeExample) {
304
- console.error('CodeContainer: 无法找到活动示例');
305
- return;
306
- }
307
-
308
- // 切换面板
309
- activeExample
310
- .querySelectorAll('.cosy\\:code-example-panel')
311
- .forEach((panel) => {
312
- if (panel.getAttribute('data-panel') === 'code') {
313
- console.log(
314
- '处理代码面板的展示/隐藏,此时的isChecked:',
315
- isChecked
316
- );
317
- panel.classList.toggle('cosy:hidden', isChecked);
318
- panel.classList.toggle('cosy:block', !isChecked);
319
- } else {
320
- console.log(
321
- '处理预览面板的展示/隐藏,此时的isChecked:',
322
- isChecked
323
- );
324
- panel.classList.toggle('cosy:hidden', !isChecked);
325
- panel.classList.toggle('cosy:block', isChecked);
326
- }
327
- });
328
- });
329
- });
330
-
331
- // 复制代码功能
332
- const copyButtons = document.querySelectorAll('[aria-label="复制代码"]');
333
- copyButtons.forEach((button) => {
334
- button.addEventListener('click', () => {
335
- const container = button.closest('[data-role="code-container"]');
336
- if (!container) return;
337
-
338
- // 确保代码面板是显示的
339
- const codeToggle = container.querySelector('[data-toggle="code"]');
340
- if (codeToggle?.getAttribute('aria-checked') !== 'true') {
341
- (codeToggle as HTMLElement)?.click();
342
- }
343
-
344
- // 找到当前显示的示例
345
- const activeExample = container.querySelector(
346
- '.cosy\\:example-container:not(.cosy\\:hidden)'
347
- );
348
- if (!activeExample) return;
349
-
350
- const codePanel = activeExample.querySelector('[data-panel="code"]');
351
- if (!codePanel) return;
352
-
353
- const codeElement = codePanel.querySelector('code');
354
- if (!codeElement) return;
355
-
356
- const code = codeElement.textContent || '';
357
-
358
- // 复制到剪贴板
359
- navigator.clipboard.writeText(code).then(() => {
360
- button.classList.add('cosy:btn-success');
361
- button
362
- .querySelector('.cosy\\:copy-icon')
363
- ?.classList.add('cosy:hidden');
364
- button
365
- .querySelector('.cosy\\:check-icon')
366
- ?.classList.remove('cosy:hidden');
367
-
368
- setTimeout(() => {
369
- button.classList.remove('cosy:btn-success');
370
- button
371
- .querySelector('.cosy\\:copy-icon')
372
- ?.classList.remove('cosy:hidden');
373
- button
374
- .querySelector('.cosy\\:check-icon')
375
- ?.classList.add('cosy:hidden');
376
- }, 3000);
377
- });
378
- });
379
- });
380
-
381
106
  // 初始化时隐藏除第一个以外的所有内容
382
107
  document
383
108
  .querySelectorAll('[data-role="code-container"]')