@coffic/cosy-ui 0.8.25 → 0.8.27

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.
@@ -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';
@@ -27,13 +27,25 @@
27
27
  * </Alert>
28
28
  * ```
29
29
  *
30
+ * 自定义操作按钮:
31
+ * ```astro
32
+ * <Alert type="info">
33
+ * 这是带自定义操作的提示
34
+ * <slot name="action">
35
+ * <button>操作</button>
36
+ * </slot>
37
+ * </Alert>
38
+ * ```
39
+ *
30
40
  * @props
31
41
  * @prop {('info'|'success'|'warning'|'error')} [type='info'] - 提示类型,影响颜色和图标
32
42
  * @prop {string} [title] - 提示标题,可选
33
43
  * @prop {string} [class] - 自定义 CSS 类名
44
+ * @prop {boolean} [closable=true] - 是否可关闭
34
45
  *
35
46
  * @slots
36
47
  * @slot default - 提示内容
48
+ * @slot action - 自定义操作按钮,显示在 alert 右侧
37
49
  */
38
50
 
39
51
  // 注意:
@@ -46,15 +58,22 @@ import {
46
58
  SuccessIcon,
47
59
  WarningIcon,
48
60
  ErrorIcon,
61
+ CloseIcon,
49
62
  } from '../../index-astro';
50
63
 
51
64
  interface Props {
52
65
  type?: 'info' | 'success' | 'warning' | 'error';
53
66
  title?: string;
54
67
  class?: string;
68
+ closable?: boolean;
55
69
  }
56
70
 
57
- const { type = 'info', title, class: className = '' } = Astro.props;
71
+ const {
72
+ type = 'info',
73
+ title,
74
+ class: className = '',
75
+ closable = true,
76
+ } = Astro.props;
58
77
 
59
78
  // 根据类型设置样式
60
79
  const alertClass = {
@@ -73,12 +92,17 @@ const IconComponent = {
73
92
  }[type as 'info' | 'success' | 'warning' | 'error'];
74
93
  ---
75
94
 
76
- <div class={`cosy:alert ${alertClass} ${className}`} role="alert">
95
+ <div
96
+ class={`cosy:alert cosy:w-full cosy:flex ${alertClass} ${className}`}
97
+ role="alert">
77
98
  <div
78
- class="cosy:flex cosy:flex-row cosy:items-center cosy:gap-4 cosy:alert-content">
79
- <IconComponent />
99
+ class="cosy:flex cosy:flex-row cosy:items-center cosy:gap-4 cosy:justify-between cosy:w-full">
100
+ <IconComponent
101
+ class="cosy:btn cosy:btn-sm cosy:btn-ghost cosy:btn-circle"
102
+ />
80
103
 
81
- <div class="cosy:flex cosy:flex-col cosy:items-center cosy:h-full">
104
+ <div
105
+ class="cosy:flex cosy:flex-col cosy:items-start cosy:h-full cosy:flex-1">
82
106
  {
83
107
  title && (
84
108
  <h3 class="cosy:font-bold" style="margin-top: 0 !important">
@@ -96,5 +120,21 @@ const IconComponent = {
96
120
 
97
121
  {!title && <slot />}
98
122
  </div>
123
+
124
+ <div
125
+ class="cosy:flex cosy:flex-row cosy:items-center cosy:gap-2"
126
+ data-role="actions">
127
+ <slot name="action" />
128
+
129
+ {
130
+ closable && (
131
+ <button
132
+ class="cosy:ml-auto cosy:btn cosy:btn-ghost cosy:btn-sm cosy:btn-circle"
133
+ onclick="this.parentElement.parentElement.style.display = 'none';">
134
+ <CloseIcon class="cosy:h-5 cosy:w-5" />
135
+ </button>
136
+ )
137
+ }
138
+ </div>
99
139
  </div>
100
140
  </div>
@@ -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';
@@ -26,75 +26,101 @@ Alert 组件用于向用户显示重要的提示信息,支持多种类型的
26
26
  </Alert>
27
27
  ```
28
28
 
29
+ 自定义操作按钮:
30
+ ```vue
31
+ <Alert type="info">
32
+ 这是带自定义操作的提示
33
+ <template #action>
34
+ <button @click="doSomething">操作</button>
35
+ </template>
36
+ </Alert>
37
+ ```
38
+
29
39
  @props
30
40
  @prop {('info'|'success'|'warning'|'error')} [type='info'] - 提示类型,影响颜色和图标
31
41
  @prop {string} [title] - 提示标题,可选
32
42
  @prop {string} [class] - 自定义 CSS 类名
43
+ @prop {boolean} [closable] - 是否可关闭,默认可关闭
33
44
 
34
45
  @slots
35
46
  @slot default - 提示内容
47
+ @slot action - 自定义操作按钮,显示在 alert 右侧
36
48
  -->
37
49
 
38
50
  <script setup lang="ts">
39
51
  import '../../style';
40
52
  import { computed } from 'vue';
41
53
  import { InfoIcon, SuccessIcon, WarningIcon, ErrorIcon } from '../icons/index';
54
+ import { RiCloseLine } from '@remixicon/vue';
42
55
 
43
56
  interface Props {
44
- type?: 'info' | 'success' | 'warning' | 'error';
45
- title?: string;
46
- class?: string;
57
+ type?: 'info' | 'success' | 'warning' | 'error';
58
+ title?: string;
59
+ class?: string;
60
+ closable?: boolean;
47
61
  }
48
62
 
49
63
  const props = withDefaults(defineProps<Props>(), {
50
- type: 'info',
51
- title: '',
52
- class: '',
64
+ type: 'info',
65
+ title: '',
66
+ class: '',
67
+ closable: true,
53
68
  });
54
69
 
70
+ const emit = defineEmits(['close']);
71
+
72
+ const handleClose = () => {
73
+ emit('close');
74
+ };
75
+
55
76
  // 根据类型设置样式
56
77
  const alertClass = computed(() => {
57
- const alertClasses = {
58
- info: 'cosy:alert-info',
59
- success: 'cosy:alert-success',
60
- warning: 'cosy:alert-warning',
61
- error: 'cosy:alert-error',
62
- };
63
- return alertClasses[props.type];
78
+ const alertClasses = {
79
+ info: 'cosy:alert-info',
80
+ success: 'cosy:alert-success',
81
+ warning: 'cosy:alert-warning',
82
+ error: 'cosy:alert-error',
83
+ };
84
+ return alertClasses[props.type];
64
85
  });
65
86
 
66
87
  // 根据类型设置图标组件
67
88
  const IconComponent = computed(() => {
68
- const iconComponents = {
69
- info: InfoIcon,
70
- success: SuccessIcon,
71
- warning: WarningIcon,
72
- error: ErrorIcon,
73
- };
74
- return iconComponents[props.type];
89
+ const iconComponents = {
90
+ info: InfoIcon,
91
+ success: SuccessIcon,
92
+ warning: WarningIcon,
93
+ error: ErrorIcon,
94
+ };
95
+ return iconComponents[props.type];
75
96
  });
76
97
  </script>
77
98
 
78
99
  <template>
79
- <div :class="['cosy:alert', alertClass, props.class]" role="alert">
80
- <div
81
- class="cosy:flex cosy:flex-row cosy:items-center cosy:gap-4 cosy:alert-content"
82
- >
83
- <component :is="IconComponent" />
84
-
85
- <div class="cosy:flex cosy:flex-col cosy:items-center cosy:h-full">
86
- <h3
87
- v-if="props.title"
88
- class="cosy:font-bold"
89
- style="margin-top: 0 !important"
90
- >
91
- {{ props.title }}
92
- </h3>
93
- <div v-if="props.title" class="cosy:text-xs">
94
- <slot />
100
+ <div :class="['cosy:alert cosy:w-full cosy:flex', alertClass, props.class]" role="alert">
101
+ <div class="cosy:flex cosy:flex-row cosy:items-center cosy:gap-4 cosy:justify-between cosy:w-full">
102
+ <div class="cosy:flex cosy:items-center cosy:gap-4">
103
+ <component :is="IconComponent" class="cosy:btn cosy:btn-sm cosy:btn-ghost cosy:btn-circle" />
104
+
105
+ <div class="cosy:flex cosy:flex-col cosy:items-start cosy:h-full cosy:flex-1">
106
+ <h3 v-if="props.title" class="cosy:font-bold" style="margin-top: 0 !important">
107
+ {{ props.title }}
108
+ </h3>
109
+ <div v-if="props.title" class="cosy:text-xs">
110
+ <slot />
111
+ </div>
112
+ <slot v-else />
113
+ </div>
114
+ </div>
115
+
116
+ <div class="cosy:flex cosy:flex-row cosy:items-center cosy:gap-2" data-role="actions">
117
+ <slot name="action" />
118
+
119
+ <button v-if="props.closable" @click="handleClose"
120
+ class="cosy:btn cosy:btn-ghost cosy:btn-sm cosy:btn-circle">
121
+ <RiCloseLine class="cosy:h-5 cosy:w-5" />
122
+ </button>
123
+ </div>
95
124
  </div>
96
- <slot v-else />
97
- </div>
98
125
  </div>
99
- </div>
100
126
  </template>
@@ -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,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';
@@ -0,0 +1,121 @@
1
+ <!--
2
+ @component KeyCatcher
3
+
4
+ @description
5
+ KeyCatcher 组件用于全局捕获键盘按键事件,并可通过自定义事件通知外部。支持可选的按键展示浮窗。
6
+
7
+ @usage
8
+ 基本用法:
9
+ ```vue
10
+ <KeyCatcher />
11
+ ```
12
+
13
+ 显示最近按下的按键:
14
+ ```vue
15
+ <KeyCatcher :showKey="true" />
16
+ ```
17
+
18
+ 监听全局按键事件:
19
+ ```vue
20
+ <KeyCatcher @globalKey="onGlobalKey" />
21
+ ```
22
+
23
+ @props
24
+ @prop {boolean} [showKey=false] - 是否显示最近按下的按键浮窗
25
+
26
+ @events
27
+ @event globalKey - 当捕获到全局按键时触发,参数为按键字符串
28
+
29
+ @slots
30
+
31
+ -->
32
+
33
+ <script setup lang="ts">
34
+ import { ref, onMounted, onUnmounted } from 'vue';
35
+ import '../../style';
36
+
37
+ const lastKey = ref<string | null>(null);
38
+ let timer: ReturnType<typeof setTimeout> | null = null;
39
+
40
+ const props = defineProps<{ showKey?: boolean }>();
41
+ const emit = defineEmits<{ (e: 'globalKey', key: string): void }>();
42
+
43
+ const handleKeydown = (event: KeyboardEvent) => {
44
+ const tag = (event.target as HTMLElement)?.tagName?.toLowerCase();
45
+ const isEditable =
46
+ tag === 'input' ||
47
+ tag === 'textarea' ||
48
+ (event.target as HTMLElement)?.isContentEditable;
49
+ if (
50
+ event.key.length === 1 ||
51
+ [
52
+ 'Enter',
53
+ 'Escape',
54
+ 'Backspace',
55
+ 'Tab',
56
+ 'Shift',
57
+ 'Control',
58
+ 'Alt',
59
+ 'Meta',
60
+ 'ArrowUp',
61
+ 'ArrowDown',
62
+ 'ArrowLeft',
63
+ 'ArrowRight',
64
+ 'CapsLock',
65
+ 'Delete',
66
+ 'Home',
67
+ 'End',
68
+ 'PageUp',
69
+ 'PageDown',
70
+ ].includes(event.key)
71
+ ) {
72
+ // 只在不是输入框、textarea、contenteditable 时发事件
73
+ if (/^[a-zA-Z]$/.test(event.key) && !isEditable) {
74
+ emit('globalKey', event.key);
75
+ }
76
+ // 展示按键(如果允许)
77
+ if (props.showKey) {
78
+ let key = event.key;
79
+ if (key === ' ') key = 'Space';
80
+ lastKey.value = key;
81
+ if (timer) clearTimeout(timer);
82
+ timer = setTimeout(() => {
83
+ lastKey.value = null;
84
+ }, 3000);
85
+ }
86
+ }
87
+ };
88
+
89
+ onMounted(() => {
90
+ window.addEventListener('keydown', handleKeydown);
91
+ });
92
+
93
+ onUnmounted(() => {
94
+ window.removeEventListener('keydown', handleKeydown);
95
+ if (timer) clearTimeout(timer);
96
+ });
97
+ </script>
98
+
99
+ <template>
100
+ <Transition name="key-fade">
101
+ <div v-if="props.showKey && lastKey"
102
+ class="cosy:fixed cosy:bottom-4 cosy:right-4 cosy:bg-accent cosy:shadow-lg cosy:rounded cosy:px-6 cosy:py-3 cosy:z-50 cosy:text-xl cosy:font-bold cosy:text-gray-800 cosy:select-none cosy:pointer-events-none cosy:border cosy:border-gray-200 cosy:backdrop-blur-sm">
103
+ <span class="cosy:text-blue-600">{{ lastKey }}</span>
104
+ </div>
105
+ </Transition>
106
+ </template>
107
+
108
+ <style scoped>
109
+ .key-fade-enter-active,
110
+ .key-fade-leave-active {
111
+ transition:
112
+ opacity 0.2s,
113
+ transform 0.2s;
114
+ }
115
+
116
+ .key-fade-enter-from,
117
+ .key-fade-leave-to {
118
+ opacity: 0;
119
+ transform: translateY(20px);
120
+ }
121
+ </style>
@@ -0,0 +1 @@
1
+ export { default as KeyCatcher } from './KeyCatcher.vue';