tang-ui-x 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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/components/TActionSheet/index.uvue +170 -0
  4. package/components/TActionSheet/type.uts +29 -0
  5. package/components/TAvatar/index.uvue +156 -0
  6. package/components/TAvatar/type.uts +54 -0
  7. package/components/TBadge/index.uvue +152 -0
  8. package/components/TBadge/type.uts +48 -0
  9. package/components/TButton/README.md +111 -0
  10. package/components/TButton/index.uvue +380 -0
  11. package/components/TButton/type.uts +95 -0
  12. package/components/TCard/index.uvue +174 -0
  13. package/components/TCard/type.uts +50 -0
  14. package/components/TCell/index.uvue +49 -0
  15. package/components/TCheckbox/index.uvue +187 -0
  16. package/components/TCheckboxGroup/index.uvue +139 -0
  17. package/components/TCheckboxGroup/type.uts +26 -0
  18. package/components/TCol/index.uvue +82 -0
  19. package/components/TCol/type.uts +30 -0
  20. package/components/TCollapse/index.uvue +93 -0
  21. package/components/TCollapse/type.uts +36 -0
  22. package/components/TCollapseItem/index.uvue +194 -0
  23. package/components/TCollapseItem/type.uts +25 -0
  24. package/components/TDialog/index.uvue +386 -0
  25. package/components/TDialog/type.uts +84 -0
  26. package/components/TDivider/index.uvue +235 -0
  27. package/components/TDivider/type.uts +91 -0
  28. package/components/TEmpty/index.uvue +128 -0
  29. package/components/TErrorState/index.uvue +57 -0
  30. package/components/TGrid/index.uvue +115 -0
  31. package/components/TGrid/type.uts +77 -0
  32. package/components/TGridItem/index.uvue +243 -0
  33. package/components/TGridItem/type.uts +64 -0
  34. package/components/TIcon/index.uvue +96 -0
  35. package/components/TImage/index.uvue +255 -0
  36. package/components/TImage/type.uts +146 -0
  37. package/components/TInput/README.md +119 -0
  38. package/components/TInput/index.uvue +376 -0
  39. package/components/TInput/type.uts +138 -0
  40. package/components/TList/index.uvue +82 -0
  41. package/components/TList/type.uts +68 -0
  42. package/components/TListItem/index.uvue +161 -0
  43. package/components/TListItem/type.uts +49 -0
  44. package/components/TLoading/index.uvue +153 -0
  45. package/components/TLoading/type.uts +43 -0
  46. package/components/TNavBar/index.uvue +120 -0
  47. package/components/TNavBar/type.uts +22 -0
  48. package/components/TNoticeBar/index.uvue +106 -0
  49. package/components/TNoticeBar/type.uts +21 -0
  50. package/components/TNumberInput/index.uvue +226 -0
  51. package/components/TPicker/index.uvue +276 -0
  52. package/components/TPicker/type.uts +105 -0
  53. package/components/TPopup/index.uvue +442 -0
  54. package/components/TProgress/index.uvue +103 -0
  55. package/components/TProgress/type.uts +64 -0
  56. package/components/TRadioButton/index.uvue +232 -0
  57. package/components/TRadioGroup/index.uvue +117 -0
  58. package/components/TRadioGroup/type.uts +25 -0
  59. package/components/TRate/index.uvue +182 -0
  60. package/components/TRow/index.uvue +105 -0
  61. package/components/TRow/type.uts +52 -0
  62. package/components/TSearchBar/index.uvue +255 -0
  63. package/components/TSearchBar/type.uts +140 -0
  64. package/components/TSelect/index.uvue +655 -0
  65. package/components/TSelect/type.uts +57 -0
  66. package/components/TSlider/index.uvue +72 -0
  67. package/components/TSlider/type.uts +21 -0
  68. package/components/TSwiper/index.uvue +222 -0
  69. package/components/TSwiper/type.uts +77 -0
  70. package/components/TSwitch/index.uvue +177 -0
  71. package/components/TSwitch/type.uts +52 -0
  72. package/components/TText/README.md +124 -0
  73. package/components/TText/index.uvue +257 -0
  74. package/components/TText/type.uts +114 -0
  75. package/components/TTextarea/index.uvue +239 -0
  76. package/components/TTextarea/type.uts +106 -0
  77. package/components/TToast/type.uts +14 -0
  78. package/components/Tabs/README.md +297 -0
  79. package/components/Tabs/index.uvue +383 -0
  80. package/components/Tabs/type.uts +10 -0
  81. package/components/Tags/README.md +297 -0
  82. package/components/Tags/index.uvue +383 -0
  83. package/components/Tags/type.uts +10 -0
  84. package/components/VbenFrom/index.uvue +392 -0
  85. package/composables/useModal.uts +294 -0
  86. package/composables/useTheme.uts +235 -0
  87. package/composables/useToast.uts +322 -0
  88. package/index.js +62 -0
  89. package/package.json +48 -0
  90. package/style/colors/index.scss +157 -0
  91. package/style/index.scss +399 -0
  92. package/types/index.uts +52 -0
  93. package/uni.scss +79 -0
  94. package/utils/color.uts +92 -0
  95. package/utils/common.uts +245 -0
  96. package/utils/dom.uts +275 -0
  97. package/utils/index.uts +10 -0
  98. package/utils/validator.uts +155 -0
@@ -0,0 +1,383 @@
1
+ <script setup lang="uts">
2
+ import type {TabItem} from './type'
3
+
4
+
5
+ /**
6
+ * Tabs 组件属性
7
+ */
8
+ type TabsProps = {
9
+ /** 选项卡数据 */
10
+ items: TabItem[]
11
+ /** 当前激活的 tab key */
12
+ activeKey?: string
13
+ /** 默认激活的 tab key */
14
+ defaultActiveKey?: string
15
+ /** 选项卡类型 */
16
+ type?: "line" | "card"
17
+ /** 选项卡位置 */
18
+ tabPosition?: "top" | "bottom"
19
+ /** 是否居中显示 */
20
+ centered?: boolean
21
+ /** 是否可滑动 */
22
+ scrollable?: boolean
23
+ /** 激活标签的颜色 */
24
+ activeColor?: string
25
+ /** 未激活标签的颜色 */
26
+ inactiveColor?: string
27
+ /** 标签大小 */
28
+ size?: "small" | "medium" | "large"
29
+ /** 是否显示动画 */
30
+ animated?: boolean
31
+ }
32
+
33
+ // v-model 绑定当前激活 key
34
+ const model = defineModel()
35
+
36
+ const props = withDefaults(defineProps<TabsProps>(), {
37
+ items: () => [],
38
+ type: 'line',
39
+ tabPosition: 'top',
40
+ centered: false,
41
+ scrollable: true,
42
+ activeColor: '#00bba7',
43
+ inactiveColor: '#666666',
44
+ size: 'medium',
45
+ animated: true,
46
+ })
47
+
48
+ const emit = defineEmits<{
49
+ /** 切换标签时触发 */
50
+ change: [key: string |number]
51
+ /** 当前激活的 tab 改变时触发 */
52
+ 'update:activeKey': [key: string |number]
53
+ /** 点击标签时触发 */
54
+ tabClick: [key: string |number, item: TabItem]
55
+ }>()
56
+
57
+ /** 获取默认激活项的 key */
58
+ const getDefaultKey = (): string => {
59
+ if (props.defaultActiveKey) {
60
+ return props.defaultActiveKey
61
+ }
62
+ if (props.items.length > 0) {
63
+ const firstEnabled = props.items.find(item => !item.disabled)
64
+ return firstEnabled ? firstEnabled.key : props.items[0].key
65
+ }
66
+ return ''
67
+ }
68
+
69
+ /** 初始化默认激活项 */
70
+ onMounted(() => {
71
+ if (!model.value) {
72
+ model.value = getDefaultKey()
73
+ }
74
+ })
75
+
76
+ /** 计算当前激活项的索引 */
77
+ const activeIndex = computed(() => {
78
+ return props.items.findIndex(item => item.key === model.value)
79
+ })
80
+
81
+ /** 获取当前激活项的 DOM 元素 */
82
+ const activeItemRef = ref<UniElement | null>(null)
83
+ const navListRef = ref<UniElement | null>(null)
84
+
85
+ /** 将十六进制颜色转换为 RGB */
86
+ const hexToRgb = (hex: string): string => {
87
+ // 移除 # 号
88
+ hex = hex.replace('#', '')
89
+
90
+ // 处理缩写形式 (#fff)
91
+ if (hex.length === 3) {
92
+ hex = hex.split('').map(char => char + char).join('')
93
+ }
94
+
95
+ // 转换为 RGB
96
+ const r = parseInt(hex.substring(0, 2), 16)
97
+ const g = parseInt(hex.substring(2, 4), 16)
98
+ const b = parseInt(hex.substring(4, 6), 16)
99
+
100
+ return `${r}, ${g}, ${b}`
101
+ }
102
+
103
+ /** 计算激活颜色的 RGB 值 */
104
+ const activeColorRgb = computed(() => {
105
+ // 如果是 rgba 格式,直接提取 RGB 部分
106
+ if (props.activeColor.startsWith('rgb')) {
107
+ const match = props.activeColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/)
108
+ if (match) {
109
+ return `${match[1]}, ${match[2]}, ${match[3]}`
110
+ }
111
+ }
112
+ // 如果是十六进制颜色,转换为 RGB
113
+ return hexToRgb(props.activeColor)
114
+ })
115
+
116
+ /** 点击切换 tab */
117
+ const handleClick = (item: TabItem, index: number, e: UniPointerEvent) => {
118
+ if (item.disabled) return
119
+
120
+ model.value = item.key
121
+ emit('update:activeKey', item.key)
122
+ emit('change', item.key)
123
+ emit('tabClick', item.key, item)
124
+
125
+ // 滚动到激活项
126
+ if (props.scrollable) {
127
+ nextTick(() => {
128
+ })
129
+ }
130
+ }
131
+
132
+ </script>
133
+
134
+ <template>
135
+ <view
136
+ class="nav-tabs"
137
+ :class="[type, centered ? 'centered' : '', `size-${size}`]"
138
+ :style="{
139
+ '--active-color': activeColor,
140
+ '--active-color-rgb': activeColorRgb
141
+ }"
142
+ >
143
+ <scroll-view
144
+ v-if="tabPosition === 'top'"
145
+ ref="navListRef"
146
+ class="nav-scroll"
147
+ :scroll-x="true"
148
+ :show-scrollbar="false"
149
+ >
150
+ <view class="nav-list">
151
+ <view
152
+ v-for="(item, index) in items"
153
+ :key="item.key"
154
+ class="nav-item"
155
+ :class="{
156
+ active: item.key === model,
157
+ disabled: item.disabled
158
+ }"
159
+ :ref="'navItem' + index"
160
+ @click="handleClick(item, index, $event)"
161
+ >
162
+ <image v-if="item.icon" class="nav-icon" :src="item.icon" mode="aspectFit" />
163
+
164
+ <text
165
+ class="nav-label"
166
+ :style="{ color: item.key === model ? activeColor : inactiveColor }"
167
+ >
168
+ {{ item.label }}
169
+ </text>
170
+
171
+ <view v-if="item.badge" class="nav-badge">
172
+ {{ item.badge }}
173
+ </view>
174
+ </view>
175
+ </view>
176
+ </scroll-view>
177
+
178
+ <!-- 内容插槽 -->
179
+ <view class="tab-content">
180
+ <slot :active-key="model"></slot>
181
+ </view>
182
+
183
+ <!-- 底部标签 -->
184
+ <scroll-view
185
+ v-if="tabPosition === 'bottom'"
186
+ ref="navListRef"
187
+ class="nav-scroll"
188
+ :scroll-x="true"
189
+ :show-scrollbar="false"
190
+ >
191
+ <view class="nav-list">
192
+ <view
193
+ v-for="(item, index) in items"
194
+ :key="item.key"
195
+ class="nav-item"
196
+ :class="{
197
+ active: item.key === model,
198
+ disabled: item.disabled
199
+ }"
200
+ @click="handleClick(item, index, $event)"
201
+ >
202
+ <image v-if="item.icon" class="nav-icon" :src="item.icon" mode="aspectFit" />
203
+
204
+ <text
205
+ class="nav-label"
206
+ :style="{ color: item.key === model ? activeColor : inactiveColor }"
207
+ >
208
+ {{ item.label }}
209
+ </text>
210
+
211
+ <view v-if="item.badge" class="nav-badge">
212
+ {{ item.badge }}
213
+ </view>
214
+ </view>
215
+
216
+ <!-- 滑动指示器 -->
217
+ <view
218
+ v-if="type === 'line' && animated && activeIndex >= 0"
219
+ class="nav-line"
220
+ :style="[{ backgroundColor: activeColor }]"
221
+ ></view>
222
+ </view>
223
+ </scroll-view>
224
+ </view>
225
+ </template>
226
+
227
+ <style scoped lang="scss">
228
+ .nav-tabs {
229
+ width: 100%;
230
+ background-color: #fff;
231
+ overflow: hidden;
232
+
233
+ &.centered {
234
+ .nav-list {
235
+ justify-content: center;
236
+ }
237
+ }
238
+
239
+ .nav-scroll {
240
+ width: 100%;
241
+ white-space: nowrap;
242
+ }
243
+
244
+ .nav-list {
245
+ display: flex;
246
+ align-items: center;
247
+ flex-direction: row;
248
+ position: relative;
249
+ padding: 20rpx 0;
250
+ overflow: overlay;
251
+ scrollbar-width: none;
252
+
253
+ &::-webkit-scrollbar {
254
+ display: none;
255
+ }
256
+ }
257
+
258
+ .nav-item {
259
+ position: relative;
260
+ display: inline-flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ padding: 16rpx 28rpx;
264
+ transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
265
+ transform-origin: center;
266
+
267
+ &.active {
268
+ transform: scale(1.05);
269
+ }
270
+
271
+ &.disabled {
272
+ opacity: 0.5;
273
+ cursor: not-allowed;
274
+ }
275
+
276
+ .nav-icon {
277
+ width: 36rpx;
278
+ height: 36rpx;
279
+ margin-right: 8rpx;
280
+ transition: all 0.3s;
281
+ }
282
+
283
+ .nav-label {
284
+ font-size: 28rpx;
285
+ transition: color 0.3s, transform 0.3s;
286
+ }
287
+
288
+ .nav-badge {
289
+ position: absolute;
290
+ top: 6rpx;
291
+ right: 12rpx;
292
+ min-width: 28rpx;
293
+ height: 28rpx;
294
+ border-radius: 14rpx;
295
+ background-color: #f56c6c;
296
+ color: #fff;
297
+ font-size: 20rpx;
298
+ line-height: 28rpx;
299
+ text-align: center;
300
+ padding: 0 6rpx;
301
+ transform: translateX(30%);
302
+ }
303
+ }
304
+
305
+ /* 滑动指示器 */
306
+ .nav-line {
307
+ position: absolute;
308
+ bottom: 0;
309
+ height: 4rpx;
310
+ border-radius: 2rpx;
311
+ background-color: var(--active-color);
312
+ transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
313
+ opacity: 0;
314
+
315
+ &.animated {
316
+ opacity: 1;
317
+ }
318
+ }
319
+
320
+ /* 卡片样式 */
321
+ &.card {
322
+ .nav-list {
323
+ padding: 16rpx 24rpx;
324
+ background-color: #f5f5f5;
325
+ border-radius: 12rpx 12rpx 0 0;
326
+ }
327
+
328
+ .nav-item {
329
+ border: 1rpx solid #e8e8e8;
330
+ border-radius: 8rpx;
331
+ margin: 0 8rpx;
332
+ padding: 16rpx 32rpx;
333
+ background-color: #fff;
334
+ transition: all 0.3s ease;
335
+
336
+ &.active {
337
+ /* 使用 CSS 变量控制的激活颜色 */
338
+ background-color: rgba(var(--active-color-rgb, 0, 187, 167), 0.1);
339
+ border-color: var(--active-color);
340
+ color: var(--active-color);
341
+ transform: translateY(-2rpx);
342
+ box-shadow: 0 4rpx 12rpx rgba(var(--active-color-rgb, 0, 187, 167), 0.2);
343
+ }
344
+ }
345
+ }
346
+
347
+ /* 尺寸变体 */
348
+ &.size-small .nav-item {
349
+ padding: 12rpx 20rpx;
350
+
351
+ .nav-label {
352
+ font-size: 24rpx;
353
+ }
354
+ }
355
+
356
+ &.size-large .nav-item {
357
+ padding: 20rpx 40rpx;
358
+
359
+ .nav-label {
360
+ font-size: 32rpx;
361
+ }
362
+ }
363
+
364
+ /* 底部位置 */
365
+ &[tab-position="bottom"] {
366
+ flex-direction: column-reverse;
367
+
368
+ .nav-list {
369
+ border-top: 1rpx solid #e8e8e8;
370
+ border-bottom: none;
371
+ }
372
+
373
+ .nav-line {
374
+ top: 0;
375
+ bottom: auto;
376
+ }
377
+ }
378
+
379
+ .tab-content {
380
+ background-color: #fff;
381
+ }
382
+ }
383
+ </style>
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Tab 选项项
3
+ */
4
+ export type TabItem = {
5
+ key: string |number,
6
+ label: string
7
+ disabled?: boolean
8
+ badge?: number | string
9
+ icon?: string
10
+ }