@firerian/fireui 1.0.1 → 1.0.3

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.
@@ -340,8 +340,30 @@ declare const _default_2: __VLS_WithTemplateSlots_2<typeof __VLS_component_2, __
340
340
 
341
341
  declare const _default_3: __VLS_WithTemplateSlots_3<typeof __VLS_component_3, __VLS_TemplateResult_3["slots"]>;
342
342
 
343
+ declare const _default_4: DefineComponent<RichTextProps, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
344
+ change: (value: string) => any;
345
+ "update:modelValue": (value: string) => any;
346
+ ready: (quill: any) => any;
347
+ "image-upload": (file: File, callback: (url: string) => void) => any;
348
+ }, string, PublicProps, Readonly<RichTextProps> & Readonly<{
349
+ onChange?: ((value: string) => any) | undefined;
350
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
351
+ onReady?: ((quill: any) => any) | undefined;
352
+ "onImage-upload"?: ((file: File, callback: (url: string) => void) => any) | undefined;
353
+ }>, {
354
+ disabled: boolean;
355
+ height: string | number;
356
+ placeholder: string;
357
+ modelValue: string;
358
+ toolbar: any[];
359
+ }, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
360
+
343
361
  export declare const FButton: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
344
362
 
363
+ export declare const FInput: typeof _default_3 & {
364
+ install: (app: App) => void;
365
+ };
366
+
345
367
  export declare const FireAside: {
346
368
  new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<{
347
369
  width?: string | number;
@@ -643,10 +665,6 @@ height: string | number;
643
665
  install: (app: App) => void;
644
666
  };
645
667
 
646
- export declare const FireInput: typeof _default_3 & {
647
- install: (app: App) => void;
648
- };
649
-
650
668
  export declare const FireMain: {
651
669
  new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<{
652
670
  class?: string;
@@ -676,6 +694,10 @@ style?: string | object;
676
694
  install: (app: App) => void;
677
695
  };
678
696
 
697
+ export declare const FireRichText: typeof _default_4 & {
698
+ install: (app: App) => void;
699
+ };
700
+
679
701
  export declare const FireRow: {
680
702
  new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<RowProps_2> & Readonly<{}>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, PublicProps, {
681
703
  gutter: number | [number, number];
@@ -1240,6 +1262,37 @@ declare interface ResponsiveProps_2 {
1240
1262
  offset?: number;
1241
1263
  }
1242
1264
 
1265
+ declare interface RichTextProps {
1266
+ /**
1267
+ * 绑定的值
1268
+ */
1269
+ modelValue?: string;
1270
+ /**
1271
+ * 编辑器高度
1272
+ */
1273
+ height?: string | number;
1274
+ /**
1275
+ * 是否禁用
1276
+ */
1277
+ disabled?: boolean;
1278
+ /**
1279
+ * 占位文本
1280
+ */
1281
+ placeholder?: string;
1282
+ /**
1283
+ * 自定义工具栏配置
1284
+ */
1285
+ toolbar?: any[];
1286
+ /**
1287
+ * 自定义类名
1288
+ */
1289
+ class?: string;
1290
+ /**
1291
+ * 自定义样式
1292
+ */
1293
+ style?: string | object;
1294
+ }
1295
+
1243
1296
  export declare interface RowProps {
1244
1297
  /**
1245
1298
  * 栅格间距
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firerian/fireui",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "A modern Vue 3 UI component library",
6
6
  "main": "./dist/fireui.cjs",
@@ -74,8 +74,10 @@
74
74
  },
75
75
  "dependencies": {
76
76
  "@floating-ui/vue": "^1.1.10",
77
+ "@vueup/vue-quill": "^1.2.0",
77
78
  "async-validator": "^4.2.5",
78
- "lucide-vue-next": "^0.454.0"
79
+ "lucide-vue-next": "^0.454.0",
80
+ "quill": "^2.0.3"
79
81
  },
80
82
  "devDependencies": {
81
83
  "@eslint/js": "^9.13.0",
@@ -24,7 +24,7 @@ export { FireTips } from './tips'
24
24
  export type { TipsProps, TipsEmits, TipsSlots } from './tips/types'
25
25
 
26
26
  // Input 组件
27
- export { FireInput } from './input'
27
+ export { FInput } from './input'
28
28
  export type { InputProps, InputEmits, InputSlots } from './input/types'
29
29
 
30
30
  // Form 组件
@@ -34,3 +34,7 @@ export type { FormProps, FormEmits, FormInstance, FormItemProps, FormItemEmits,
34
34
  // Table 组件
35
35
  export { FireTable, FireTableColumn } from './table'
36
36
  export type { TableProps, TableEmits, TableInstance, TableColumnProps, TableColumnSlots } from '../types/table'
37
+
38
+ // RichText 组件
39
+ export { FireRichText } from './rich-text'
40
+ export type { RichTextProps, RichTextEmits } from './rich-text/types'
@@ -2,7 +2,7 @@ import Input from './input.vue'
2
2
  import { withInstall } from '../../utils'
3
3
  import type { App } from 'vue'
4
4
 
5
- const FireInput = withInstall(Input as any) as typeof Input & { install: (app: App) => void }
5
+ const FInput = withInstall(Input as any) as typeof Input & { install: (app: App) => void }
6
6
 
7
- export { FireInput }
8
- export default FireInput
7
+ export { FInput }
8
+ export default FInput
@@ -1,10 +1,10 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
- import { FireInput } from './index'
3
+ import { FInput } from './index'
4
4
 
5
- describe('FireInput', () => {
5
+ describe('FInput', () => {
6
6
  it('should render correctly', () => {
7
- const wrapper = mount(FireInput, {
7
+ const wrapper = mount(FInput, {
8
8
  props: {
9
9
  modelValue: 'test'
10
10
  }
@@ -15,7 +15,7 @@ describe('FireInput', () => {
15
15
  })
16
16
 
17
17
  it('should update modelValue when input', async () => {
18
- const wrapper = mount(FireInput, {
18
+ const wrapper = mount(FInput, {
19
19
  props: {
20
20
  modelValue: ''
21
21
  }
@@ -30,7 +30,7 @@ describe('FireInput', () => {
30
30
  })
31
31
 
32
32
  it('should emit focus and blur events', async () => {
33
- const wrapper = mount(FireInput)
33
+ const wrapper = mount(FInput)
34
34
 
35
35
  const input = wrapper.find('input')
36
36
  await input.trigger('focus')
@@ -41,7 +41,7 @@ describe('FireInput', () => {
41
41
  })
42
42
 
43
43
  it('should emit enter event', async () => {
44
- const wrapper = mount(FireInput)
44
+ const wrapper = mount(FInput)
45
45
 
46
46
  const input = wrapper.find('input')
47
47
  await input.trigger('keydown.enter')
@@ -49,7 +49,7 @@ describe('FireInput', () => {
49
49
  })
50
50
 
51
51
  it('should be disabled', () => {
52
- const wrapper = mount(FireInput, {
52
+ const wrapper = mount(FInput, {
53
53
  props: {
54
54
  disabled: true
55
55
  }
@@ -61,7 +61,7 @@ describe('FireInput', () => {
61
61
  })
62
62
 
63
63
  it('should be readonly', () => {
64
- const wrapper = mount(FireInput, {
64
+ const wrapper = mount(FInput, {
65
65
  props: {
66
66
  readonly: true
67
67
  }
@@ -72,7 +72,7 @@ describe('FireInput', () => {
72
72
  })
73
73
 
74
74
  it('should show word limit', () => {
75
- const wrapper = mount(FireInput, {
75
+ const wrapper = mount(FInput, {
76
76
  props: {
77
77
  modelValue: 'test',
78
78
  maxlength: 10,
@@ -84,7 +84,7 @@ describe('FireInput', () => {
84
84
  })
85
85
 
86
86
  it('should toggle password visibility', async () => {
87
- const wrapper = mount(FireInput, {
87
+ const wrapper = mount(FInput, {
88
88
  props: {
89
89
  password: true
90
90
  }
@@ -105,7 +105,7 @@ describe('FireInput', () => {
105
105
  const sizes = ['large', 'medium', 'small']
106
106
 
107
107
  sizes.forEach((size) => {
108
- const wrapper = mount(FireInput, {
108
+ const wrapper = mount(FInput, {
109
109
  props: {
110
110
  size: size as 'large' | 'medium' | 'small'
111
111
  }
@@ -116,7 +116,7 @@ describe('FireInput', () => {
116
116
  })
117
117
 
118
118
  it('should render prefix and suffix slots', () => {
119
- const wrapper = mount(FireInput, {
119
+ const wrapper = mount(FInput, {
120
120
  slots: {
121
121
  prefix: '<span class="prefix">P</span>',
122
122
  suffix: '<span class="suffix">S</span>'
@@ -10,7 +10,7 @@
10
10
  }
11
11
  ]"
12
12
  >
13
- <div v-if="$slots.prefix || prefixIcon" class="fire-input__prefix">
13
+ <div v-if="$slots.prefix || prefixIcon" :class="ns.e('prefix')">
14
14
  <slot name="prefix">
15
15
  <component :is="prefixIcon" v-if="prefixIcon" />
16
16
  </slot>
@@ -28,15 +28,15 @@
28
28
  @focus="handleFocus"
29
29
  @blur="handleBlur"
30
30
  @keydown.enter="handleEnter"
31
- class="fire-input__inner"
31
+ :class="ns.e('inner')"
32
32
  />
33
- <div v-if="$slots.suffix || suffixIcon || showWordLimit || (password && !disabled)" class="fire-input__suffix">
34
- <span v-if="showWordLimit" class="fire-input__word-limit">{{ (modelValue || '').toString().length }}/{{ maxlength }}</span>
35
- <div v-if="password && !disabled" class="fire-input__password-toggle">
33
+ <div v-if="$slots.suffix || suffixIcon || showWordLimit || (password && !disabled)" :class="ns.e('suffix')">
34
+ <span v-if="showWordLimit" :class="ns.e('word-limit')">{{ (modelValue || '').toString().length }}/{{ maxlength }}</span>
35
+ <div v-if="password && !disabled" :class="ns.e('password-toggle')">
36
36
  <button
37
37
  type="button"
38
38
  @click="togglePassword"
39
- class="fire-input__password-icon"
39
+ :class="ns.e('password-icon')"
40
40
  aria-label="Toggle password visibility"
41
41
  >
42
42
  <Eye v-if="!showPassword" :size="16" />
@@ -58,6 +58,11 @@ import { useNamespace } from '../../utils'
58
58
  // 初始化命名空间
59
59
  const ns = useNamespace('input')
60
60
 
61
+ // 组件名称
62
+ defineOptions({
63
+ name: 'FInput'
64
+ })
65
+
61
66
  // Props 定义 - 使用 export 导出
62
67
  export interface InputProps {
63
68
  type?: string
@@ -127,47 +132,45 @@ const togglePassword = () => {
127
132
  }
128
133
  </script>
129
134
 
130
- <style scoped lang="scss">
131
- @import '../../styles/variables.scss';
132
-
133
- .#{$namespace}-input {
135
+ <style lang="scss">
136
+ .fire-input {
134
137
  position: relative;
135
138
  display: inline-flex;
136
139
  align-items: center;
137
140
  width: 100%;
138
- border: 1px solid $border-color;
139
- border-radius: $border-radius-base;
141
+ border: 1px solid #d9d9d9;
142
+ border-radius: 4px;
140
143
  transition: all 0.2s ease;
141
144
  background-color: #fff;
142
145
 
143
146
  &:hover {
144
- border-color: $primary-color;
147
+ border-color: #3b82f6;
145
148
  }
146
149
 
147
150
  &:focus-within {
148
- border-color: $primary-color;
149
- box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
151
+ border-color: #3b82f6;
152
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
150
153
  }
151
154
 
152
155
  &--disabled {
153
- background-color: $disabled-bg;
154
- border-color: $border-color;
156
+ background-color: #f5f5f5;
157
+ border-color: #d9d9d9;
155
158
  cursor: not-allowed;
156
159
 
157
- .#{$namespace}-input__inner {
158
- background-color: $disabled-bg;
160
+ .fire-input__inner {
161
+ background-color: #f5f5f5;
159
162
  cursor: not-allowed;
160
163
  }
161
164
  }
162
165
 
163
166
  &--prefix {
164
- .#{$namespace}-input__inner {
167
+ .fire-input__inner {
165
168
  padding-left: 8px;
166
169
  }
167
170
  }
168
171
 
169
172
  &--suffix {
170
- .#{$namespace}-input__inner {
173
+ .fire-input__inner {
171
174
  padding-right: 8px;
172
175
  }
173
176
  }
@@ -175,7 +178,7 @@ const togglePassword = () => {
175
178
  &--large {
176
179
  height: 40px;
177
180
 
178
- .#{$namespace}-input__inner {
181
+ .fire-input__inner {
179
182
  height: 40px;
180
183
  font-size: 14px;
181
184
  padding: 0 12px;
@@ -185,7 +188,7 @@ const togglePassword = () => {
185
188
  &--medium {
186
189
  height: 32px;
187
190
 
188
- .#{$namespace}-input__inner {
191
+ .fire-input__inner {
189
192
  height: 32px;
190
193
  font-size: 14px;
191
194
  padding: 0 12px;
@@ -195,7 +198,7 @@ const togglePassword = () => {
195
198
  &--small {
196
199
  height: 24px;
197
200
 
198
- .#{$namespace}-input__inner {
201
+ .fire-input__inner {
199
202
  height: 24px;
200
203
  font-size: 12px;
201
204
  padding: 0 8px;
@@ -208,17 +211,17 @@ const togglePassword = () => {
208
211
  outline: none;
209
212
  background: transparent;
210
213
  font-size: 14px;
211
- color: $text-color;
214
+ color: #333333;
212
215
  transition: all 0.2s ease;
213
216
 
214
217
  &::placeholder {
215
- color: $text-color-secondary;
218
+ color: #666666;
216
219
  }
217
220
  }
218
221
 
219
222
  &__prefix {
220
223
  padding: 0 8px;
221
- color: $text-color-secondary;
224
+ color: #666666;
222
225
  font-size: 14px;
223
226
  }
224
227
 
@@ -226,13 +229,13 @@ const togglePassword = () => {
226
229
  display: flex;
227
230
  align-items: center;
228
231
  padding: 0 8px;
229
- color: $text-color-secondary;
232
+ color: #666666;
230
233
  font-size: 14px;
231
234
  }
232
235
 
233
236
  &__word-limit {
234
237
  font-size: 12px;
235
- color: $text-color-secondary;
238
+ color: #666666;
236
239
  margin-right: 8px;
237
240
  }
238
241
 
@@ -245,11 +248,11 @@ const togglePassword = () => {
245
248
  border: none;
246
249
  cursor: pointer;
247
250
  padding: 0;
248
- color: $text-color-secondary;
251
+ color: #666666;
249
252
  transition: color 0.2s ease;
250
253
 
251
254
  &:hover {
252
- color: $primary-color;
255
+ color: #3b82f6;
253
256
  }
254
257
  }
255
258
  }
@@ -0,0 +1,8 @@
1
+ import RichText from './rich-text.vue'
2
+ import { withInstall } from '../../utils'
3
+ import type { App } from 'vue'
4
+
5
+ const FireRichText = withInstall(RichText as any) as typeof RichText & { install: (app: App) => void }
6
+
7
+ export { FireRichText }
8
+ export default FireRichText
@@ -0,0 +1,210 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ ns.b.value,
5
+ {
6
+ [ns.m('disabled')]: disabled
7
+ }
8
+ ]"
9
+ :style="{
10
+ ...(typeof props.style === 'object' ? props.style : {}),
11
+ height
12
+ }"
13
+ >
14
+ <QuillEditor
15
+ v-model:content="localValue"
16
+ :disabled="disabled"
17
+ :placeholder="placeholder"
18
+ :toolbar="toolbar"
19
+ @ready="handleReady"
20
+ @update:content="handleUpdate"
21
+ @text-change="handleTextChange"
22
+ @image-upload="handleImageUpload"
23
+ />
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { ref, computed, watch } from 'vue'
29
+ import { QuillEditor } from '@vueup/vue-quill'
30
+ import 'quill/dist/quill.snow.css'
31
+ import { useNamespace } from '../../utils'
32
+ import type { RichTextProps, RichTextEmits } from './types'
33
+
34
+ const ns = useNamespace('rich-text')
35
+
36
+ const props = withDefaults(defineProps<RichTextProps>(), {
37
+ modelValue: '',
38
+ height: '300px',
39
+ disabled: false,
40
+ placeholder: '请输入内容...',
41
+ toolbar: () => [
42
+ ['bold', 'italic', 'underline', 'strike'],
43
+ ['blockquote', 'code-block'],
44
+ [{ 'header': 1 }, { 'header': 2 }],
45
+ [{ 'list': 'ordered' }, { 'list': 'bullet' }],
46
+ [{ 'script': 'sub' }, { 'script': 'super' }],
47
+ [{ 'indent': '-1' }, { 'indent': '+1' }],
48
+ [{ 'direction': 'rtl' }],
49
+ [{ 'size': ['small', false, 'large', 'huge'] }],
50
+ [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
51
+ [{ 'color': [] }, { 'background': [] }],
52
+ [{ 'font': [] }],
53
+ [{ 'align': [] }],
54
+ ['clean'],
55
+ ['link', 'image', 'video']
56
+ ]
57
+ })
58
+
59
+ const emit = defineEmits<RichTextEmits>()
60
+
61
+ const localValue = ref(props.modelValue)
62
+
63
+ const height = computed(() => {
64
+ return typeof props.height === 'number' ? `${props.height}px` : props.height
65
+ })
66
+
67
+
68
+
69
+ watch(
70
+ () => props.modelValue,
71
+ (newValue) => {
72
+ if (newValue !== localValue.value) {
73
+ localValue.value = newValue
74
+ }
75
+ }
76
+ )
77
+
78
+ const handleReady = (quill: any) => {
79
+ emit('ready', quill)
80
+ }
81
+
82
+ const handleUpdate = (value: string) => {
83
+ emit('update:modelValue', value)
84
+ emit('change', value)
85
+ }
86
+
87
+ const handleTextChange = (_delta: any, _oldDelta: any, source: any) => {
88
+ if (source === 'user') {
89
+ emit('change', localValue.value)
90
+ }
91
+ }
92
+
93
+ const handleImageUpload = (file: File, callback: (url: string) => void) => {
94
+ emit('image-upload', file, callback)
95
+ }
96
+ </script>
97
+
98
+ <style scoped lang="scss">
99
+ @use '../../styles/variables' as *;
100
+
101
+ .#{$namespace}-rich-text {
102
+ border: 1px solid $border-color;
103
+ border-radius: $border-radius-base;
104
+ transition: all 0.2s ease;
105
+ overflow: hidden;
106
+
107
+ &:hover {
108
+ border-color: $primary-color;
109
+ }
110
+
111
+ &--disabled {
112
+ background-color: $disabled-bg;
113
+ border-color: $border-color;
114
+ cursor: not-allowed;
115
+
116
+ .ql-container {
117
+ background-color: $disabled-bg;
118
+ cursor: not-allowed;
119
+ }
120
+ }
121
+
122
+ .ql-container {
123
+ font-size: 14px;
124
+ height: 100%;
125
+ }
126
+
127
+ .ql-editor {
128
+ min-height: calc(100% - 42px);
129
+ line-height: 1.5;
130
+ max-height: calc(100% - 42px);
131
+ overflow-y: auto;
132
+ padding-bottom: 40px;
133
+ }
134
+
135
+ .ql-toolbar.ql-snow {
136
+ border-bottom: 1px solid $border-color;
137
+ }
138
+
139
+ .ql-snow .ql-picker-label {
140
+ color: $text-color;
141
+ }
142
+
143
+ .ql-snow .ql-picker-options {
144
+ color: $text-color;
145
+ background-color: #fff;
146
+ }
147
+
148
+ .ql-snow .ql-picker-item:hover {
149
+ background-color: $primary-color-light;
150
+ }
151
+
152
+ .ql-snow .ql-picker-item.ql-selected {
153
+ color: $primary-color;
154
+ }
155
+
156
+ .ql-snow .ql-button:hover {
157
+ color: $primary-color;
158
+ }
159
+
160
+ .ql-snow .ql-active {
161
+ color: $primary-color;
162
+ }
163
+
164
+ .ql-snow .ql-tooltip {
165
+ background-color: #fff;
166
+ border: 1px solid $border-color;
167
+ border-radius: $border-radius-base;
168
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
169
+ }
170
+
171
+ .ql-snow .ql-tooltip input[type=text] {
172
+ border: 1px solid $border-color;
173
+ border-radius: $border-radius-base;
174
+ }
175
+
176
+ .ql-snow .ql-tooltip input[type=text]:focus {
177
+ border-color: $primary-color;
178
+ box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
179
+ }
180
+
181
+ .ql-snow .ql-tooltip button {
182
+ background-color: $primary-color;
183
+ color: #fff;
184
+ border: none;
185
+ border-radius: $border-radius-base;
186
+ padding: 4px 8px;
187
+ cursor: pointer;
188
+
189
+ &:hover {
190
+ background-color: $primary-color-dark;
191
+ }
192
+ }
193
+
194
+ .ql-snow .ql-tooltip button.ql-action {
195
+ background-color: $success-color;
196
+
197
+ &:hover {
198
+ background-color: $success-color-dark;
199
+ }
200
+ }
201
+
202
+ .ql-snow .ql-tooltip button.ql-remove {
203
+ background-color: $danger-color;
204
+
205
+ &:hover {
206
+ background-color: $danger-color-dark;
207
+ }
208
+ }
209
+ }
210
+ </style>
@@ -0,0 +1,52 @@
1
+ // 简化 Quill 类型定义
2
+ type QuillType = any
3
+
4
+ export interface RichTextProps {
5
+ /**
6
+ * 绑定的值
7
+ */
8
+ modelValue?: string
9
+ /**
10
+ * 编辑器高度
11
+ */
12
+ height?: string | number
13
+ /**
14
+ * 是否禁用
15
+ */
16
+ disabled?: boolean
17
+ /**
18
+ * 占位文本
19
+ */
20
+ placeholder?: string
21
+ /**
22
+ * 自定义工具栏配置
23
+ */
24
+ toolbar?: any[]
25
+ /**
26
+ * 自定义类名
27
+ */
28
+ class?: string
29
+ /**
30
+ * 自定义样式
31
+ */
32
+ style?: string | object
33
+ }
34
+
35
+ export interface RichTextEmits {
36
+ /**
37
+ * 值变化事件
38
+ */
39
+ (e: 'update:modelValue', value: string): void
40
+ /**
41
+ * 编辑器初始化完成事件
42
+ */
43
+ (e: 'ready', quill: QuillType): void
44
+ /**
45
+ * 内容变化事件
46
+ */
47
+ (e: 'change', value: string): void
48
+ /**
49
+ * 图片上传事件
50
+ */
51
+ (e: 'image-upload', file: File, callback: (url: string) => void): void
52
+ }