@yunlefun/vue 0.1.1 → 0.1.2

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 (65) hide show
  1. package/README.md +26 -37
  2. package/components/YlfAccordion.vue +165 -0
  3. package/components/YlfAvatar.vue +70 -0
  4. package/components/YlfBadge.vue +142 -0
  5. package/components/YlfBottomMenu.vue +23 -6
  6. package/components/YlfBottomMenuItem.vue +41 -21
  7. package/components/YlfButton.vue +290 -7
  8. package/components/YlfCard.vue +74 -3
  9. package/components/YlfCheckbox.vue +84 -0
  10. package/components/YlfDialog.vue +174 -0
  11. package/components/YlfDropdownMenu.vue +98 -0
  12. package/components/YlfLogo.vue +83 -7
  13. package/components/YlfPopover.vue +64 -0
  14. package/components/YlfProgress.vue +65 -0
  15. package/components/YlfRadioGroup.vue +120 -0
  16. package/components/YlfSelect.vue +195 -0
  17. package/components/YlfSeparator.vue +68 -0
  18. package/components/YlfSlider.vue +89 -0
  19. package/components/YlfSwitch.vue +111 -0
  20. package/components/YlfTabs.vue +117 -0
  21. package/components/YlfTooltip.vue +67 -0
  22. package/components/accordion/demo.vue +11 -0
  23. package/components/accordion/index.md +19 -0
  24. package/components/avatar/demo.vue +9 -0
  25. package/components/avatar/index.md +15 -0
  26. package/components/badge/demo.vue +84 -0
  27. package/components/badge/index.md +41 -0
  28. package/components/bottom-menu/demo.vue +19 -10
  29. package/components/bottom-menu/index.md +25 -0
  30. package/components/button/demo.vue +101 -3
  31. package/components/button/index.md +41 -0
  32. package/components/card/demo.vue +76 -3
  33. package/components/card/index.md +19 -0
  34. package/components/checkbox/demo.vue +28 -0
  35. package/components/checkbox/index.md +14 -0
  36. package/components/common/demo.vue +7 -1
  37. package/components/common/index.md +10 -0
  38. package/components/dialog/demo.vue +33 -0
  39. package/components/dialog/index.md +30 -0
  40. package/components/dropdown-menu/demo.vue +27 -0
  41. package/components/dropdown-menu/index.md +27 -0
  42. package/components/popover/demo.vue +18 -0
  43. package/components/popover/index.md +21 -0
  44. package/components/progress/demo.vue +24 -0
  45. package/components/progress/index.md +17 -0
  46. package/components/radio/demo.vue +20 -0
  47. package/components/radio/index.md +23 -0
  48. package/components/select/demo.vue +20 -0
  49. package/components/select/index.md +41 -0
  50. package/components/separator/demo.vue +52 -0
  51. package/components/separator/index.md +19 -0
  52. package/components/slider/demo.vue +23 -0
  53. package/components/slider/index.md +16 -0
  54. package/components/switch/demo.vue +33 -0
  55. package/components/switch/index.md +18 -0
  56. package/components/tabs/demo.vue +30 -0
  57. package/components/tabs/index.md +18 -0
  58. package/components/theme.ts +5 -0
  59. package/components/tooltip/demo.vue +19 -0
  60. package/components/tooltip/index.md +21 -0
  61. package/dist/index.cjs +2 -1
  62. package/dist/index.d.cts +7 -1
  63. package/dist/index.d.mts +7 -1
  64. package/dist/index.d.ts +7 -1
  65. package/package.json +18 -3
package/README.md CHANGED
@@ -1,52 +1,41 @@
1
1
  # @yunlefun/vue
2
2
 
3
- Vue toolkit(components/composables) for YunLeFun.
3
+ YunLeFun Design 的 Vue 组件实现,提供 `Ylf*` 组件与 Nuxt 集成。
4
4
 
5
- ## Usage
5
+ ## 安装
6
6
 
7
7
  ```bash
8
- pnpm add -D @yunlefun/vue sass
8
+ pnpm add @yunlefun/ui @yunlefun/vue
9
+ pnpm add -D sass
9
10
  ```
10
11
 
11
- ### Use it in Nuxt
12
+ 在应用 SCSS 入口加载共享主题:
12
13
 
13
- ```ts
14
- export default defineNuxtConfig({
15
- modules: [
16
- // ...
17
- '@vueuse/nuxt',
18
- '@unocss/nuxt',
19
- '@pinia/nuxt',
20
- // add this line
21
- '@yunlefun/vue/nuxt',
22
- ],
23
- })
14
+ ```scss
15
+ @use '@yunlefun/ui/styles';
24
16
  ```
25
17
 
26
- ### Add auto import resolver for vue components
18
+ ## Vue / Vite
19
+
20
+ ```vue
21
+ <script setup lang="ts">
22
+ import YlfButton from '@yunlefun/vue/components/YlfButton.vue'
23
+ </script>
24
+
25
+ <template>
26
+ <YlfButton>保存资料</YlfButton>
27
+ </template>
28
+ ```
29
+
30
+ ## Nuxt
27
31
 
28
32
  ```ts
29
- // vite.config.ts
30
- import { defineConfig } from 'vite'
31
- import Components from 'unplugin-vue-components/vite'
32
-
33
- export default defineConfig({
34
- plugins: [
35
- // https://github.com/antfu/unplugin-vue-components
36
- Components({
37
- // allow auto load markdown components under `./src/components/`
38
- extensions: ['vue', 'md'],
39
- // allow auto import and register components used in markdown
40
- include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
41
- dts: 'src/components.d.ts',
42
- resolvers: [
43
- // add @yunlefun/vue auto import resolver
44
- YlfResolver(),
45
- ],
46
- di
47
- }),
48
- ]
33
+ export default defineNuxtConfig({
34
+ modules: ['@yunlefun/vue/nuxt'],
35
+ css: ['@yunlefun/ui/styles'],
49
36
  })
50
37
  ```
51
38
 
52
- You can use `<YlfXxx />` components in your vue files directly.
39
+ 根入口目前提供类型和组件目录信息,组件通过 `components/*.vue` 子路径或 Nuxt 模块使用;没有公开的 `YlfResolver`。
40
+
41
+ 更多内容见 [Vue 接入](./index.md)、[子包职责](../guide/packages.md)与[设计体系](../guide/design-system.md)。
@@ -0,0 +1,165 @@
1
+ <script lang="ts" setup>
2
+ import { AccordionContent, AccordionHeader, AccordionItem, AccordionRoot, AccordionTrigger } from 'reka-ui'
3
+
4
+ interface AccordionItemData {
5
+ value: string
6
+ title: string
7
+ content?: string
8
+ disabled?: boolean
9
+ }
10
+
11
+ withDefaults(defineProps<{
12
+ items: AccordionItemData[]
13
+ type?: 'single' | 'multiple'
14
+ collapsible?: boolean
15
+ }>(), {
16
+ type: 'single',
17
+ collapsible: true,
18
+ })
19
+
20
+ const model = defineModel<string | string[]>()
21
+ </script>
22
+
23
+ <template>
24
+ <AccordionRoot
25
+ v-model="model"
26
+ :type="type"
27
+ :collapsible="collapsible"
28
+ class="ylf-accordion"
29
+ >
30
+ <AccordionItem
31
+ v-for="it in items"
32
+ :key="it.value"
33
+ :value="it.value"
34
+ :disabled="it.disabled"
35
+ class="ylf-accordion__item"
36
+ >
37
+ <AccordionHeader class="ylf-accordion__header">
38
+ <AccordionTrigger class="ylf-accordion__trigger">
39
+ <span>{{ it.title }}</span>
40
+ <svg class="ylf-accordion__chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
+ <path d="M6 9l6 6 6-6" />
42
+ </svg>
43
+ </AccordionTrigger>
44
+ </AccordionHeader>
45
+ <AccordionContent class="ylf-accordion__content">
46
+ <div class="ylf-accordion__body">
47
+ <slot :name="it.value">
48
+ {{ it.content }}
49
+ </slot>
50
+ </div>
51
+ </AccordionContent>
52
+ </AccordionItem>
53
+ </AccordionRoot>
54
+ </template>
55
+
56
+ <style lang="scss">
57
+ .ylf-accordion {
58
+ border: 1px solid var(--ylf-c-border, #e2e8f0);
59
+ border-radius: var(--ylf-radius, 14px);
60
+ overflow: hidden;
61
+ background: var(--ylf-c-surface, #fff);
62
+ }
63
+
64
+ .ylf-accordion__item + .ylf-accordion__item {
65
+ border-top: 1px solid var(--ylf-c-border, #e2e8f0);
66
+ }
67
+
68
+ .ylf-accordion__header {
69
+ margin: 0;
70
+ }
71
+
72
+ .ylf-accordion__trigger {
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: space-between;
76
+ gap: 12px;
77
+ width: 100%;
78
+ padding: 16px;
79
+ font-family: inherit;
80
+ font-size: 15px;
81
+ font-weight: 500;
82
+ text-align: left;
83
+ color: var(--ylf-c-text, #0f172a);
84
+ background: transparent;
85
+ border: none;
86
+ cursor: pointer;
87
+ transition: background 0.18s ease;
88
+
89
+ &:hover {
90
+ background: var(--ylf-c-bg-soft, #f1f5f9);
91
+ }
92
+
93
+ &[data-disabled] {
94
+ opacity: 0.45;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ &:focus-visible {
99
+ outline: none;
100
+ box-shadow: inset 0 0 0 2px var(--ylf-c-brand, #2563eb);
101
+ }
102
+ }
103
+
104
+ .ylf-accordion__chevron {
105
+ flex: none;
106
+ width: 18px;
107
+ height: 18px;
108
+ color: var(--ylf-c-text-3, #64748b);
109
+ transition: transform 0.25s ease;
110
+ }
111
+
112
+ .ylf-accordion__trigger[data-state='open'] .ylf-accordion__chevron {
113
+ transform: rotate(180deg);
114
+ }
115
+
116
+ .ylf-accordion__content {
117
+ overflow: hidden;
118
+ font-size: 14px;
119
+ line-height: 1.6;
120
+ color: var(--ylf-c-text-2, #475569);
121
+
122
+ &[data-state='open'] {
123
+ animation: ylf-acc-down 0.25s ease;
124
+ }
125
+
126
+ &[data-state='closed'] {
127
+ animation: ylf-acc-up 0.2s ease;
128
+ }
129
+ }
130
+
131
+ .ylf-accordion__body {
132
+ padding: 0 16px 16px;
133
+ }
134
+
135
+ @keyframes ylf-acc-down {
136
+ from {
137
+ height: 0;
138
+ }
139
+
140
+ to {
141
+ height: var(--reka-accordion-content-height);
142
+ }
143
+ }
144
+
145
+ @keyframes ylf-acc-up {
146
+ from {
147
+ height: var(--reka-accordion-content-height);
148
+ }
149
+
150
+ to {
151
+ height: 0;
152
+ }
153
+ }
154
+
155
+ @media (prefers-reduced-motion: reduce) {
156
+ .ylf-accordion__content[data-state='open'],
157
+ .ylf-accordion__content[data-state='closed'] {
158
+ animation: none;
159
+ }
160
+
161
+ .ylf-accordion__chevron {
162
+ transition: none;
163
+ }
164
+ }
165
+ </style>
@@ -0,0 +1,70 @@
1
+ <script lang="ts" setup>
2
+ import { AvatarFallback, AvatarImage, AvatarRoot } from 'reka-ui'
3
+
4
+ withDefaults(defineProps<{
5
+ src?: string
6
+ alt?: string
7
+ /** 图片加载失败/缺省时显示的文字(如姓名首字) */
8
+ fallback?: string
9
+ size?: 'sm' | 'md' | 'lg'
10
+ }>(), {
11
+ size: 'md',
12
+ fallback: '',
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <AvatarRoot class="ylf-avatar" :class="`ylf-avatar--${size}`">
18
+ <AvatarImage v-if="src" :src="src" :alt="alt" class="ylf-avatar__img" />
19
+ <AvatarFallback class="ylf-avatar__fallback" :delay-ms="200">
20
+ {{ fallback }}
21
+ </AvatarFallback>
22
+ </AvatarRoot>
23
+ </template>
24
+
25
+ <style lang="scss">
26
+ .ylf-avatar {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ flex: none;
31
+ overflow: hidden;
32
+ border-radius: 50%;
33
+ user-select: none;
34
+ background: var(--ylf-c-brand-soft, #eff6ff);
35
+
36
+ &--sm {
37
+ width: 32px;
38
+ height: 32px;
39
+ font-size: 13px;
40
+ }
41
+
42
+ &--md {
43
+ width: 44px;
44
+ height: 44px;
45
+ font-size: 16px;
46
+ }
47
+
48
+ &--lg {
49
+ width: 64px;
50
+ height: 64px;
51
+ font-size: 22px;
52
+ }
53
+
54
+ &__img {
55
+ width: 100%;
56
+ height: 100%;
57
+ object-fit: cover;
58
+ }
59
+
60
+ &__fallback {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ width: 100%;
65
+ height: 100%;
66
+ font-weight: 600;
67
+ color: var(--ylf-c-brand, #2563eb);
68
+ }
69
+ }
70
+ </style>
@@ -0,0 +1,142 @@
1
+ <script lang="ts" setup>
2
+ import type { YlfAccentTone, YlfColorAppearance } from './theme'
3
+ import { computed } from 'vue'
4
+
5
+ type BadgeVariant
6
+ = | 'featured' // 精选:明黄星芒 + 中性文字
7
+ | 'accent' // 高饱和实色
8
+ | 'aurora' // @deprecated 使用 featured
9
+ | 'brand' // 晴空蓝
10
+ | 'neutral' // 灰蓝中性信息
11
+ | 'success'
12
+ | 'warning'
13
+ | 'danger'
14
+ | 'info'
15
+
16
+ const props = withDefaults(defineProps<{
17
+ variant?: BadgeVariant
18
+ /** 仅 accent 变体使用 */
19
+ tone?: YlfAccentTone
20
+ /** accent 与状态变体的强调程度 */
21
+ appearance?: YlfColorAppearance
22
+ /** 胶囊圆角 */
23
+ round?: boolean
24
+ }>(), {
25
+ variant: 'brand',
26
+ tone: 'blue',
27
+ appearance: 'solid',
28
+ round: true,
29
+ })
30
+
31
+ const statusPaths: Partial<Record<BadgeVariant, string>> = {
32
+ success: 'M3.5 8 6.5 11 12.5 5',
33
+ warning: 'M8 3v6M8 12h.01',
34
+ danger: 'm4.5 4.5 7 7m0-7-7 7',
35
+ info: 'M8 7v5M8 4h.01',
36
+ }
37
+ const statusPath = computed(() => statusPaths[props.variant])
38
+ </script>
39
+
40
+ <template>
41
+ <span class="ylf-badge" :data-ylf-tone="tone" :class="[`ylf-badge--${variant}`, `is-${appearance}`, { 'is-round': round }]">
42
+ <span v-if="variant === 'featured' || variant === 'aurora'" class="ylf-badge__spark" aria-hidden="true" />
43
+ <svg v-else-if="statusPath" class="ylf-badge__status-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
44
+ <path :d="statusPath" />
45
+ </svg>
46
+ <slot />
47
+ </span>
48
+ </template>
49
+
50
+ <style lang="scss">
51
+ .ylf-badge {
52
+ --_badge-tone: var(--ylf-c-brand, #2563eb);
53
+
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ gap: 5px;
58
+ box-sizing: border-box;
59
+ vertical-align: middle;
60
+ padding: 2px 9px;
61
+ border: 1px solid color-mix(in srgb, var(--_badge-tone) 18%, var(--ylf-c-surface, #fff));
62
+ border-radius: var(--ylf-radius-sm, 10px);
63
+ background: color-mix(in srgb, var(--_badge-tone) 6%, var(--ylf-c-surface, #fff));
64
+ color: var(--_badge-tone);
65
+ font-family: var(--ylf-font-body, inherit);
66
+ font-size: 12px;
67
+ font-weight: 500;
68
+ line-height: 1.5;
69
+ white-space: nowrap;
70
+
71
+ &.is-round {
72
+ border-radius: var(--ylf-radius-pill, 999px);
73
+ }
74
+
75
+ &--accent {
76
+ --_color: var(--ylf-accent, var(--ylf-c-brand, #2563eb));
77
+ --_soft: var(--ylf-accent-soft, var(--ylf-c-brand-soft, #eff6ff));
78
+ --_text: var(--ylf-accent-text, var(--ylf-c-brand, #2563eb));
79
+
80
+ color: var(--ylf-accent-on, var(--ylf-c-text-on-accent, #fff));
81
+ background: var(--ylf-accent, var(--ylf-c-brand, #2563eb));
82
+ border-color: transparent;
83
+ }
84
+
85
+ &--featured,
86
+ &--aurora {
87
+ color: var(--ylf-c-feature-text, #0f172a);
88
+ background: var(--ylf-c-feature-bg, #fef9c3);
89
+ border-color: color-mix(in srgb, var(--ylf-c-feature, #facc15) 45%, var(--ylf-c-surface, #fff));
90
+ }
91
+
92
+ // 装饰星芒不承担状态语义,标签文字保持清晰。
93
+ &__spark {
94
+ flex: none;
95
+ width: 12px;
96
+ height: 12px;
97
+ background: var(--ylf-c-feature, #facc15);
98
+ clip-path: polygon(50% 0, 64% 36%, 100% 50%, 64% 64%, 50% 100%, 36% 64%, 0 50%, 36% 36%);
99
+ }
100
+
101
+ &--neutral {
102
+ --_badge-tone: var(--ylf-c-text-2, #475569);
103
+ }
104
+
105
+ &__status-icon {
106
+ flex: none;
107
+ width: 12px;
108
+ height: 12px;
109
+ }
110
+
111
+ @each $status, $fill, $on in (success, #22c55e, #124324), (warning, #facc15, #64380e), (danger, #ff6b4a, #551b0a),
112
+ (info, #06b6d4, #083e4f)
113
+ {
114
+ &--#{$status} {
115
+ --_color: var(--ylf-status-#{$status}, #{$fill});
116
+ --_soft: var(--ylf-status-#{$status}-soft, var(--ylf-c-bg-soft, #f1f5f9));
117
+ --_text: var(--ylf-status-#{$status}-text, #{$on});
118
+
119
+ color: var(--ylf-status-#{$status}-on, $on);
120
+ background: var(--ylf-status-#{$status}, $fill);
121
+ border-color: transparent;
122
+ }
123
+ }
124
+
125
+ &--accent,
126
+ &--success,
127
+ &--warning,
128
+ &--danger,
129
+ &--info {
130
+ &.is-soft {
131
+ color: var(--_text);
132
+ background: var(--_soft);
133
+ }
134
+
135
+ &.is-outline {
136
+ color: var(--_text);
137
+ background: transparent;
138
+ border-color: var(--_color);
139
+ }
140
+ }
141
+ }
142
+ </style>
@@ -1,15 +1,32 @@
1
+ <script lang="ts" setup>
2
+ withDefaults(defineProps<{
3
+ /** 浮起阴影 */
4
+ shadow?: boolean
5
+ }>(), {
6
+ shadow: false,
7
+ })
8
+ </script>
9
+
1
10
  <template>
2
- <nav class="ylf-bottom-menu mx-auto flex justify-between">
11
+ <nav class="ylf-bottom-menu" :class="{ 'is-shadow': shadow }">
3
12
  <slot />
4
13
  </nav>
5
14
  </template>
6
15
 
7
- <style>
16
+ <style lang="scss">
8
17
  .ylf-bottom-menu {
9
- background-color: var(--ylf-bottom-menu-c-bg, #fff);
10
- }
18
+ display: flex;
19
+ align-items: stretch;
20
+ justify-content: space-around;
21
+ gap: 4px;
22
+ padding: 8px 6px;
23
+ background: var(--ylf-bottom-menu-c-bg, var(--ylf-glass-bg, rgba(255, 255, 255, 0.62)));
24
+ -webkit-backdrop-filter: var(--ylf-glass-blur, saturate(160%) blur(16px));
25
+ backdrop-filter: var(--ylf-glass-blur, saturate(160%) blur(16px));
26
+ border-top: 1px solid var(--ylf-c-border, #e2e8f0);
11
27
 
12
- .dark .ylf-bottom-menu {
13
- background-color: var(--ylf-bottom-menu-c-bg, #222);
28
+ &.is-shadow {
29
+ box-shadow: var(--ylf-shadow, 0 10px 28px -8px rgba(15, 23, 42, 0.16));
30
+ }
14
31
  }
15
32
  </style>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import type { BottomMenuItem } from '@yunlefun/vue'
2
+ import type { BottomMenuItem } from './bottom-menu'
3
3
 
4
4
  defineProps<{
5
5
  active: boolean
@@ -11,35 +11,55 @@ const emit = defineEmits(['click'])
11
11
 
12
12
  <template>
13
13
  <button
14
- :class="{
15
- active,
16
- }"
17
- class="ylf-bottom-menu-item cursor-pointer transition"
18
- flex="~ 1"
19
- justify="center" items="center"
20
- text-gray-800 dark="text-white"
21
- hover="text-blue-500"
22
- p="2"
23
- :to="item.to"
14
+ class="ylf-bottom-menu-item"
15
+ :class="{ active }"
16
+ type="button"
24
17
  @click="emit('click', item)"
25
18
  >
26
- <div flex="~ col" justify="center" items="center">
27
- <slot>
28
- <div text-xl :class="(active && item.activeIcon) || item.icon" />
29
- <span text-xs mt="1">
30
- {{ item.title }}
31
- </span>
32
- </slot>
33
- </div>
19
+ <slot>
20
+ <div class="ylf-bottom-menu-item__icon" :class="(active && item.activeIcon) || item.icon" />
21
+ <span class="ylf-bottom-menu-item__label">{{ item.title }}</span>
22
+ </slot>
34
23
  </button>
35
24
  </template>
36
25
 
37
26
  <style lang="scss">
38
27
  .ylf-bottom-menu-item {
39
- color: var(--ylf-c-text, black);
28
+ flex: 1;
29
+ display: flex;
30
+ flex-direction: column;
31
+ align-items: center;
32
+ justify-content: center;
33
+ gap: 4px;
34
+ padding: 6px 8px;
35
+ border: none;
36
+ background: transparent;
37
+ cursor: pointer;
38
+ color: var(--ylf-c-text-3, #64748b);
39
+ transition:
40
+ color 0.2s ease,
41
+ transform 0.2s var(--ylf-ease-bounce, cubic-bezier(0.34, 1.56, 0.64, 1));
42
+
43
+ &__icon {
44
+ font-size: 22px;
45
+ }
46
+
47
+ &__label {
48
+ font-size: 12px;
49
+ }
50
+
51
+ &:active {
52
+ transform: scale(0.92);
53
+ }
40
54
 
41
55
  &.active {
42
- color: var(--ylf-c-primary, dodgerblue);
56
+ color: var(--ylf-c-brand, #2563eb);
57
+ }
58
+ }
59
+
60
+ @media (prefers-reduced-motion: reduce) {
61
+ .ylf-bottom-menu-item {
62
+ transition: none;
43
63
  }
44
64
  }
45
65
  </style>