@coffic/cosy-ui 0.6.12 → 0.6.14

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 (149) hide show
  1. package/dist/app.css +1 -1
  2. package/dist/assets/iconData.ts +93 -0
  3. package/dist/components/base/Alert.astro +1 -1
  4. package/dist/components/base/Button.astro +102 -91
  5. package/dist/components/base/Image.astro +1 -1
  6. package/dist/components/base/Link.astro +1 -1
  7. package/dist/components/containers/Container.astro +1 -1
  8. package/dist/components/containers/Main.astro +1 -1
  9. package/dist/components/containers/Section.astro +96 -94
  10. package/dist/components/data-display/Blog.astro +1 -1
  11. package/dist/components/data-display/ProductCard.astro +2 -4
  12. package/dist/components/data-display/Products.astro +2 -2
  13. package/dist/components/data-display/TeamMember.astro +2 -4
  14. package/dist/components/data-display/TeamMembers.astro +1 -1
  15. package/dist/components/display/Banner.astro +1 -1
  16. package/dist/components/display/Card.astro +1 -1
  17. package/dist/components/display/CodeBlock.astro +1 -1
  18. package/dist/components/display/CodeExample.astro +1 -1
  19. package/dist/components/display/Hero.astro +1 -1
  20. package/dist/components/display/Modal.astro +1 -1
  21. package/dist/components/layouts/AppLayout.astro +2 -6
  22. package/dist/components/layouts/BaseLayout.astro +1 -1
  23. package/dist/components/layouts/DashboardLayout.astro +1 -1
  24. package/dist/components/layouts/Footer.astro +2 -5
  25. package/dist/components/layouts/Grid.astro +1 -1
  26. package/dist/components/layouts/Header.astro +1 -1
  27. package/dist/components/layouts/NavItems.astro +1 -1
  28. package/dist/components/layouts/Sidebar.astro +2 -2
  29. package/dist/components/layouts/SidebarNav.astro +1 -1
  30. package/dist/components/layouts/Stack.astro +72 -70
  31. package/dist/components/navigation/LanguageSwitcher.astro +2 -2
  32. package/dist/components/navigation/TableOfContents.astro +1 -1
  33. package/dist/components/navigation/ThemeSwitcher.astro +2 -2
  34. package/dist/components/typography/Article.astro +2 -2
  35. package/dist/components/typography/Heading.astro +2 -2
  36. package/dist/components/typography/Text.astro +1 -1
  37. package/dist/icons/AlertTriangle.astro +24 -0
  38. package/dist/icons/AstroIcon.astro +46 -0
  39. package/dist/icons/CalendarIcon.astro +24 -0
  40. package/dist/icons/CheckCircle.astro +23 -0
  41. package/dist/icons/CheckIcon.astro +27 -0
  42. package/dist/{components/icons → icons}/ChevronDownIcon.astro +3 -13
  43. package/dist/icons/ClipboardIcon.astro +27 -0
  44. package/dist/icons/CloseIcon.astro +27 -0
  45. package/dist/icons/ErrorIcon.astro +24 -0
  46. package/dist/icons/GithubIcon.astro +24 -0
  47. package/dist/icons/InfoCircle.astro +24 -0
  48. package/dist/icons/InfoIcon.astro +26 -0
  49. package/dist/icons/LinkIcon.astro +27 -0
  50. package/dist/icons/LinkedinIcon.astro +23 -0
  51. package/dist/icons/MenuIcon.astro +27 -0
  52. package/dist/icons/SearchIcon.astro +23 -0
  53. package/dist/icons/SettingsIcon.astro +18 -0
  54. package/dist/{components/icons → icons}/SocialIcon.astro +14 -14
  55. package/dist/icons/SuccessIcon.astro +24 -0
  56. package/dist/icons/SunCloudyIcon.astro +23 -0
  57. package/dist/icons/TwitterIcon.astro +24 -0
  58. package/dist/icons/UserIcon.astro +24 -0
  59. package/dist/icons/WarningIcon.astro +27 -0
  60. package/dist/icons/XCircle.astro +24 -0
  61. package/dist/index.ts +1 -1
  62. package/dist/index_icons.ts +23 -0
  63. package/dist/index_vue.ts +41 -0
  64. package/dist/vue/{AlertDialog.vue → AlertDialog/AlertDialog.vue} +1 -1
  65. package/dist/vue/AlertDialog/Basic.vue +38 -0
  66. package/dist/vue/AlertDialog/Multilang.vue +48 -0
  67. package/dist/vue/AlertDialog/index.ts +58 -0
  68. package/dist/vue/BannerBox/BannerBox.vue +435 -0
  69. package/dist/vue/BannerBox/Basic.vue +32 -0
  70. package/dist/vue/BannerBox/CustomBg.vue +32 -0
  71. package/dist/vue/BannerBox/CustomComponent.vue +60 -0
  72. package/dist/vue/BannerBox/DisplayMode.vue +49 -0
  73. package/dist/vue/{FeatureCard.vue → BannerBox/FeatureCard.vue} +23 -42
  74. package/dist/vue/BannerBox/ImageExport.vue +37 -0
  75. package/dist/vue/BannerBox/SizePreset.vue +35 -0
  76. package/dist/vue/BannerBox/SmartBanner.vue +44 -0
  77. package/dist/vue/BannerBox/index.ts +76 -0
  78. package/dist/vue/FeatureButton.vue +25 -26
  79. package/dist/vue/Icons/AlertTriangleIcon.vue +30 -0
  80. package/dist/vue/Icons/CalendarIcon.vue +30 -0
  81. package/dist/vue/Icons/CheckCircleIcon.vue +30 -0
  82. package/dist/vue/Icons/CheckIcon.vue +30 -0
  83. package/dist/vue/Icons/ChevronDownIcon.vue +30 -0
  84. package/dist/vue/Icons/ClipboardIcon.vue +30 -0
  85. package/dist/vue/Icons/CloseIcon.vue +30 -0
  86. package/dist/vue/Icons/InfoCircleIcon.vue +30 -0
  87. package/dist/vue/Icons/InfoIcon.vue +30 -0
  88. package/dist/vue/Icons/LinkIcon.vue +30 -0
  89. package/dist/vue/Icons/MenuIcon.vue +30 -0
  90. package/dist/vue/Icons/SearchIcon.vue +30 -0
  91. package/dist/vue/Icons/SettingsIcon.vue +30 -0
  92. package/dist/vue/Icons/UserIcon.vue +30 -0
  93. package/dist/vue/Icons/VueIcon.vue +76 -0
  94. package/dist/vue/Icons/XCircleIcon.vue +30 -0
  95. package/dist/vue/MacWindow/Basic.vue +11 -0
  96. package/dist/vue/MacWindow/CustomHeight.vue +13 -0
  97. package/dist/vue/{MacWindow.vue → MacWindow/MacWindow.vue} +70 -38
  98. package/dist/vue/MacWindow/WithEvents.vue +34 -0
  99. package/dist/vue/MacWindow/WithSidebar.vue +21 -0
  100. package/dist/vue/MacWindow/WithTabs.vue +21 -0
  101. package/dist/vue/MacWindow/WithToolbar.vue +43 -0
  102. package/dist/vue/MacWindow/index.ts +50 -0
  103. package/dist/vue/SmartHero.vue +28 -34
  104. package/dist/vue/VueCounter.vue +29 -0
  105. package/dist/vue/iPhone/Basic.vue +33 -0
  106. package/dist/vue/iPhone/CustomBackground.vue +33 -0
  107. package/dist/vue/iPhone/NoFrame.vue +33 -0
  108. package/dist/vue/iPhone/WeatherApp.vue +97 -0
  109. package/dist/vue/iPhone/assets/iPhone 14 Pro - Deep Purple - Landscape.png +0 -0
  110. package/dist/vue/iPhone/assets/iPhone 14 Pro - Deep Purple - Portrait.png +0 -0
  111. package/dist/vue/iPhone/assets/iPhone 14 Pro - Gold - Landscape.png +0 -0
  112. package/dist/vue/iPhone/assets/iPhone 14 Pro - Gold - Portrait.png +0 -0
  113. package/dist/vue/iPhone/assets/iPhone 14 Pro - Silver - Landscape.png +0 -0
  114. package/dist/vue/iPhone/assets/iPhone 14 Pro - Silver - Portrait.png +0 -0
  115. package/dist/vue/iPhone/assets/iPhone 14 Pro - Space Black - Landscape.png +0 -0
  116. package/dist/vue/iPhone/assets/iPhone 14 Pro - Space Black - Portrait.png +0 -0
  117. package/dist/vue/{iPhoneWindow.vue → iPhone/iPhoneWindow.vue} +11 -7
  118. package/dist/vue/iPhone/index.ts +41 -0
  119. package/dist/vue/shims-vue.d.ts +5 -0
  120. package/package.json +13 -12
  121. package/dist/components/icons/AlertTriangle.astro +0 -35
  122. package/dist/components/icons/CalendarIcon.astro +0 -38
  123. package/dist/components/icons/CheckCircle.astro +0 -36
  124. package/dist/components/icons/CheckIcon.astro +0 -38
  125. package/dist/components/icons/ClipboardIcon.astro +0 -39
  126. package/dist/components/icons/CloseIcon.astro +0 -38
  127. package/dist/components/icons/ErrorIcon.astro +0 -35
  128. package/dist/components/icons/GithubIcon.astro +0 -31
  129. package/dist/components/icons/InfoCircle.astro +0 -37
  130. package/dist/components/icons/InfoIcon.astro +0 -38
  131. package/dist/components/icons/LinkIcon.astro +0 -39
  132. package/dist/components/icons/LinkedinIcon.astro +0 -31
  133. package/dist/components/icons/MenuIcon.astro +0 -38
  134. package/dist/components/icons/SearchIcon.astro +0 -36
  135. package/dist/components/icons/SettingsIcon.astro +0 -36
  136. package/dist/components/icons/SuccessIcon.astro +0 -35
  137. package/dist/components/icons/SunCloudyIcon.astro +0 -41
  138. package/dist/components/icons/TwitterIcon.astro +0 -31
  139. package/dist/components/icons/UserIcon.astro +0 -35
  140. package/dist/components/icons/WarningIcon.astro +0 -38
  141. package/dist/components/icons/XCircle.astro +0 -37
  142. package/dist/entities/Banner.ts +0 -105
  143. package/dist/icons.ts +0 -22
  144. package/dist/vue/BannerBox.vue +0 -267
  145. package/dist/vue/FeatureGroup.vue +0 -22
  146. package/dist/vue/SmartBanner.vue +0 -45
  147. package/dist/vue/WildBanner.vue +0 -15
  148. package/dist/vue.ts +0 -14
  149. /package/dist/{collection.ts → index_collection.ts} +0 -0
@@ -0,0 +1,435 @@
1
+ <!--
2
+ @component BannerBox
3
+
4
+ @description
5
+ BannerBox 组件是一个可定制的横幅容器,支持自定义背景、尺寸调整和导出为图片功能。
6
+ 可以直接用作容器,也可以通过传入标题、描述和特性列表来显示内容。
7
+ 适用于创建营销横幅、特性展示、社交媒体卡片等内容。
8
+
9
+ @usage
10
+ 基本用法:
11
+ ```vue
12
+ <BannerBox>
13
+ <div>横幅内容</div>
14
+ </BannerBox>
15
+ ```
16
+
17
+ 使用标题和描述:
18
+ ```vue
19
+ <BannerBox
20
+ title="我的横幅标题"
21
+ description="这是一段描述文字"
22
+ :features="[
23
+ { emoji: '🚀', title: '高性能' },
24
+ { emoji: '⚡', title: '快速响应' },
25
+ { emoji: '🔒', title: '安全可靠' }
26
+ ]"
27
+ />
28
+ ```
29
+
30
+ 自定义背景:
31
+ ```vue
32
+ <BannerBox :backgroundClassIndex="5">
33
+ <div>自定义背景的横幅</div>
34
+ </BannerBox>
35
+ ```
36
+
37
+ 设置下载按钮显示模式:
38
+ ```vue
39
+ <BannerBox displayMode="always">
40
+ <div>总是显示下载按钮</div>
41
+ </BannerBox>
42
+ ```
43
+
44
+ @props
45
+ @prop {String} [displayMode='hover'] - 下载按钮显示模式:'always'(总是显示),'hover'(悬停显示),'never'(不显示)
46
+ @prop {Number} [backgroundClassIndex=0] - 背景样式索引,对应内置的背景样式列表
47
+ @prop {String} [title=''] - 横幅标题
48
+ @prop {String} [description=''] - 横幅描述
49
+ @prop {Array} [features=[]] - 特性列表,每项包含{emoji, title, link}
50
+ @prop {Object} [customComponent=null] - 自定义组件
51
+ @prop {Object} [customComponentProps={}] - 自定义组件的属性
52
+
53
+ @slots
54
+ @slot default - 横幅内容,当不使用title/description/features属性时显示
55
+ -->
56
+
57
+ <script lang="ts">
58
+ import { ref, onMounted, watch, onUnmounted, computed, defineComponent } from 'vue';
59
+ import { RiDownloadLine } from '@remixicon/vue';
60
+ import { toPng } from 'html-to-image';
61
+ import FeatureCard from './FeatureCard.vue';
62
+ import '../../style'
63
+
64
+ interface Feature {
65
+ emoji: string;
66
+ title: string;
67
+ link?: string;
68
+ }
69
+
70
+ export default defineComponent({
71
+ name: 'BannerBox',
72
+ components: {
73
+ RiDownloadLine,
74
+ FeatureCard
75
+ },
76
+ props: {
77
+ displayMode: {
78
+ type: String,
79
+ default: 'hover',
80
+ validator: (value: string) => ['always', 'hover', 'never'].includes(value)
81
+ },
82
+ backgroundClassIndex: {
83
+ type: Number,
84
+ default: 0
85
+ },
86
+ title: {
87
+ type: String,
88
+ default: ''
89
+ },
90
+ description: {
91
+ type: String,
92
+ default: ''
93
+ },
94
+ features: {
95
+ type: Array as () => Feature[],
96
+ default: () => []
97
+ },
98
+ // 自定义组件
99
+ customComponent: {
100
+ type: Object,
101
+ default: null
102
+ },
103
+ // 自定义组件的属性
104
+ customComponentProps: {
105
+ type: Object,
106
+ default: () => ({})
107
+ }
108
+ },
109
+ setup(props) {
110
+ const componentRef = ref<HTMLElement | null>(null);
111
+ const isDropdownOpen = ref(false);
112
+ const isLoadedFromStorage = ref(false);
113
+ const selectedBgIndex = ref(props.backgroundClassIndex);
114
+
115
+ const sizePresets = [
116
+ { name: 'Default', width: 'cosy:w-full', height: 'cosy:h-full' },
117
+ { name: 'Square', width: 'cosy:w-[600px]', height: 'cosy:h-[600px]' },
118
+ { name: 'Landscape', width: 'cosy:w-[800px]', height: 'cosy:h-[450px]' },
119
+ { name: 'Portrait', width: 'cosy:w-[450px]', height: 'cosy:h-[800px]' },
120
+ { name: 'Wide', width: 'cosy:w-[1200px]', height: 'cosy:h-[675px]' },
121
+ { name: 'Banner', width: 'cosy:w-[1200px]', height: 'cosy:h-[300px]' },
122
+ { name: '1280 × 800', width: 'cosy:w-[1280px]', height: 'cosy:h-[800px]' },
123
+ { name: '1440 × 900', width: 'cosy:w-[1440px]', height: 'cosy:h-[900px]' },
124
+ { name: '2560 × 1600', width: 'cosy:w-[2560px]', height: 'cosy:h-[1600px]' },
125
+ { name: '2880 × 1800', width: 'cosy:w-[2880px]', height: 'cosy:h-[1800px]' },
126
+ ];
127
+
128
+ const selectedSize = ref(sizePresets[0]);
129
+
130
+ const toggleDropdown = () => {
131
+ console.log("Toggle Dropdown")
132
+ isDropdownOpen.value = !isDropdownOpen.value;
133
+ };
134
+
135
+ // 监听尺寸变化并保存到 localStorage
136
+ watch(selectedSize, (newSize) => {
137
+ localStorage.setItem('bannerBoxSize', JSON.stringify(newSize));
138
+ // 当尺寸改变时,发出事件通知其他组件
139
+ window.dispatchEvent(new CustomEvent('bannerBoxSizeChange', {
140
+ detail: newSize
141
+ }));
142
+ // 设置为已加载状态,显示尺寸标签
143
+ isLoadedFromStorage.value = true;
144
+ });
145
+
146
+ // 创建自定义事件处理函数
147
+ const handleSizeClear = () => {
148
+ selectedSize.value = sizePresets[0];
149
+ isLoadedFromStorage.value = false;
150
+ };
151
+
152
+ // 处理尺寸变化的事件
153
+ const handleSizeChange = (event: Event) => {
154
+ const customEvent = event as CustomEvent;
155
+ selectedSize.value = customEvent.detail;
156
+ isLoadedFromStorage.value = true;
157
+ };
158
+
159
+ onMounted(() => {
160
+ const savedSize = localStorage.getItem('bannerBoxSize');
161
+ if (savedSize) {
162
+ const parsed = JSON.parse(savedSize);
163
+ const found = sizePresets.find(preset => preset.name === parsed.name);
164
+ if (found) {
165
+ selectedSize.value = found;
166
+ isLoadedFromStorage.value = true;
167
+ }
168
+ }
169
+
170
+ // 添加事件监听
171
+ window.addEventListener('bannerBoxClear', handleSizeClear);
172
+ window.addEventListener('bannerBoxSizeChange', handleSizeChange);
173
+
174
+ // 恢复到简单的点击监听方式,但使用正确的选择器
175
+ document.addEventListener('click', (event) => {
176
+ if (!isDropdownOpen.value) return; // 如果下拉菜单已经关闭,就不需要处理
177
+
178
+ const target = event.target as HTMLElement;
179
+ // 检查点击的元素是否是下拉菜单中的元素
180
+ const isClickedOnDropdown = !!target.closest('[data-dropdown]');
181
+
182
+ if (!isClickedOnDropdown) {
183
+ isDropdownOpen.value = false;
184
+ }
185
+ });
186
+ });
187
+
188
+ // Update downloadAsImage function to accept scale parameter
189
+ const downloadAsImage = async () => {
190
+ try {
191
+ const element = componentRef.value;
192
+ if (!element) {
193
+ console.error('Component reference is null');
194
+ return;
195
+ }
196
+
197
+ const dataUrl = await toPng(element, {
198
+ backgroundColor: undefined,
199
+ style: {
200
+ transform: 'scale(1)',
201
+ transformOrigin: 'top left'
202
+ }
203
+ });
204
+
205
+ const link = document.createElement('a');
206
+ const fileName = `feature-${element.offsetWidth}x${element.offsetHeight}.png`;
207
+ link.download = fileName;
208
+ link.href = dataUrl;
209
+ link.click();
210
+ isDropdownOpen.value = false;
211
+ } catch (error) {
212
+ console.error('Failed to download image:', error);
213
+ }
214
+ };
215
+
216
+ const bgClasses = [
217
+ 'cosy:bg-gradient-to-b cosy:from-blue-100/50 cosy:to-blue-200/50 dark:cosy:from-blue-500/10 dark:cosy:to-blue-200/10',
218
+ 'cosy:bg-gradient-to-b cosy:from-blue-200/50 cosy:to-purple-200/50 dark:cosy:from-blue-500/10 dark:cosy:to-purple-200/10',
219
+ 'cosy:bg-gradient-to-b cosy:from-yellow-200/50 cosy:to-green-200/50 dark:cosy:from-yellow-500/10 dark:cosy:to-green-200/10',
220
+ 'cosy:bg-gradient-to-b cosy:from-teal-200/50 cosy:to-blue-200/50 dark:cosy:from-teal-500/10 dark:cosy:to-blue-200/10',
221
+ 'cosy:bg-gradient-to-b cosy:from-pink-200/50 cosy:to-indigo-200/20 dark:cosy:from-pink-500/10 dark:cosy:to-indigo-200/10',
222
+ 'cosy:bg-gradient-to-b cosy:from-red-200/50 cosy:to-orange-200/50 dark:cosy:from-red-500/10 dark:cosy:to-orange-200/10',
223
+ 'cosy:bg-gradient-to-b cosy:from-orange-200/50 cosy:to-yellow-200/50 dark:cosy:from-orange-500/10 dark:cosy:to-yellow-200/10',
224
+ 'cosy:bg-gradient-to-b cosy:from-green-200/50 cosy:to-teal-200/50 dark:cosy:from-green-500/10 dark:cosy:to-teal-200/10',
225
+
226
+ // 不透明的背景
227
+ 'cosy:bg-gradient-to-b cosy:from-blue-100 cosy:to-blue-200 dark:cosy:from-blue-500 dark:cosy:to-blue-200',
228
+ 'cosy:bg-gradient-to-b cosy:from-blue-200 cosy:to-purple-200 dark:cosy:from-blue-500 dark:cosy:to-purple-200',
229
+ 'cosy:bg-gradient-to-b cosy:from-yellow-200 cosy:to-green-200 dark:cosy:from-yellow-500 dark:cosy:to-green-200',
230
+ 'cosy:bg-gradient-to-b cosy:from-teal-200 cosy:to-blue-200 dark:cosy:from-teal-500 dark:cosy:to-blue-200',
231
+ 'cosy:bg-gradient-to-b cosy:from-pink-200 cosy:to-red-200 dark:cosy:from-pink-500 dark:cosy:to-red-200',
232
+ 'cosy:bg-gradient-to-b cosy:from-red-200 cosy:to-orange-200 dark:cosy:from-red-500 dark:cosy:to-orange-200',
233
+ 'cosy:bg-gradient-to-b cosy:from-orange-200 cosy:to-yellow-200 dark:cosy:from-orange-500 dark:cosy:to-yellow-200',
234
+ 'cosy:bg-gradient-to-b cosy:from-green-200 cosy:to-teal-200 dark:cosy:from-green-500 dark:cosy:to-teal-200',
235
+
236
+ // 不透明的深色背景
237
+ 'cosy:bg-gradient-to-b cosy:from-blue-900 cosy:to-blue-200 dark:cosy:from-blue-900 dark:cosy:to-blue-200',
238
+ 'cosy:bg-gradient-to-b cosy:from-blue-900 cosy:to-purple-200 dark:cosy:from-blue-900 dark:cosy:to-purple-200',
239
+ 'cosy:bg-gradient-to-b cosy:from-yellow-900 cosy:to-green-200 dark:cosy:from-yellow-900 dark:cosy:to-green-200',
240
+ 'cosy:bg-gradient-to-b cosy:from-teal-900 cosy:to-blue-200 dark:cosy:from-teal-900 dark:cosy:to-blue-200',
241
+ 'cosy:bg-gradient-to-b cosy:from-pink-900 cosy:to-red-200 dark:cosy:from-pink-900 dark:cosy:to-red-200',
242
+ 'cosy:bg-gradient-to-b cosy:from-red-900 cosy:to-orange-200 dark:cosy:from-red-900 dark:cosy:to-orange-200',
243
+ 'cosy:bg-gradient-to-b cosy:from-orange-900 cosy:to-yellow-200 dark:cosy:from-orange-900 dark:cosy:to-yellow-200',
244
+ 'cosy:bg-gradient-to-b cosy:from-green-900 cosy:to-teal-900 dark:cosy:from-green-900 dark:cosy:to-teal-900',
245
+ // 不透明的渐变背景
246
+ 'cosy:bg-gradient-to-br cosy:from-emerald-400 cosy:to-cyan-400 dark:cosy:from-emerald-600 dark:cosy:to-cyan-600',
247
+ 'cosy:bg-gradient-to-br cosy:from-violet-400 cosy:to-fuchsia-400 dark:cosy:from-violet-600 dark:cosy:to-fuchsia-600',
248
+ 'cosy:bg-gradient-to-br cosy:from-amber-400 cosy:to-orange-400 dark:cosy:from-amber-600 dark:cosy:to-orange-600',
249
+ 'cosy:bg-gradient-to-br cosy:from-rose-400 cosy:to-pink-400 dark:cosy:from-rose-600 dark:cosy:to-pink-600',
250
+ 'cosy:bg-gradient-to-br cosy:from-sky-400 cosy:to-indigo-400 dark:cosy:from-sky-600 dark:cosy:to-indigo-600',
251
+ 'cosy:bg-gradient-to-br cosy:from-lime-400 cosy:to-emerald-400 dark:cosy:from-lime-600 dark:cosy:to-emerald-600',
252
+ 'cosy:bg-gradient-to-br cosy:from-purple-400 cosy:to-indigo-400 dark:cosy:from-purple-600 dark:cosy:to-indigo-600',
253
+ 'cosy:bg-gradient-to-br cosy:from-blue-400 cosy:to-violet-400 dark:cosy:from-blue-600 dark:cosy:to-violet-600',
254
+
255
+ // 纯色背景
256
+ 'cosy:bg-emerald-400 dark:cosy:bg-emerald-600',
257
+ 'cosy:bg-violet-400 dark:cosy:bg-violet-600',
258
+ 'cosy:bg-amber-400 dark:cosy:bg-amber-600',
259
+ 'cosy:bg-rose-400 dark:cosy:bg-rose-600',
260
+ 'cosy:bg-sky-400 dark:cosy:bg-sky-600',
261
+ 'cosy:bg-lime-400 dark:cosy:bg-lime-600',
262
+ 'cosy:bg-purple-400 dark:cosy:bg-purple-600',
263
+ 'cosy:bg-blue-400 dark:cosy:bg-blue-600'
264
+ ];
265
+
266
+ const getBackgroundClass = (): string => {
267
+ return bgClasses[selectedBgIndex.value % bgClasses.length];
268
+ }
269
+
270
+ const clearStoredSize = () => {
271
+ localStorage.removeItem('bannerBoxSize');
272
+ // 触发自定义事件
273
+ window.dispatchEvent(new CustomEvent('bannerBoxClear'));
274
+ isDropdownOpen.value = false;
275
+ };
276
+
277
+ // 确保在组件卸载时清理事件监听器
278
+ onUnmounted(() => {
279
+ window.removeEventListener('bannerBoxClear', handleSizeClear);
280
+ window.removeEventListener('bannerBoxSizeChange', handleSizeChange);
281
+ });
282
+
283
+ // 是否显示Banner内容
284
+ const showBannerContent = computed(() => props.title !== '' || props.description !== '' || props.features.length > 0 || props.customComponent !== null);
285
+
286
+ // 计算下载按钮是否显示及其样式类
287
+ const downloadButtonStyles = computed(() => {
288
+ switch (props.displayMode) {
289
+ case 'always':
290
+ return {
291
+ show: true,
292
+ classes: 'cosy:opacity-100'
293
+ };
294
+ case 'hover':
295
+ return {
296
+ show: true,
297
+ classes: 'cosy:opacity-0 cosy:hover:opacity-100 cosy:transition-opacity'
298
+ };
299
+ case 'never':
300
+ return {
301
+ show: false,
302
+ classes: ''
303
+ };
304
+ default:
305
+ return {
306
+ show: true,
307
+ classes: 'cosy:opacity-0 cosy:hover:opacity-100 cosy:transition-opacity'
308
+ };
309
+ }
310
+ });
311
+
312
+ return {
313
+ componentRef,
314
+ isDropdownOpen,
315
+ isLoadedFromStorage,
316
+ selectedSize,
317
+ sizePresets,
318
+ selectedBgIndex,
319
+ toggleDropdown,
320
+ downloadAsImage,
321
+ getBackgroundClass,
322
+ clearStoredSize,
323
+ showBannerContent,
324
+ downloadButtonStyles
325
+ };
326
+ }
327
+ });
328
+ </script>
329
+
330
+ <template>
331
+ <div class="cosy:relative cosy:w-full cosy:rounded-2xl cosy:max-w-7xl cosy:mx-auto">
332
+ <!-- Add size indicator -->
333
+ <div v-if="isLoadedFromStorage"
334
+ class="cosy:absolute cosy:top-4 cosy:right-4 cosy:bg-yellow-500/30 cosy:backdrop-blur-sm cosy:px-3 cosy:py-1 cosy:rounded-lg cosy:text-sm cosy:text-white">
335
+ {{ selectedSize.name }}
336
+ </div>
337
+
338
+ <!-- Download button with dropdown menu -->
339
+ <div v-if="downloadButtonStyles.show" class="cosy:absolute cosy:top-4 cosy:left-4"
340
+ :class="downloadButtonStyles.classes">
341
+ <div class="cosy:relative" data-dropdown>
342
+ <button
343
+ class="cosy:bg-yellow-500/30 cosy:backdrop-blur-sm cosy:p-2 cosy:rounded-lg hover:cosy:bg-yellow-500/40"
344
+ @click="toggleDropdown">
345
+ <RiDownloadLine class="cosy:w-6 cosy:h-6 cosy:text-white" />
346
+ </button>
347
+ <!-- Size selection dropdown -->
348
+ <div v-if="isDropdownOpen"
349
+ class="cosy:absolute cosy:left-0 cosy:mt-2 cosy:w-96 cosy:bg-white dark:cosy:bg-gray-800 cosy:rounded-lg cosy:shadow-lg cosy:py-2 cosy:z-50">
350
+ <!-- Component size presets -->
351
+ <div class="cosy:px-4 cosy:py-2 cosy:border-b cosy:border-gray-200 dark:cosy:border-gray-700">
352
+ <div class="cosy:grid cosy:grid-cols-3 cosy:gap-2">
353
+ <button v-for="preset in sizePresets" :key="preset.name" :class="[
354
+ 'cosy:p-2 cosy:text-left cosy:rounded cosy:text-sm',
355
+ selectedSize.name === preset.name
356
+ ? 'cosy:bg-yellow-500/30 cosy:text-yellow-900 dark:cosy:text-yellow-100'
357
+ : 'hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700'
358
+ ]" @click="selectedSize = preset">
359
+ <div class="cosy:flex cosy:flex-col">
360
+ <span class="cosy:font-medium">{{ preset.name }}</span>
361
+ <span class="cosy:text-xs cosy:text-gray-500 dark:cosy:text-gray-400">
362
+ {{ preset.width.replace('cosy:w-[', '').replace(']', '') }}
363
+ </span>
364
+ </div>
365
+ </button>
366
+ <!-- Clear size button -->
367
+ <button
368
+ class="cosy:p-2 cosy:text-left cosy:rounded cosy:text-sm hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700"
369
+ @click="clearStoredSize">
370
+ <div class="cosy:flex cosy:flex-col">
371
+ <span
372
+ class="cosy:font-medium cosy:text-red-600 dark:cosy:text-red-400">清除记住的尺寸</span>
373
+ <span class="cosy:text-xs cosy:text-gray-500 dark:cosy:text-gray-400">重置为默认尺寸</span>
374
+ </div>
375
+ </button>
376
+ </div>
377
+ </div>
378
+ <!-- Background settings -->
379
+ <div class="cosy:px-4 cosy:py-2 cosy:border-b cosy:border-gray-200 dark:cosy:border-gray-700">
380
+ <div class="cosy:mt-2">
381
+ <div class="cosy:grid cosy:grid-cols-8 cosy:gap-2">
382
+ <button v-for="(_, index) in bgClasses" :key="index" :class="[
383
+ bgClasses[index],
384
+ 'cosy:w-8 cosy:h-8 cosy:rounded-lg cosy:border-2',
385
+ selectedBgIndex === index ? 'cosy:border-yellow-500' : 'cosy:border-transparent'
386
+ ]" @click="selectedBgIndex = index" />
387
+ </div>
388
+ </div>
389
+ </div>
390
+ <!-- Size options -->
391
+ <div class="cosy:p-4">
392
+ <button
393
+ class="cosy:w-full cosy:p-2 cosy:text-center cosy:rounded hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700"
394
+ @click="downloadAsImage()">
395
+ <div class="cosy:flex cosy:items-center cosy:justify-center cosy:gap-2">
396
+ <RiDownloadLine class="cosy:w-4 cosy:h-4" />
397
+ <span class="cosy:font-medium">下载图片</span>
398
+ </div>
399
+ </button>
400
+ </div>
401
+ </div>
402
+ </div>
403
+ </div>
404
+
405
+ <div ref="componentRef" class="cosy:flex cosy:p-8 cosy:rounded-2xl cosy:shadow" :class="[
406
+ getBackgroundClass(),
407
+ selectedSize.width,
408
+ selectedSize.height
409
+ ]">
410
+ <!-- Smart Banner Content (when banner prop is provided) -->
411
+ <div v-if="showBannerContent" class="cosy:py-16 cosy:px-8 cosy:text-center cosy:w-full cosy:rounded-2xl"
412
+ data-type="smart-banner">
413
+ <h2 class="cosy:text-4xl cosy:mb-4">
414
+ {{ title }}
415
+ </h2>
416
+
417
+ <p v-if="description.length > 0" class="cosy:text-lg cosy:text-center cosy:max-w-2xl cosy:mx-auto">
418
+ {{ description }}
419
+ </p>
420
+
421
+ <div class="cosy:flex cosy:flex-row cosy:justify-center cosy:gap-8 cosy:mx-auto cosy:w-full cosy:mt-24">
422
+ <FeatureCard v-for="feature in features" :key="feature.title" :emoji="feature.emoji"
423
+ :title="feature.title" :link="feature.link" />
424
+ </div>
425
+
426
+ <div class="cosy:mt-12">
427
+ <component :is="customComponent" v-if="customComponent" v-bind="customComponentProps" />
428
+ </div>
429
+ </div>
430
+
431
+ <!-- Default slot for custom content (when banner prop is not provided) -->
432
+ <slot v-if="!showBannerContent" />
433
+ </div>
434
+ </div>
435
+ </template>
@@ -0,0 +1,32 @@
1
+ <!--
2
+ @component BannerBox.Basic
3
+
4
+ @description
5
+ BannerBox 组件的基础示例,展示最基本的横幅容器用法。
6
+
7
+ @usage
8
+ ```vue
9
+ <BannerBoxExamples.Basic />
10
+ ```
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import '../../app.css'
15
+ import { defineComponent } from 'vue'
16
+ import BannerBox from './BannerBox.vue'
17
+
18
+ export default defineComponent({
19
+ name: 'BannerBoxBasicExample',
20
+ components: {
21
+ BannerBox
22
+ }
23
+ })
24
+ </script>
25
+
26
+ <template>
27
+ <BannerBox>
28
+ <div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[200px]">
29
+ <h2 class="cosy:text-3xl cosy:font-bold">欢迎使用 BannerBox</h2>
30
+ </div>
31
+ </BannerBox>
32
+ </template>
@@ -0,0 +1,32 @@
1
+ <!--
2
+ @component BannerBox.CustomBg
3
+
4
+ @description
5
+ BannerBox 组件的自定义背景示例,展示如何通过 backgroundClassIndex 属性设置背景样式。
6
+
7
+ @usage
8
+ ```vue
9
+ <BannerBoxExamples.CustomBg />
10
+ ```
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import '../../app.css'
15
+ import { defineComponent } from 'vue'
16
+ import BannerBox from './BannerBox.vue'
17
+
18
+ export default defineComponent({
19
+ name: 'BannerBoxCustomBgExample',
20
+ components: {
21
+ BannerBox
22
+ }
23
+ })
24
+ </script>
25
+
26
+ <template>
27
+ <BannerBox :backgroundClassIndex="5">
28
+ <div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[200px]">
29
+ <h2 class="cosy:text-3xl cosy:font-bold">自定义背景颜色</h2>
30
+ </div>
31
+ </BannerBox>
32
+ </template>
@@ -0,0 +1,60 @@
1
+ <!--
2
+ @component BannerBox.CustomComponent
3
+
4
+ @description
5
+ BannerBox 组件的自定义组件集成示例,展示如何在横幅中插入其他UI组件。
6
+
7
+ @usage
8
+ ```vue
9
+ <BannerBoxExamples.CustomComponent />
10
+ ```
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import '../../app.css'
15
+ import { defineComponent } from 'vue'
16
+ import BannerBox from './BannerBox.vue'
17
+
18
+ export default defineComponent({
19
+ name: 'BannerBoxCustomComponentExample',
20
+ components: {
21
+ BannerBox
22
+ },
23
+ setup() {
24
+ // 模拟按钮组件
25
+ const Button = {
26
+ props: ['text', 'variant', 'size'],
27
+ template: `
28
+ <button
29
+ class="cosy:btn"
30
+ :class="[
31
+ variant ? 'cosy:btn-' + variant : '',
32
+ size ? 'cosy:btn-' + size : ''
33
+ ]"
34
+ @click="$emit('click')"
35
+ >
36
+ {{ text }}
37
+ </button>
38
+ `
39
+ };
40
+
41
+ return {
42
+ Button
43
+ };
44
+ }
45
+ })
46
+ </script>
47
+
48
+ <template>
49
+ <BannerBox title="带自定义组件的横幅" description="在横幅中集成其他UI组件" :features="[
50
+ {
51
+ emoji: '🎨',
52
+ title: '可定制UI',
53
+ link: '#customize'
54
+ }
55
+ ]" :customComponent="Button" :customComponentProps="{
56
+ text: '立即注册',
57
+ variant: 'primary',
58
+ size: 'lg'
59
+ }" />
60
+ </template>
@@ -0,0 +1,49 @@
1
+ <!--
2
+ @component BannerBox.DisplayMode
3
+
4
+ @description
5
+ BannerBox 组件的显示模式示例,展示如何通过 displayMode 属性控制下载按钮的显示方式。
6
+
7
+ @usage
8
+ ```vue
9
+ <BannerBoxExamples.DisplayMode />
10
+ ```
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import '../../app.css'
15
+ import { defineComponent } from 'vue'
16
+ import BannerBox from './BannerBox.vue'
17
+
18
+ export default defineComponent({
19
+ name: 'BannerBoxDisplayModeExample',
20
+ components: {
21
+ BannerBox
22
+ }
23
+ })
24
+ </script>
25
+
26
+ <template>
27
+ <div class="cosy:space-y-4">
28
+ <h3 class="cosy:text-lg cosy:font-medium">总是显示下载按钮</h3>
29
+ <BannerBox displayMode="always">
30
+ <div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[100px]">
31
+ <p>下载按钮始终可见</p>
32
+ </div>
33
+ </BannerBox>
34
+
35
+ <h3 class="cosy:text-lg cosy:font-medium">悬停时显示下载按钮(默认)</h3>
36
+ <BannerBox displayMode="hover">
37
+ <div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[100px]">
38
+ <p>鼠标悬停时显示下载按钮</p>
39
+ </div>
40
+ </BannerBox>
41
+
42
+ <h3 class="cosy:text-lg cosy:font-medium">隐藏下载按钮</h3>
43
+ <BannerBox displayMode="never">
44
+ <div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[100px]">
45
+ <p>不显示下载按钮</p>
46
+ </div>
47
+ </BannerBox>
48
+ </div>
49
+ </template>