gi-component 0.0.39 → 0.0.41

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gi-component",
3
3
  "type": "module",
4
- "version": "0.0.39",
4
+ "version": "0.0.41",
5
5
  "description": "Vue3中基于Element Plus二次封装基础组件库",
6
6
  "author": "lin",
7
7
  "license": "MIT",
@@ -0,0 +1,5 @@
1
+ import Tag from './src/tag.vue'
2
+
3
+ export type TagInstance = InstanceType<typeof Tag>
4
+ export * from './src/type'
5
+ export default Tag
@@ -0,0 +1,355 @@
1
+ <template>
2
+ <span :class="className" :style="tagStyle" @click="handleClick">
3
+ <span v-if="hasIcon" :class="b('tag__icon')">
4
+ <slot name="icon">
5
+ <ElIcon v-if="icon">
6
+ <component :is="icon" />
7
+ </ElIcon>
8
+ </slot>
9
+ </span>
10
+ <slot />
11
+ <span v-if="closable" class="gi-tag-close-btn" @click="handleClose">
12
+ <ElIcon class="close-icon">
13
+ <Close />
14
+ </ElIcon>
15
+ </span>
16
+ </span>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import type { CSSProperties } from 'vue'
21
+ import type { TagProps, TagThemeColor } from './type'
22
+ import { Close } from '@element-plus/icons-vue'
23
+ import { ElIcon } from 'element-plus'
24
+ import { computed, useSlots } from 'vue'
25
+ import { useBemClass } from '../../../hooks'
26
+
27
+ const props = withDefaults(defineProps<TagProps>(), {
28
+ type: 'light',
29
+ color: 'info',
30
+ size: 'default',
31
+ round: false,
32
+ closable: false
33
+ })
34
+
35
+ const emit = defineEmits<{
36
+ click: []
37
+ close: []
38
+ }>()
39
+
40
+ defineSlots<{
41
+ default?: () => unknown
42
+ icon?: () => unknown
43
+ }>()
44
+
45
+ const slots = useSlots()
46
+ const hasIcon = computed(() => Boolean(slots.icon) || Boolean(props.icon))
47
+
48
+ const SEMANTIC_THEME_COLORS: readonly TagThemeColor[] = [
49
+ 'primary',
50
+ 'success',
51
+ 'warning',
52
+ 'danger',
53
+ 'info'
54
+ ]
55
+
56
+ function isSemanticThemeColor(value: string): value is TagThemeColor {
57
+ return (SEMANTIC_THEME_COLORS as readonly string[]).includes(value)
58
+ }
59
+
60
+ const { b } = useBemClass()
61
+
62
+ const BASE_COLORS = {
63
+ red: '#FF0000',
64
+ orangered: '#f77234',
65
+ orange: '#ff7d00',
66
+ gold: '#f7ba1e',
67
+ lime: '#9fdb1d',
68
+ green: '#00b42a',
69
+ cyan: '#14c9c9',
70
+ blue: '#3491fa',
71
+ purple: '#722ed1',
72
+ pink: '#f5319d',
73
+ gray: '#86909c'
74
+ } as const
75
+
76
+ type PresetColorKey = keyof typeof BASE_COLORS
77
+
78
+ function resolveColorToken(input: string): string {
79
+ return BASE_COLORS[input as PresetColorKey] || input
80
+ }
81
+
82
+ function hexToRgb(hex: string): { r: number, g: number, b: number } {
83
+ const body = hex.startsWith('#') ? hex.slice(1) : hex
84
+ const full = body.length === 3
85
+ ? body.split('').map((c) => c + c).join('')
86
+ : body
87
+ return {
88
+ r: Number.parseInt(full.slice(0, 2), 16),
89
+ g: Number.parseInt(full.slice(2, 4), 16),
90
+ b: Number.parseInt(full.slice(4, 6), 16)
91
+ }
92
+ }
93
+
94
+ function stylesForType(
95
+ type: NonNullable<TagProps['type']>,
96
+ color: string,
97
+ rgb: { r: number, g: number, b: number }
98
+ ): CSSProperties {
99
+ const { r, g, b } = rgb
100
+ /** 与主题色标签一致:悬停时白字 + 实心色底 */
101
+ const closeHoverVars = {
102
+ '--tag-close-hover-color': '#fff',
103
+ '--tag-close-hover-bg-color': color
104
+ } as CSSProperties
105
+
106
+ switch (type) {
107
+ case 'dark':
108
+ return {
109
+ 'color': '#fff',
110
+ 'backgroundColor': color,
111
+ '--tag-close-hover-color': color,
112
+ '--tag-close-hover-bg-color': 'rgba(255, 255, 255, 0.9)'
113
+ } as CSSProperties
114
+ case 'outline':
115
+ return {
116
+ color,
117
+ backgroundColor: 'transparent',
118
+ borderColor: `rgba(${r}, ${g}, ${b}, 0.6)`,
119
+ ...closeHoverVars
120
+ }
121
+ case 'light-outline':
122
+ return {
123
+ color,
124
+ backgroundColor: `rgba(${r}, ${g}, ${b}, 0.1)`,
125
+ borderColor: `rgba(${r}, ${g}, ${b}, 0.2)`,
126
+ ...closeHoverVars
127
+ }
128
+ case 'light':
129
+ default:
130
+ return {
131
+ color,
132
+ backgroundColor: `rgba(${r}, ${g}, ${b}, 0.1)`,
133
+ ...closeHoverVars
134
+ }
135
+ }
136
+ }
137
+
138
+ const className = computed(() => {
139
+ const c = props.color
140
+ return [
141
+ b('tag'),
142
+ props.type && b(`tag__type--${props.type}`),
143
+ props.size && b(`tag__size--${props.size}`),
144
+ props.round && b('tag--round'),
145
+ c && isSemanticThemeColor(c) && b(`tag__color--${c}`)
146
+ ].filter(Boolean).join(' ')
147
+ })
148
+
149
+ const tagStyle = computed((): CSSProperties => {
150
+ const raw = props.color
151
+ if (!raw || isSemanticThemeColor(raw))
152
+ return {}
153
+ const color = resolveColorToken(raw)
154
+ return stylesForType(props.type ?? 'light', color, hexToRgb(color))
155
+ })
156
+
157
+ function handleClick() {
158
+ emit('click')
159
+ }
160
+
161
+ function handleClose(event: MouseEvent) {
162
+ event.stopPropagation()
163
+ emit('close')
164
+ }
165
+ </script>
166
+
167
+ <style scoped lang="scss">
168
+ @use '../../../styles/var.scss' as a;
169
+
170
+ $theme-colors: primary, success, warning, danger, info;
171
+
172
+ $tag-size-small-height: 20px;
173
+ $tag-size-default-height: 22px;
174
+ $tag-size-large-height: 24px;
175
+
176
+ $tag-size-small-padding: 0 6px;
177
+ $tag-size-default-padding: 0 8px;
178
+ $tag-size-large-padding: 0 10px;
179
+
180
+ .#{a.$prefix}-tag {
181
+ box-sizing: border-box;
182
+ display: inline-flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ font-size: 12px;
186
+ line-height: 1;
187
+ white-space: nowrap;
188
+ border-radius: 3px;
189
+ }
190
+
191
+ .#{a.$prefix}-tag--round {
192
+ border-radius: 9999px;
193
+ }
194
+
195
+ .#{a.$prefix}-tag__icon {
196
+ display: inline-flex;
197
+ flex-shrink: 0;
198
+ align-items: center;
199
+ margin-right: 4px;
200
+ line-height: 0;
201
+ color: inherit;
202
+
203
+ :deep(.el-icon),
204
+ :deep(svg) {
205
+ width: 11px;
206
+ height: 11px;
207
+ }
208
+ }
209
+
210
+ .#{a.$prefix}-tag-close-btn {
211
+ position: relative;
212
+ box-sizing: border-box;
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ width: 15px;
217
+ height: 15px;
218
+ margin-left: 4px;
219
+ cursor: pointer;
220
+ background-color: transparent;
221
+ border-radius: 50%;
222
+ transition: background-color 0.1s cubic-bezier(0, 0, 1, 1);
223
+
224
+ .close-icon {
225
+ z-index: 9;
226
+ width: 11px;
227
+ height: 11px;
228
+ color: inherit;
229
+ }
230
+
231
+ /* 主题色写在 .gi-tag__color--* 上,自定义色写在内联 style;统一用变量驱动悬停 */
232
+ &:hover {
233
+ color: var(--tag-close-hover-color);
234
+ background-color: var(--tag-close-hover-bg-color);
235
+ }
236
+ }
237
+
238
+ .#{a.$prefix}-tag__size--small {
239
+ height: $tag-size-small-height;
240
+ padding: $tag-size-small-padding;
241
+
242
+ .#{a.$prefix}-tag__icon {
243
+
244
+ :deep(.el-icon),
245
+ :deep(svg) {
246
+ width: 10px;
247
+ height: 10px;
248
+ }
249
+ }
250
+
251
+ .#{a.$prefix}-tag-close-btn {
252
+ width: 14px;
253
+ height: 14px;
254
+
255
+ .close-icon {
256
+ width: 10px;
257
+ height: 10px;
258
+ }
259
+ }
260
+ }
261
+
262
+ .#{a.$prefix}-tag__size--default {
263
+ height: $tag-size-default-height;
264
+ padding: $tag-size-default-padding;
265
+ }
266
+
267
+ .#{a.$prefix}-tag__size--large {
268
+ height: $tag-size-large-height;
269
+ padding: $tag-size-large-padding;
270
+
271
+ .#{a.$prefix}-tag__icon {
272
+
273
+ :deep(.el-icon),
274
+ :deep(svg) {
275
+ width: 12px;
276
+ height: 12px;
277
+ }
278
+ }
279
+
280
+ .#{a.$prefix}-tag-close-btn {
281
+ width: 16px;
282
+ height: 16px;
283
+
284
+ .close-icon {
285
+ width: 12px;
286
+ height: 12px;
287
+ }
288
+ }
289
+ }
290
+
291
+ .#{a.$prefix}-tag__type--light {
292
+ @each $s in $theme-colors {
293
+ &.#{a.$prefix}-tag__color--#{$s} {
294
+ color: var(--el-color-#{$s});
295
+ background-color: var(--el-color-#{$s}-light-9);
296
+
297
+ --tag-close-hover-color: #fff;
298
+ --tag-close-hover-bg-color: var(--el-color-#{$s});
299
+ }
300
+ }
301
+ }
302
+
303
+ .#{a.$prefix}-tag__type--dark {
304
+ color: #fff;
305
+
306
+ @each $s in $theme-colors {
307
+ &.#{a.$prefix}-tag__color--#{$s} {
308
+ background-color: var(--el-color-#{$s});
309
+
310
+ --tag-close-hover-color: var(--el-color-#{$s});
311
+ --tag-close-hover-bg-color: rgb(255 255 255 / 90%);
312
+ }
313
+ }
314
+ }
315
+
316
+ .#{a.$prefix}-tag__type--outline {
317
+ background: transparent;
318
+ border-style: solid;
319
+ border-width: 1px;
320
+
321
+ @each $s in $theme-colors {
322
+ &.#{a.$prefix}-tag__color--#{$s} {
323
+ color: var(--el-color-#{$s});
324
+ border-color: var(--el-color-#{$s}-light-5);
325
+
326
+ --tag-close-hover-color: #fff;
327
+ --tag-close-hover-bg-color: var(--el-color-#{$s});
328
+ }
329
+ }
330
+ }
331
+
332
+ .#{a.$prefix}-tag__type--light-outline {
333
+ border-style: solid;
334
+ border-width: 1px;
335
+
336
+ @each $s in $theme-colors {
337
+ &.#{a.$prefix}-tag__color--#{$s} {
338
+ color: var(--el-color-#{$s});
339
+ background-color: var(--el-color-#{$s}-light-9);
340
+ border-color: var(--el-color-#{$s}-light-7);
341
+
342
+ --tag-close-hover-color: #fff;
343
+ --tag-close-hover-bg-color: var(--el-color-#{$s});
344
+ }
345
+ }
346
+ }
347
+
348
+ .#{a.$prefix}-tag__type--light,
349
+ .#{a.$prefix}-tag__type--outline,
350
+ .#{a.$prefix}-tag__type--light-outline {
351
+ &.#{a.$prefix}-tag__color--info {
352
+ color: var(--el-color-text-primary);
353
+ }
354
+ }
355
+ </style>
@@ -0,0 +1,38 @@
1
+ import type { Component } from 'vue'
2
+
3
+ /** 与 Element Plus 语义色一致的主题色(走 CSS 变量,非内联色值) */
4
+ export type TagThemeColor = 'primary' | 'success' | 'warning' | 'danger' | 'info'
5
+
6
+ /** 内置调色板色名(映射为固定十六进制) */
7
+ export type TagPaletteColor =
8
+ | 'red'
9
+ | 'orangered'
10
+ | 'orange'
11
+ | 'gold'
12
+ | 'lime'
13
+ | 'green'
14
+ | 'cyan'
15
+ | 'blue'
16
+ | 'purple'
17
+ | 'pink'
18
+ | 'gray'
19
+
20
+ /** 组件属性定义 */
21
+ export interface TagProps {
22
+ /** 标签类型 */
23
+ type?: 'dark' | 'light' | 'outline' | 'light-outline'
24
+ /**
25
+ * 颜色:主题色名(primary / success 等,使用 Element 色板)、调色板预设名(red / blue 等)或任意 CSS 颜色字符串(如十六进制)
26
+ */
27
+ color?: TagThemeColor | TagPaletteColor | string
28
+ /** 标签尺寸 */
29
+ size?: 'small' | 'default' | 'large'
30
+ /**
31
+ * 左侧图标组件(如 `@element-plus/icons-vue` 图标);与 `#icon` 插槽同时存在时以插槽为准
32
+ */
33
+ icon?: Component
34
+ /** 是否为胶囊圆角(大圆角) */
35
+ round?: boolean
36
+ /** 是否可关闭 */
37
+ closable?: boolean
38
+ }
@@ -1,32 +1,33 @@
1
- /* eslint-disable */
2
- // @ts-nocheck
3
- // Generated by unplugin-vue-components
4
- // Read more: https://github.com/vuejs/core/pull/3399
5
- // biome-ignore lint: disable
6
- export {}
7
-
8
- /* prettier-ignore */
9
- declare module 'vue' {
10
- export interface GlobalComponents {
11
- GiButton: typeof import('./components/button/src/button.vue')['default']
12
- GiCard: typeof import('./components/card/src/card.vue')['default']
13
- GiDialog: typeof import('./components/dialog/src/dialog.vue')['default']
14
- GiDialogContent: typeof import('./components/dialog/src/dialog-content.vue')['default']
15
- GiDescriptions: typeof import('./components/descriptions/src/descriptions.vue')['default']
16
- GiDot: typeof import('./components/dot/src/dot.vue')['default']
17
- GiDrawer: typeof import('./components/drawer/src/drawer.vue')['default']
18
- GiEditTable: typeof import('./components/edit-table/src/edit-table.vue')['default']
19
- GiFlex: typeof import('./components/flex/src/flex.vue')['default']
20
- GiForm: typeof import('./components/form/src/form.vue')['default']
21
- GiGrid: typeof import('./components/grid/src/grid.vue')['default']
22
- GiGridItem: typeof import('./components/grid/src/grid-item.vue')['default']
23
- GiInputGroup: typeof import('./components/input-group/src/input-group.vue')['default']
24
- GiInputSearch: typeof import('./components/input-search/src/input-search.vue')['default']
25
- GiPageLayout: typeof import('./components/page-layout/src/page-layout.vue')['default']
26
- GiSplitButton: typeof import('./components/page-layout/src/split-button.vue')['default']
27
- GiTable: typeof import('./components/table/src/table.vue')['default']
28
- GiTableColumn: typeof import('./components/table/src/TableColumn.vue')['default']
29
- GiTabs: typeof import('./components/tabs/src/tabs.vue')['default']
30
- GiTreeTransfer: typeof import('./components/tree-transfer/src/tree-transfer.vue')['default']
31
- }
32
- }
1
+ /* eslint-disable */
2
+ // @ts-nocheck
3
+ // Generated by unplugin-vue-components
4
+ // Read more: https://github.com/vuejs/core/pull/3399
5
+ // biome-ignore lint: disable
6
+ export {}
7
+
8
+ /* prettier-ignore */
9
+ declare module 'vue' {
10
+ export interface GlobalComponents {
11
+ GiButton: typeof import('./components/button/src/button.vue')['default']
12
+ GiCard: typeof import('./components/card/src/card.vue')['default']
13
+ GiDescriptions: typeof import('./components/descriptions/src/descriptions.vue')['default']
14
+ GiDialog: typeof import('./components/dialog/src/dialog.vue')['default']
15
+ GiDialogContent: typeof import('./components/dialog/src/dialog-content.vue')['default']
16
+ GiDot: typeof import('./components/dot/src/dot.vue')['default']
17
+ GiDrawer: typeof import('./components/drawer/src/drawer.vue')['default']
18
+ GiEditTable: typeof import('./components/edit-table/src/edit-table.vue')['default']
19
+ GiFlex: typeof import('./components/flex/src/flex.vue')['default']
20
+ GiForm: typeof import('./components/form/src/form.vue')['default']
21
+ GiGrid: typeof import('./components/grid/src/grid.vue')['default']
22
+ GiGridItem: typeof import('./components/grid/src/grid-item.vue')['default']
23
+ GiInputGroup: typeof import('./components/input-group/src/input-group.vue')['default']
24
+ GiInputSearch: typeof import('./components/input-search/src/input-search.vue')['default']
25
+ GiPageLayout: typeof import('./components/page-layout/src/page-layout.vue')['default']
26
+ GiSplitButton: typeof import('./components/page-layout/src/split-button.vue')['default']
27
+ GiTable: typeof import('./components/table/src/table.vue')['default']
28
+ GiTableColumn: typeof import('./components/table/src/TableColumn.vue')['default']
29
+ GiTabs: typeof import('./components/tabs/src/tabs.vue')['default']
30
+ GiTag: typeof import('./components/tag/src/tag.vue')['default']
31
+ GiTreeTransfer: typeof import('./components/tree-transfer/src/tree-transfer.vue')['default']
32
+ }
33
+ }
package/packages/index.ts CHANGED
@@ -16,6 +16,7 @@ import InputSearch from './components/input-search'
16
16
  import PageLayout from './components/page-layout'
17
17
  import Table from './components/table'
18
18
  import Tabs from './components/tabs'
19
+ import Tag from './components/tag'
19
20
  import TreeTransfer from './components/tree-transfer'
20
21
  import './styles/index.scss'
21
22
 
@@ -29,6 +30,7 @@ export * from './components/edit-table'
29
30
  export * from './components/form'
30
31
  export * from './components/table'
31
32
  export * from './components/tabs'
33
+ export * from './components/tag'
32
34
  export * from './hooks'
33
35
  export * from './utils'
34
36
 
@@ -49,6 +51,7 @@ const components = {
49
51
  Dialog: DialogComponent,
50
52
  EditTable,
51
53
  Table,
54
+ Tag,
52
55
  TreeTransfer
53
56
  } as unknown as Record<string, Component>
54
57
 
@@ -69,6 +72,7 @@ export const GiPageLayout: typeof PageLayout = PageLayout
69
72
  export const GiDialog: typeof DialogComponent = DialogComponent
70
73
  export const GiEditTable: typeof EditTable = EditTable
71
74
  export const GiTable: typeof Table = Table
75
+ export const GiTag: typeof Tag = Tag
72
76
  export const GiTreeTransfer: typeof TreeTransfer = TreeTransfer
73
77
 
74
78
  function capitalizeWord(word: string) {