@firerian/fireui 1.0.0

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/dist/fireui.cjs +2 -0
  4. package/dist/fireui.cjs.map +1 -0
  5. package/dist/fireui.css +1 -0
  6. package/dist/fireui.es.mjs +3867 -0
  7. package/dist/fireui.es.mjs.map +1 -0
  8. package/dist/fireui.umd.js +2 -0
  9. package/dist/fireui.umd.js.map +1 -0
  10. package/dist/types/index.d.ts +1590 -0
  11. package/package.json +132 -0
  12. package/src/components/button/button.test.ts +357 -0
  13. package/src/components/button/button.vue +366 -0
  14. package/src/components/button/index.ts +17 -0
  15. package/src/components/button/types.ts +76 -0
  16. package/src/components/form/form-item.vue +136 -0
  17. package/src/components/form/form.vue +76 -0
  18. package/src/components/form/index.ts +16 -0
  19. package/src/components/grid/col.vue +99 -0
  20. package/src/components/grid/index.ts +16 -0
  21. package/src/components/grid/row.vue +85 -0
  22. package/src/components/grid/types.ts +66 -0
  23. package/src/components/index.ts +36 -0
  24. package/src/components/input/index.ts +8 -0
  25. package/src/components/input/input.test.ts +129 -0
  26. package/src/components/input/input.vue +256 -0
  27. package/src/components/input/types.ts +100 -0
  28. package/src/components/layout/aside.vue +89 -0
  29. package/src/components/layout/container.vue +53 -0
  30. package/src/components/layout/footer.vue +57 -0
  31. package/src/components/layout/header.vue +56 -0
  32. package/src/components/layout/index.ts +28 -0
  33. package/src/components/layout/main.vue +36 -0
  34. package/src/components/layout/types.ts +74 -0
  35. package/src/components/table/index.ts +16 -0
  36. package/src/components/table/table-column.vue +69 -0
  37. package/src/components/table/table.vue +354 -0
  38. package/src/components/tips/index.ts +12 -0
  39. package/src/components/tips/tips.test.ts +96 -0
  40. package/src/components/tips/tips.vue +206 -0
  41. package/src/components/tips/types.ts +56 -0
  42. package/src/components/tooltip/index.ts +8 -0
  43. package/src/components/tooltip/tooltip.test.ts +187 -0
  44. package/src/components/tooltip/tooltip.vue +261 -0
  45. package/src/components/tooltip/types.ts +60 -0
  46. package/src/hooks/useForm.ts +233 -0
  47. package/src/hooks/useTable.ts +153 -0
  48. package/src/index.ts +48 -0
  49. package/src/styles/main.scss +6 -0
  50. package/src/styles/mixins.scss +48 -0
  51. package/src/styles/reset.scss +49 -0
  52. package/src/styles/variables.scss +137 -0
  53. package/src/types/component.ts +9 -0
  54. package/src/types/form.ts +149 -0
  55. package/src/types/global.d.ts +49 -0
  56. package/src/types/grid.ts +76 -0
  57. package/src/types/index.ts +23 -0
  58. package/src/types/table.ts +181 -0
  59. package/src/types/tooltip.ts +44 -0
  60. package/src/utils/auto-import.ts +41 -0
  61. package/src/utils/index.ts +2 -0
  62. package/src/utils/install.ts +20 -0
  63. package/src/utils/useNamespace.ts +29 -0
@@ -0,0 +1,8 @@
1
+ import Tooltip from './tooltip.vue'
2
+ import { withInstall } from '../../utils'
3
+ import type { App } from 'vue'
4
+
5
+ const FireTooltip = withInstall(Tooltip as any) as typeof Tooltip & { install: (app: App) => void }
6
+
7
+ export { FireTooltip }
8
+ export default FireTooltip
@@ -0,0 +1,187 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { FireTooltip } from './index'
4
+
5
+ describe('FireTooltip', () => {
6
+ beforeEach(() => {
7
+ // 清理 DOM
8
+ document.body.innerHTML = ''
9
+ })
10
+
11
+ it('should render correctly', () => {
12
+ const wrapper = mount(FireTooltip, {
13
+ props: {
14
+ content: 'Test tooltip'
15
+ },
16
+ slots: {
17
+ default: '<button>Hover me</button>'
18
+ }
19
+ })
20
+
21
+ expect(wrapper.exists()).toBe(true)
22
+ expect(wrapper.find('button').text()).toBe('Hover me')
23
+ })
24
+
25
+ it('should show tooltip on hover', async () => {
26
+ const wrapper = mount(FireTooltip, {
27
+ props: {
28
+ content: 'Test tooltip',
29
+ trigger: 'hover'
30
+ },
31
+ slots: {
32
+ default: '<button>Hover me</button>'
33
+ }
34
+ })
35
+
36
+ // 初始状态应该隐藏
37
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
38
+
39
+ // 模拟鼠标移入
40
+ await wrapper.find('button').trigger('mouseenter')
41
+ await wrapper.vm.$nextTick()
42
+
43
+ // 应该显示 tooltip
44
+ expect(document.querySelector('.fire-tooltip')).toBeTruthy()
45
+
46
+ // 模拟鼠标移出
47
+ await wrapper.find('button').trigger('mouseleave')
48
+ await wrapper.vm.$nextTick()
49
+
50
+ // 应该隐藏 tooltip
51
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
52
+ })
53
+
54
+ it('should show tooltip on click', async () => {
55
+ const wrapper = mount(FireTooltip, {
56
+ props: {
57
+ content: 'Test tooltip',
58
+ trigger: 'click'
59
+ },
60
+ slots: {
61
+ default: '<button>Click me</button>'
62
+ }
63
+ })
64
+
65
+ // 初始状态应该隐藏
66
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
67
+
68
+ // 模拟点击
69
+ await wrapper.find('button').trigger('click')
70
+ await wrapper.vm.$nextTick()
71
+
72
+ // 应该显示 tooltip
73
+ expect(document.querySelector('.fire-tooltip')).toBeTruthy()
74
+
75
+ // 再次点击
76
+ await wrapper.find('button').trigger('click')
77
+ await wrapper.vm.$nextTick()
78
+
79
+ // 应该隐藏 tooltip
80
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
81
+ })
82
+
83
+ it('should show tooltip on focus', async () => {
84
+ const wrapper = mount(FireTooltip, {
85
+ props: {
86
+ content: 'Test tooltip',
87
+ trigger: 'focus'
88
+ },
89
+ slots: {
90
+ default: '<input type="text" placeholder="Focus me" />'
91
+ }
92
+ })
93
+
94
+ // 初始状态应该隐藏
95
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
96
+
97
+ // 模拟聚焦
98
+ await wrapper.find('input').trigger('focus')
99
+ await wrapper.vm.$nextTick()
100
+
101
+ // 应该显示 tooltip
102
+ expect(document.querySelector('.fire-tooltip')).toBeTruthy()
103
+
104
+ // 模拟失焦
105
+ await wrapper.find('input').trigger('blur')
106
+ await wrapper.vm.$nextTick()
107
+
108
+ // 应该隐藏 tooltip
109
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
110
+ })
111
+
112
+ it('should be disabled', async () => {
113
+ const wrapper = mount(FireTooltip, {
114
+ props: {
115
+ content: 'Test tooltip',
116
+ disabled: true,
117
+ trigger: 'hover'
118
+ },
119
+ slots: {
120
+ default: '<button>Hover me</button>'
121
+ }
122
+ })
123
+
124
+ // 模拟鼠标移入
125
+ await wrapper.find('button').trigger('mouseenter')
126
+ await wrapper.vm.$nextTick()
127
+
128
+ // 应该仍然隐藏
129
+ expect(document.querySelector('.fire-tooltip')).toBe(null)
130
+ })
131
+
132
+ it('should use slot content', async () => {
133
+ const wrapper = mount(FireTooltip, {
134
+ props: {
135
+ trigger: 'hover'
136
+ },
137
+ slots: {
138
+ default: '<button>Hover me</button>',
139
+ content: '<div>Custom tooltip content</div>'
140
+ }
141
+ })
142
+
143
+ // 模拟鼠标移入
144
+ await wrapper.find('button').trigger('mouseenter')
145
+ await wrapper.vm.$nextTick()
146
+
147
+ // 应该显示自定义内容
148
+ expect(document.querySelector('.fire-tooltip-content')?.textContent).toBe('Custom tooltip content')
149
+ })
150
+
151
+ it('should emit events', async () => {
152
+ const showSpy = vi.fn()
153
+ const hideSpy = vi.fn()
154
+ const updateVisibleSpy = vi.fn()
155
+
156
+ const wrapper = mount(FireTooltip, {
157
+ props: {
158
+ content: 'Test tooltip',
159
+ trigger: 'click'
160
+ },
161
+ slots: {
162
+ default: '<button>Click me</button>'
163
+ },
164
+ listeners: {
165
+ show: showSpy,
166
+ hide: hideSpy,
167
+ 'update:visible': updateVisibleSpy
168
+ }
169
+ })
170
+
171
+ // 模拟点击
172
+ await wrapper.find('button').trigger('click')
173
+ await wrapper.vm.$nextTick()
174
+
175
+ // 应该触发 show 和 update:visible 事件
176
+ expect(showSpy).toHaveBeenCalled()
177
+ expect(updateVisibleSpy).toHaveBeenCalledWith(true)
178
+
179
+ // 再次点击
180
+ await wrapper.find('button').trigger('click')
181
+ await wrapper.vm.$nextTick()
182
+
183
+ // 应该触发 hide 和 update:visible 事件
184
+ expect(hideSpy).toHaveBeenCalled()
185
+ expect(updateVisibleSpy).toHaveBeenCalledWith(false)
186
+ })
187
+ })
@@ -0,0 +1,261 @@
1
+ <template>
2
+ <div
3
+ ref="reference"
4
+ :class="ns.b"
5
+ @mouseenter="handleMouseEnter"
6
+ @mouseleave="handleMouseLeave"
7
+ @focus="handleFocus"
8
+ @blur="handleBlur"
9
+ >
10
+ <slot></slot>
11
+ <Teleport to="body">
12
+ <Transition name="fire-tooltip">
13
+ <div
14
+ v-if="isVisible"
15
+ ref="floating"
16
+ :class="[
17
+ ns.e('content'),
18
+ ns.m(position)
19
+ ]"
20
+ :style="floatingStyles"
21
+ :id="tooltipId"
22
+ role="tooltip"
23
+ >
24
+ <div :class="ns.e('arrow')"></div>
25
+ <div :class="ns.e('inner')">
26
+ <slot name="content">{{ content }}</slot>
27
+ </div>
28
+ </div>
29
+ </Transition>
30
+ </Teleport>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
36
+ import { useFloating, flip, offset, autoUpdate } from '@floating-ui/vue'
37
+ import { useNamespace } from '../../utils'
38
+ // 类型定义内联,避免模块解析问题
39
+ type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right'
40
+ type TooltipTrigger = 'hover' | 'click' | 'focus'
41
+
42
+ const ns = useNamespace('tooltip')
43
+
44
+ // Props 定义 - 使用 export 导出
45
+ export interface TooltipProps {
46
+ content?: string
47
+ placement?: TooltipPlacement
48
+ trigger?: TooltipTrigger
49
+ disabled?: boolean
50
+ visible?: boolean
51
+ offset?: number
52
+ }
53
+
54
+ const props = withDefaults(defineProps<TooltipProps>(), {
55
+ content: '',
56
+ placement: 'top',
57
+ trigger: 'hover',
58
+ disabled: false,
59
+ visible: false,
60
+ offset: 8
61
+ })
62
+
63
+ const emit = defineEmits<{
64
+ 'update:visible': [value: boolean]
65
+ show: []
66
+ hide: []
67
+ }>()
68
+
69
+ const reference = ref<HTMLElement | null>(null)
70
+ const floating = ref<HTMLElement | null>(null)
71
+ const isVisible = ref(props.visible)
72
+ const tooltipId = `fire-tooltip-${Math.random().toString(36).substr(2, 9)}`
73
+ const position = ref(props.placement)
74
+
75
+ const { x, y, strategy } = useFloating(
76
+ reference,
77
+ floating,
78
+ {
79
+ placement: props.placement,
80
+ middleware: [
81
+ offset(props.offset),
82
+ flip()
83
+ ],
84
+ whileElementsMounted: autoUpdate
85
+ }
86
+ )
87
+
88
+ const floatingStyles = computed(() => ({
89
+ position: strategy.value,
90
+ left: `${x.value ?? 0}px`,
91
+ top: `${y.value ?? 0}px`
92
+ }))
93
+
94
+ const show = () => {
95
+ if (props.disabled) return
96
+ isVisible.value = true
97
+ emit('update:visible', true)
98
+ emit('show')
99
+ }
100
+
101
+ const hide = () => {
102
+ isVisible.value = false
103
+ emit('update:visible', false)
104
+ emit('hide')
105
+ }
106
+
107
+ const handleMouseEnter = () => {
108
+ if (props.trigger === 'hover') {
109
+ show()
110
+ }
111
+ }
112
+
113
+ const handleMouseLeave = () => {
114
+ if (props.trigger === 'hover') {
115
+ hide()
116
+ }
117
+ }
118
+
119
+ const handleFocus = () => {
120
+ if (props.trigger === 'focus') {
121
+ show()
122
+ }
123
+ }
124
+
125
+ const handleBlur = () => {
126
+ if (props.trigger === 'focus') {
127
+ hide()
128
+ }
129
+ }
130
+
131
+ // 点击外部关闭
132
+ const handleClickOutside = (event: MouseEvent) => {
133
+ if (
134
+ reference.value &&
135
+ !reference.value.contains(event.target as Node) &&
136
+ floating.value &&
137
+ !floating.value.contains(event.target as Node)
138
+ ) {
139
+ hide()
140
+ }
141
+ }
142
+
143
+ onMounted(() => {
144
+ if (props.trigger === 'click') {
145
+ document.addEventListener('click', handleClickOutside)
146
+ }
147
+ })
148
+
149
+ onUnmounted(() => {
150
+ if (props.trigger === 'click') {
151
+ document.removeEventListener('click', handleClickOutside)
152
+ }
153
+ })
154
+
155
+ // 监听 visible 变化
156
+ watch(() => props.visible, (val) => {
157
+ isVisible.value = val
158
+ })
159
+
160
+ // 清理
161
+ </script>
162
+
163
+ <style scoped lang="scss">
164
+ @import '../../styles/variables.scss';
165
+
166
+ .#{$namespace}-tooltip {
167
+ position: relative;
168
+ display: inline-block;
169
+ }
170
+
171
+ .#{$namespace}-tooltip__content {
172
+ position: absolute;
173
+ background-color: $tooltip-bg;
174
+ color: $tooltip-text;
175
+ padding: $tooltip-padding;
176
+ border-radius: $tooltip-border-radius;
177
+ font-size: $font-size-base;
178
+ line-height: 1.4;
179
+ max-width: 250px;
180
+ word-wrap: break-word;
181
+ pointer-events: auto;
182
+ box-shadow: $shadow-md;
183
+ z-index: 1000;
184
+
185
+ &--top {
186
+ bottom: 100%;
187
+ left: 50%;
188
+ transform: translateX(-50%);
189
+ margin-bottom: 8px;
190
+ }
191
+
192
+ &--bottom {
193
+ top: 100%;
194
+ left: 50%;
195
+ transform: translateX(-50%);
196
+ margin-top: 8px;
197
+ }
198
+
199
+ &--left {
200
+ right: 100%;
201
+ top: 50%;
202
+ transform: translateY(-50%);
203
+ margin-right: 8px;
204
+ }
205
+
206
+ &--right {
207
+ left: 100%;
208
+ top: 50%;
209
+ transform: translateY(-50%);
210
+ margin-left: 8px;
211
+ }
212
+ }
213
+
214
+ .#{$namespace}-tooltip__arrow {
215
+ position: absolute;
216
+ width: 8px;
217
+ height: 8px;
218
+ background-color: $tooltip-bg;
219
+ transform: rotate(45deg);
220
+
221
+ .#{$namespace}-tooltip__content--top & {
222
+ bottom: -4px;
223
+ left: 50%;
224
+ transform: translateX(-50%) rotate(45deg);
225
+ }
226
+
227
+ .#{$namespace}-tooltip__content--bottom & {
228
+ top: -4px;
229
+ left: 50%;
230
+ transform: translateX(-50%) rotate(45deg);
231
+ }
232
+
233
+ .#{$namespace}-tooltip__content--left & {
234
+ right: -4px;
235
+ top: 50%;
236
+ transform: translateY(-50%) rotate(45deg);
237
+ }
238
+
239
+ .#{$namespace}-tooltip__content--right & {
240
+ left: -4px;
241
+ top: 50%;
242
+ transform: translateY(-50%) rotate(45deg);
243
+ }
244
+ }
245
+
246
+ // 动画效果
247
+ .fire-tooltip-enter-active,
248
+ .fire-tooltip-leave-active {
249
+ transition: opacity 0.2s ease, transform 0.2s ease;
250
+ }
251
+
252
+ .fire-tooltip-enter-from {
253
+ opacity: 0;
254
+ transform: scale(0.9);
255
+ }
256
+
257
+ .fire-tooltip-leave-to {
258
+ opacity: 0;
259
+ transform: scale(0.9);
260
+ }
261
+ </style>
@@ -0,0 +1,60 @@
1
+ export interface TooltipProps {
2
+ /**
3
+ * 提示内容
4
+ */
5
+ content?: string
6
+ /**
7
+ * 触发方式
8
+ */
9
+ trigger?: 'hover' | 'click' | 'focus' | 'manual'
10
+ /**
11
+ * 禁用状态
12
+ */
13
+ disabled?: boolean
14
+ /**
15
+ * 初始可见状态 (仅用于 manual 触发)
16
+ */
17
+ visible?: boolean
18
+ /**
19
+ * 位置偏好
20
+ */
21
+ placement?: 'top' | 'bottom' | 'left' | 'right'
22
+ /**
23
+ * 偏移量
24
+ */
25
+ offset?: number
26
+ /**
27
+ * 自定义类名
28
+ */
29
+ class?: string
30
+ /**
31
+ * 自定义样式
32
+ */
33
+ style?: string | object
34
+ }
35
+
36
+ export interface TooltipEmits {
37
+ /**
38
+ * 可见状态变化事件
39
+ */
40
+ (e: 'update:visible', value: boolean): void
41
+ /**
42
+ * 显示事件
43
+ */
44
+ (e: 'show'): void
45
+ /**
46
+ * 隐藏事件
47
+ */
48
+ (e: 'hide'): void
49
+ }
50
+
51
+ export interface TooltipSlots {
52
+ /**
53
+ * 触发元素
54
+ */
55
+ default: () => any
56
+ /**
57
+ * 提示内容
58
+ */
59
+ content?: () => any
60
+ }