@coffic/cosy-ui 0.8.26 → 0.8.28

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 (31) hide show
  1. package/dist/app.css +1 -1
  2. package/dist/index-astro.ts +1 -0
  3. package/dist/index-vue.ts +18 -0
  4. package/dist/src-astro/badge/Badge.astro +67 -0
  5. package/dist/src-astro/badge/index.ts +1 -0
  6. package/dist/src-astro/code-container/CodeContainer.astro +4 -2
  7. package/dist/src-astro/team-member/TeamMember.astro +36 -4
  8. package/dist/src-astro/team-member/index.ts +3 -26
  9. package/dist/src-vue/badge/Badge.vue +66 -0
  10. package/dist/src-vue/badge/index.ts +1 -0
  11. package/dist/src-vue/buttons/ButtonFolder.vue +98 -0
  12. package/dist/src-vue/buttons/ButtonRefresh.vue +163 -0
  13. package/dist/src-vue/buttons/index.ts +2 -0
  14. package/dist/src-vue/card/Card.vue +52 -0
  15. package/dist/src-vue/card/CardCourse.vue +34 -0
  16. package/dist/src-vue/card/index.ts +2 -0
  17. package/dist/src-vue/key-catcher/KeyCatcher.vue +121 -0
  18. package/dist/src-vue/key-catcher/index.ts +1 -0
  19. package/dist/src-vue/progress/Progress.vue +68 -0
  20. package/dist/src-vue/progress/index.ts +1 -0
  21. package/dist/src-vue/status-bar/StatusBar.vue +101 -0
  22. package/dist/src-vue/status-bar/StatusBarItem.vue +97 -0
  23. package/dist/src-vue/status-bar/index.ts +2 -0
  24. package/dist/src-vue/tool-bar/ToolBar.vue +95 -0
  25. package/dist/src-vue/tool-bar/index.ts +1 -0
  26. package/package.json +1 -1
  27. package/dist/src-astro/code-container/CodePanel.astro +0 -14
  28. package/dist/src-astro/team-member/TeamMemberBasic.astro +0 -25
  29. package/dist/src-astro/team-member/TeamMemberCustomStyle.astro +0 -26
  30. package/dist/src-astro/team-member/TeamMemberGroup.astro +0 -60
  31. package/dist/src-astro/team-member/TeamMemberWithSocial.astro +0 -30
@@ -2,6 +2,7 @@
2
2
  export * from './src-astro/alert';
3
3
  export * from './src-astro/article';
4
4
  export * from './src-astro/banner';
5
+ export * from './src-astro/badge';
5
6
  export * from './src-astro/button/index_astro';
6
7
  export * from './src-astro/card';
7
8
  export * from './src-astro/code-block';
package/dist/index-vue.ts CHANGED
@@ -4,12 +4,18 @@ export * from './src-vue/alert-dialog/index';
4
4
  // Alert
5
5
  export * from './src-vue/alert/index';
6
6
 
7
+ // Badge
8
+ export * from './src-vue/badge/index';
9
+
7
10
  // Banner
8
11
  export * from './src-vue/banner-box/index';
9
12
 
10
13
  // BlogList
11
14
  export * from './src-vue/blog/index';
12
15
 
16
+ // Card
17
+ export * from './src-vue/card/index';
18
+
13
19
  // Container
14
20
  export * from './src-vue/container/index';
15
21
 
@@ -25,9 +31,21 @@ export * from './src-vue/buttons/index';
25
31
  // Icons
26
32
  export * from './src-vue/icons/index';
27
33
 
34
+ // KeyCatcher
35
+ export * from './src-vue/key-catcher/index';
36
+
28
37
  // List
29
38
  export * from './src-vue/list/index';
30
39
 
40
+ // Progress
41
+ export * from './src-vue/progress/index';
42
+
43
+ // StatusBar
44
+ export * from './src-vue/status-bar/index';
45
+
46
+ // ToolBar
47
+ export * from './src-vue/tool-bar/index';
48
+
31
49
  // Windows
32
50
  export * from './src-vue/mac-window/index';
33
51
  export * from './src-vue/iPhone/index';
@@ -0,0 +1,67 @@
1
+ ---
2
+ /**
3
+ * @component Badge
4
+ * @description 一个用于高亮信息的小组件。
5
+ * @usage
6
+ * ```astro
7
+ * <Badge>默认</Badge>
8
+ * <Badge variant="primary">Primary</Badge>
9
+ * ```
10
+ * @props
11
+ * @prop {('primary'|'secondary'|'accent'|'ghost'|'info'|'success'|'warning'|'error')} [variant] - 徽章的颜色变体。
12
+ * @prop {('xs'|'sm'|'md'|'lg')} [size] - 徽章的尺寸。
13
+ * @prop {boolean} [outline=false] - 徽章是否为描边样式。
14
+ * @prop {string} [class] - 自定义 CSS 类。
15
+ * @slots
16
+ * @slot default - 徽章内容。
17
+ */
18
+ import '../../style.ts';
19
+
20
+ interface Props {
21
+ variant?:
22
+ | 'primary'
23
+ | 'secondary'
24
+ | 'accent'
25
+ | 'ghost'
26
+ | 'info'
27
+ | 'success'
28
+ | 'warning'
29
+ | 'error';
30
+ size?: 'xs' | 'sm' | 'md' | 'lg';
31
+ outline?: boolean;
32
+ class?: string;
33
+ }
34
+
35
+ const { variant, size, outline = false, class: className } = Astro.props;
36
+
37
+ function getVariantClass(variant: Props['variant']) {
38
+ if (variant === 'primary') return 'cosy:badge-primary';
39
+ if (variant === 'secondary') return 'cosy:badge-secondary';
40
+ if (variant === 'accent') return 'cosy:badge-accent';
41
+ if (variant === 'ghost') return 'cosy:badge-ghost';
42
+ if (variant === 'info') return 'cosy:badge-info';
43
+ if (variant === 'success') return 'cosy:badge-success';
44
+ if (variant === 'warning') return 'cosy:badge-warning';
45
+ if (variant === 'error') return 'cosy:badge-error';
46
+ return '';
47
+ }
48
+
49
+ function getSizeClass(size: Props['size']) {
50
+ if (size === 'xs') return 'cosy:badge-xs';
51
+ if (size === 'sm') return 'cosy:badge-sm';
52
+ if (size === 'md') return 'cosy:badge-md';
53
+ if (size === 'lg') return 'cosy:badge-lg';
54
+ return '';
55
+ }
56
+ ---
57
+
58
+ <div
59
+ class:list={[
60
+ 'cosy:badge',
61
+ getVariantClass(variant),
62
+ getSizeClass(size),
63
+ outline ? 'cosy:badge-outline' : '',
64
+ className,
65
+ ]}>
66
+ <slot />
67
+ </div>
@@ -0,0 +1 @@
1
+ export { default as Badge } from './Badge.astro';
@@ -37,7 +37,7 @@
37
37
  import CodeToolbar from './CodeToolbar.astro';
38
38
  import Description from './Description.astro';
39
39
  import Preview from './Preview.astro';
40
- import CodePanel from './CodePanel.astro';
40
+ import { CodePanel } from '../../index-astro';
41
41
  import '../../style.ts';
42
42
 
43
43
  interface Props {
@@ -92,7 +92,9 @@ const {
92
92
  </div>
93
93
  </Preview>
94
94
 
95
- <CodePanel code={code} />
95
+ <div class="cosy:hidden cosy:code-example-panel" data-panel="code">
96
+ <CodePanel code={code} />
97
+ </div>
96
98
  </div>
97
99
  </div>
98
100
  ))
@@ -19,7 +19,7 @@
19
19
  * <TeamMember
20
20
  * name="张三"
21
21
  * role="前端开发工程师"
22
- * avatar="/images/avatars/zhangsan.jpg"
22
+ * avatar="/images/avatars/jack.jpg"
23
23
  * bio="拥有5年前端开发经验,专注于React和Vue生态系统。"
24
24
  * />
25
25
  * ```
@@ -29,7 +29,7 @@
29
29
  * <TeamMember
30
30
  * name="李四"
31
31
  * role="UI设计师"
32
- * avatar="/images/avatars/lisi.jpg"
32
+ * avatar="/images/avatars/mary.jpg"
33
33
  * bio="专注于用户体验和界面设计,热爱创造直观易用的产品。"
34
34
  * socialLinks={[
35
35
  * { platform: 'github', url: 'https://github.com/lisi' },
@@ -80,6 +80,16 @@ export interface Props {
80
80
  * 自定义类名
81
81
  */
82
82
  class?: string;
83
+ /**
84
+ * 卡片阴影强度
85
+ * @default 'md'
86
+ */
87
+ shadow?: 'sm' | 'md' | 'lg' | 'xl';
88
+ /**
89
+ * 悬停时卡片阴影强度
90
+ * @default 'lg'
91
+ */
92
+ hoverShadow?: 'sm' | 'md' | 'lg' | 'xl';
83
93
  }
84
94
 
85
95
  const {
@@ -89,13 +99,35 @@ const {
89
99
  bio,
90
100
  socialLinks,
91
101
  class: className = '',
102
+ shadow = 'md',
103
+ hoverShadow = 'lg',
92
104
  } = Astro.props;
105
+
106
+ const shadowMap = {
107
+ sm: 'cosy:shadow-sm',
108
+ md: 'cosy:shadow-md',
109
+ lg: 'cosy:shadow-lg',
110
+ xl: 'cosy:shadow-xl',
111
+ };
112
+ const hoverShadowMap = {
113
+ sm: 'cosy:hover:shadow-sm',
114
+ md: 'cosy:hover:shadow-md',
115
+ lg: 'cosy:hover:shadow-lg',
116
+ xl: 'cosy:hover:shadow-xl',
117
+ };
118
+
119
+ const shadowClass = shadowMap[shadow] || shadowMap['md'];
120
+ const hoverShadowClass = hoverShadowMap[hoverShadow] || hoverShadowMap['lg'];
93
121
  ---
94
122
 
95
123
  <div
96
124
  class:list={[
97
- 'cosy:card cosy:bg-base-100 cosy:shadow-md cosy:hover:shadow-lg cosy:transition-shadow cosy:duration-300',
98
- className,
125
+ [
126
+ 'cosy:card cosy:bg-base-100 cosy:transition-shadow cosy:duration-300',
127
+ shadowClass,
128
+ hoverShadowClass,
129
+ className,
130
+ ],
99
131
  ]}>
100
132
  <figure class="cosy:flex cosy:justify-center cosy:p-4">
101
133
  <Image
@@ -1,30 +1,7 @@
1
1
  import TeamMember from './TeamMember.astro';
2
- import TeamMemberBasic from './TeamMemberBasic.astro';
3
- import TeamMemberWithSocial from './TeamMemberWithSocial.astro';
4
- import TeamMemberCustomStyle from './TeamMemberCustomStyle.astro';
5
- import TeamMemberGroup from './TeamMemberGroup.astro';
6
- import TeamMemebrs from './TeamMembers.astro';
7
-
8
- import BasicSourceCode from './TeamMemberBasic.astro?raw';
9
- import WithSocialSourceCode from './TeamMemberWithSocial.astro?raw';
10
- import CustomStyleSourceCode from './TeamMemberCustomStyle.astro?raw';
11
- import GroupSourceCode from './TeamMemberGroup.astro?raw';
12
-
13
- import { extractSimpleExample } from '../../src/utils/component';
2
+ import TeamMembers from './TeamMembers.astro';
14
3
 
15
4
  export {
16
- TeamMember,
17
- TeamMemberBasic,
18
- TeamMemberWithSocial,
19
- TeamMemberCustomStyle,
20
- TeamMemberGroup,
21
- TeamMemebrs,
22
- };
23
-
24
- // Export example codes
25
- export const TeamMemberExampleCodes = {
26
- Basic: extractSimpleExample(BasicSourceCode, 'TeamMember'),
27
- WithSocial: extractSimpleExample(WithSocialSourceCode, 'TeamMember'),
28
- CustomStyle: extractSimpleExample(CustomStyleSourceCode, 'TeamMember'),
29
- Group: extractSimpleExample(GroupSourceCode, 'TeamMember'),
5
+ TeamMember,
6
+ TeamMembers,
30
7
  };
@@ -0,0 +1,66 @@
1
+ <!--
2
+ @component Badge
3
+ @description 一个用于高亮信息的小组件。
4
+ @usage
5
+ ```vue
6
+ <Badge>默认</Badge>
7
+ <Badge variant="primary">Primary</Badge>
8
+ ```
9
+ @props
10
+ @prop {('primary'|'secondary'|'accent'|'ghost'|'info'|'success'|'warning'|'error')} [variant] - 徽章的颜色变体。
11
+ @prop {('xs'|'sm'|'md'|'lg')} [size] - 徽章的尺寸。
12
+ @prop {boolean} [outline=false] - 徽章是否为描边样式。
13
+ @prop {string} [class] - 自定义 CSS 类。
14
+ @slots
15
+ @slot default - 徽章内容。
16
+ -->
17
+ <script setup lang="ts">
18
+ import '../../style';
19
+ import { computed } from 'vue';
20
+
21
+ const props = defineProps({
22
+ variant: {
23
+ type: String,
24
+ default: undefined,
25
+ validator: (v: string) => ['primary', 'secondary', 'accent', 'ghost', 'info', 'success', 'warning', 'error'].includes(v)
26
+ },
27
+ size: {
28
+ type: String,
29
+ default: undefined,
30
+ validator: (v: string) => ['xs', 'sm', 'md', 'lg'].includes(v)
31
+ },
32
+ outline: Boolean,
33
+ class: String
34
+ });
35
+
36
+ const variantClass = computed(() => {
37
+ if (props.variant === 'primary') return 'cosy:badge-primary';
38
+ if (props.variant === 'secondary') return 'cosy:badge-secondary';
39
+ if (props.variant === 'accent') return 'cosy:badge-accent';
40
+ if (props.variant === 'ghost') return 'cosy:badge-ghost';
41
+ if (props.variant === 'info') return 'cosy:badge-info';
42
+ if (props.variant === 'success') return 'cosy:badge-success';
43
+ if (props.variant === 'warning') return 'cosy:badge-warning';
44
+ if (props.variant === 'error') return 'cosy:badge-error';
45
+ return '';
46
+ });
47
+ const sizeClass = computed(() => {
48
+ if (props.size === 'xs') return 'cosy:badge-xs';
49
+ if (props.size === 'sm') return 'cosy:badge-sm';
50
+ if (props.size === 'md') return 'cosy:badge-md';
51
+ if (props.size === 'lg') return 'cosy:badge-lg';
52
+ return '';
53
+ });
54
+ </script>
55
+
56
+ <template>
57
+ <span :class="[
58
+ 'cosy:badge',
59
+ variantClass,
60
+ sizeClass,
61
+ props.outline ? 'cosy:badge-outline' : '',
62
+ props.class
63
+ ]">
64
+ <slot />
65
+ </span>
66
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Badge } from './Badge.vue';
@@ -0,0 +1,98 @@
1
+ <!--
2
+ ButtonFolder 组件
3
+
4
+ 一个专门用于文件夹操作的按钮组件,封装了 Button 组件,并添加了文件夹图标。
5
+
6
+ 使用示例:
7
+ ```vue
8
+ <ButtonFolder @click="openFolder" />
9
+ <ButtonFolder style="ghost" size="sm" @click="openFolder" tooltip="打开文件夹" />
10
+ ```
11
+
12
+ 属性:
13
+ - 继承 Button 组件的所有属性
14
+ - 默认使用 ghost 样式
15
+
16
+ 事件:
17
+ - click: 点击按钮时触发(disabled状态下不触发)
18
+ -->
19
+
20
+ <script setup lang="ts">
21
+ import { computed } from 'vue';
22
+ import { Button } from '@coffic/cosy-ui/vue';
23
+ import { RiFolderOpenLine } from '@remixicon/vue';
24
+
25
+ interface Props {
26
+ // 按钮颜色
27
+ color?:
28
+ | 'neutral'
29
+ | 'primary'
30
+ | 'secondary'
31
+ | 'accent'
32
+ | 'info'
33
+ | 'success'
34
+ | 'warning'
35
+ | 'error';
36
+ // 按钮样式
37
+ style?: 'outline' | 'dash' | 'soft' | 'ghost' | 'link';
38
+ // 按钮大小
39
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
40
+ // 按钮形状
41
+ shape?: 'square' | 'circle';
42
+ // 是否禁用
43
+ disabled?: boolean;
44
+ // 是否激活
45
+ active?: boolean;
46
+ // 提示文本
47
+ tooltip?: string;
48
+ }
49
+
50
+ const props = withDefaults(defineProps<Props>(), {
51
+ style: 'ghost',
52
+ size: 'md',
53
+ disabled: false,
54
+ active: false,
55
+ });
56
+
57
+ const emit = defineEmits<{
58
+ (e: 'click', event: MouseEvent): void;
59
+ }>();
60
+
61
+ // 处理点击事件
62
+ const handleClick = (event: MouseEvent) => {
63
+ // 如果按钮已经处于禁用状态,不触发事件
64
+ if (props.disabled) return;
65
+
66
+ // 触发点击事件
67
+ emit('click', event);
68
+ };
69
+
70
+ // 计算图标大小类名
71
+ const iconSizeClass = computed(() => {
72
+ if (props.size === 'xs' || props.size === 'sm') return 'cosy:w-4 cosy:h-4';
73
+ if (props.size === 'lg' || props.size === 'xl') return 'cosy:w-6 cosy:h-6';
74
+ return 'cosy:w-5 cosy:h-5'; // 默认中等大小
75
+ });
76
+ </script>
77
+
78
+ <template>
79
+ <div
80
+ class="cosy:relative cosy:inline-block"
81
+ :data-tip="tooltip"
82
+ :class="{ tooltip: tooltip }"
83
+ >
84
+ <Button
85
+ :color="color"
86
+ :style="style"
87
+ :shape="shape"
88
+ :disabled="disabled"
89
+ :active="active"
90
+ @click="handleClick"
91
+ >
92
+ <RiFolderOpenLine
93
+ :class="[iconSizeClass, 'cosy:transition-all cosy:duration-300']"
94
+ />
95
+ <slot></slot>
96
+ </Button>
97
+ </div>
98
+ </template>
@@ -0,0 +1,163 @@
1
+ <!--
2
+ ButtonRefresh 组件
3
+
4
+ 一个专门用于刷新操作的按钮组件,封装了 Button 组件,并添加了动画效果。
5
+ 当处于加载状态时,图标会旋转,提供视觉反馈。
6
+
7
+ 使用示例:
8
+ ```vue
9
+ <ButtonRefresh @click="refreshData" :loading="isLoading" />
10
+ <ButtonRefresh style="ghost" size="sm" @click="refreshData" />
11
+ ```
12
+
13
+ 属性:
14
+ - 继承 Button 组件的所有属性
15
+ - 默认使用 ghost 样式
16
+
17
+ 事件:
18
+ - click: 点击按钮时触发(disabled或loading状态下不触发)
19
+ -->
20
+
21
+ <script setup lang="ts">
22
+ import { computed, ref, watch } from 'vue';
23
+ import { Button } from '@coffic/cosy-ui/vue';
24
+ import { RiRefreshLine } from '@remixicon/vue';
25
+
26
+ interface Props {
27
+ // 按钮颜色
28
+ color?:
29
+ | 'neutral'
30
+ | 'primary'
31
+ | 'secondary'
32
+ | 'accent'
33
+ | 'info'
34
+ | 'success'
35
+ | 'warning'
36
+ | 'error';
37
+ // 按钮样式
38
+ style?: 'outline' | 'dash' | 'soft' | 'ghost' | 'link';
39
+ // 按钮大小
40
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
41
+ // 按钮形状
42
+ shape?: 'square' | 'circle';
43
+ // 是否显示加载状态
44
+ loading?: boolean;
45
+ // 是否禁用
46
+ disabled?: boolean;
47
+ // 是否激活
48
+ active?: boolean;
49
+ // 提示文本
50
+ tooltip?: string;
51
+ // 最小动画时间(毫秒)
52
+ minAnimationTime?: number;
53
+ }
54
+
55
+ const props = withDefaults(defineProps<Props>(), {
56
+ style: 'ghost',
57
+ size: 'md',
58
+ loading: false,
59
+ disabled: false,
60
+ active: false,
61
+ minAnimationTime: 800, // 默认最小动画时间为 800 毫秒
62
+ });
63
+
64
+ const emit = defineEmits<{
65
+ (e: 'click', event: MouseEvent): void;
66
+ }>();
67
+
68
+ // 处理点击事件
69
+ const handleClick = async (event: MouseEvent) => {
70
+ // 如果按钮已经处于加载或禁用状态,不触发事件
71
+ if (props.disabled || internalLoading.value) return;
72
+
73
+ // 触发点击事件
74
+ emit('click', event);
75
+ };
76
+
77
+ // 内部加载状态
78
+ const internalLoading = ref(false);
79
+
80
+ // 记录动画开始时间
81
+ const animationStartTime = ref(0);
82
+
83
+ // 监听外部加载状态
84
+ watch(
85
+ () => props.loading,
86
+ (newVal, oldVal) => {
87
+ if (newVal === true) {
88
+ // 开始加载,记录当前时间
89
+ animationStartTime.value = Date.now();
90
+ internalLoading.value = true;
91
+ } else if (oldVal === true && newVal === false) {
92
+ // 加载结束,计算已经经过的时间
93
+ const elapsedTime = Date.now() - animationStartTime.value;
94
+ const remainingTime = Math.max(0, props.minAnimationTime - elapsedTime);
95
+
96
+ if (remainingTime > 0) {
97
+ // 如果还没有达到最小动画时间,延迟关闭加载状态
98
+ setTimeout(() => {
99
+ internalLoading.value = false;
100
+ }, remainingTime);
101
+ } else {
102
+ // 已经超过最小动画时间,直接关闭
103
+ internalLoading.value = false;
104
+ }
105
+ }
106
+ },
107
+ { immediate: true }
108
+ );
109
+
110
+ // 计算内容类名
111
+ const contentClass = computed(() => {
112
+ return {
113
+ hidden: internalLoading.value,
114
+ };
115
+ });
116
+
117
+ // 计算图标大小类名
118
+ const iconSizeClass = computed(() => {
119
+ if (props.size === 'xs' || props.size === 'sm') return 'cosy:w-4 cosy:h-4';
120
+ if (props.size === 'lg' || props.size === 'xl') return 'cosy:w-6 cosy:h-6';
121
+ return 'cosy:w-5 cosy:h-5'; // 默认中等大小
122
+ });
123
+
124
+ // 计算加载器类名
125
+ const loadingClass = computed(() => {
126
+ return [
127
+ 'loading',
128
+ 'loading-spinner',
129
+ props.size === 'xs' || props.size === 'sm'
130
+ ? 'loading-xs'
131
+ : props.size === 'lg' || props.size === 'xl'
132
+ ? 'loading-lg'
133
+ : 'loading-md',
134
+ iconSizeClass.value, // 使用相同的大小类
135
+ { hidden: !internalLoading.value },
136
+ ];
137
+ });
138
+ </script>
139
+
140
+ <template>
141
+ <div
142
+ class="cosy:relative cosy:inline-block"
143
+ :data-tip="tooltip"
144
+ :class="{ tooltip: tooltip }"
145
+ >
146
+ <Button
147
+ :color="color"
148
+ :style="style"
149
+ :shape="shape"
150
+ :disabled="disabled || internalLoading"
151
+ :active="active"
152
+ @click="handleClick"
153
+ >
154
+ <span :class="loadingClass"></span>
155
+ <span :class="contentClass">
156
+ <RiRefreshLine
157
+ :class="[iconSizeClass, 'cosy:transition-all cosy:duration-300']"
158
+ />
159
+ <slot></slot>
160
+ </span>
161
+ </Button>
162
+ </div>
163
+ </template>
@@ -1 +1,3 @@
1
1
  export { default as Button } from './Button.vue';
2
+ export { default as ButtonFolder } from './ButtonFolder.vue';
3
+ export { default as ButtonRefresh } from './ButtonRefresh.vue';
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ defineOptions({
5
+ name: 'Card',
6
+ });
7
+
8
+ const props = defineProps({
9
+ title: { type: String, required: true },
10
+ subtitle: String,
11
+ imageUrl: String,
12
+ href: String,
13
+ compact: Boolean,
14
+ class: String,
15
+ });
16
+
17
+ const cardClasses = computed(() => [
18
+ 'cosy:card',
19
+ 'cosy:w-full',
20
+ 'cosy:bg-base-100',
21
+ 'cosy:shadow-xl',
22
+ 'cosy:hover:shadow-2xl',
23
+ 'cosy:transition-all',
24
+ 'cosy:duration-300',
25
+ 'cosy:ease-in-out',
26
+ props.compact ? 'cosy:card-compact' : '',
27
+ props.href ? 'cosy:cursor-pointer cosy:hover:scale-105 cosy:transform cosy:no-underline' : '',
28
+ props.class,
29
+ ].filter(Boolean).join(' '));
30
+
31
+ const contentPadding = computed(() => props.compact ? 'cosy:p-4' : 'cosy:p-6');
32
+ </script>
33
+
34
+ <template>
35
+ <component :is="props.href ? 'a' : 'article'" :href="props.href" :class="cardClasses">
36
+ <template v-if="props.imageUrl">
37
+ <figure class="not-prose cosy:m-0 cosy:p-0">
38
+ <img :src="props.imageUrl" :alt="props.title"
39
+ class="cosy:w-full cosy:h-48 cosy:object-cover cosy:rounded-t-lg" />
40
+ </figure>
41
+ </template>
42
+ <div :class="['cosy:card-body', contentPadding]">
43
+ <h2 class="cosy:card-title cosy:text-xl cosy:font-bold">{{ props.title }}</h2>
44
+ <p v-if="props.subtitle" class="cosy:text-base-content/70 cosy:text-sm cosy:leading-relaxed">{{
45
+ props.subtitle }}
46
+ </p>
47
+ <div v-if="$slots.default" class="cosy:mt-4">
48
+ <slot />
49
+ </div>
50
+ </div>
51
+ </component>
52
+ </template>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ defineOptions({
3
+ name: 'CardCourse',
4
+ });
5
+
6
+ const props = defineProps({
7
+ title: { type: String, required: true },
8
+ link: { type: String, required: true },
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <div>
14
+ <a :href="props.link" class="cosy:justify-center cosy:flex cosy:no-underline cosy:text-base-content">
15
+ <div class="cosy:w-56 cosy:h-80">
16
+ <div
17
+ class="cosy:bg-gradient-to-br cosy:w-full cosy:h-full cosy:from-accent/50 cosy:to-primary/30 cosy:rounded-3xl cosy:shadow-lg cosy:backdrop-blur-sm">
18
+ <div
19
+ class="cosy:bg-base-100/60 cosy:w-full cosy:h-full cosy:rounded-3xl cosy:border cosy:border-base-content/30 hover:cosy:scale-105 hover:cosy:shadow-2xl cosy:transform cosy:duration-200 cosy:backdrop-filter cosy:backdrop-blur-sm">
20
+ <div class="card-body cosy:p-1 cosy:h-full">
21
+ <div
22
+ class="cosy:h-3/5 cosy:w-full cosy:flex cosy:items-center cosy:justify-center cosy:text-6xl cosy:text-primary">
23
+ 📚
24
+ </div>
25
+ <div>
26
+ <h2 class="cosy:text-lg cosy:text-center cosy:font-medium">{{ props.title }}</h2>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </a>
33
+ </div>
34
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Card } from './Card.vue';
2
+ export { default as CardCourse } from './CardCourse.vue';