niuma-ui 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 (220) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +25 -0
  4. package/README.en.md +99 -0
  5. package/README.md +161 -0
  6. package/package.json +106 -0
  7. package/src/__tests__/RsBadge.spec.ts +40 -0
  8. package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
  9. package/src/__tests__/RsButton.spec.ts +108 -0
  10. package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
  11. package/src/__tests__/RsCard.spec.ts +97 -0
  12. package/src/__tests__/RsCheckbox.spec.ts +36 -0
  13. package/src/__tests__/RsCodeEditor.spec.ts +111 -0
  14. package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
  15. package/src/__tests__/RsContainer.spec.ts +89 -0
  16. package/src/__tests__/RsContextMenu.spec.ts +118 -0
  17. package/src/__tests__/RsDatePicker.spec.ts +88 -0
  18. package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
  19. package/src/__tests__/RsDialog.spec.ts +404 -0
  20. package/src/__tests__/RsDrawer.spec.ts +145 -0
  21. package/src/__tests__/RsDropdown.spec.ts +135 -0
  22. package/src/__tests__/RsEmpty.spec.ts +99 -0
  23. package/src/__tests__/RsForm.spec.ts +173 -0
  24. package/src/__tests__/RsIcon.spec.ts +244 -0
  25. package/src/__tests__/RsInput.spec.ts +269 -0
  26. package/src/__tests__/RsInputNumber.spec.ts +118 -0
  27. package/src/__tests__/RsLabel.spec.ts +106 -0
  28. package/src/__tests__/RsLink.spec.ts +49 -0
  29. package/src/__tests__/RsLoading.spec.ts +84 -0
  30. package/src/__tests__/RsMarkdown.spec.ts +143 -0
  31. package/src/__tests__/RsMenu.spec.ts +119 -0
  32. package/src/__tests__/RsNewComponents.spec.ts +17 -0
  33. package/src/__tests__/RsPagination.spec.ts +158 -0
  34. package/src/__tests__/RsPopover.spec.ts +126 -0
  35. package/src/__tests__/RsScrollbar.spec.ts +53 -0
  36. package/src/__tests__/RsSelect.spec.ts +291 -0
  37. package/src/__tests__/RsSidebar.spec.ts +119 -0
  38. package/src/__tests__/RsSplitPane.spec.ts +177 -0
  39. package/src/__tests__/RsStatCard.spec.ts +79 -0
  40. package/src/__tests__/RsSteps.spec.ts +125 -0
  41. package/src/__tests__/RsTable.perf.spec.ts +103 -0
  42. package/src/__tests__/RsTable.spec.ts +1135 -0
  43. package/src/__tests__/RsTabs.spec.ts +328 -0
  44. package/src/__tests__/RsTerminal.spec.ts +218 -0
  45. package/src/__tests__/RsTimePicker.spec.ts +77 -0
  46. package/src/__tests__/RsToaster.spec.ts +95 -0
  47. package/src/__tests__/RsToolbar.spec.ts +75 -0
  48. package/src/__tests__/RsTooltip.spec.ts +111 -0
  49. package/src/__tests__/RsTree.spec.ts +572 -0
  50. package/src/__tests__/RsUpload.spec.ts +160 -0
  51. package/src/__tests__/RsVirtualList.spec.ts +77 -0
  52. package/src/__tests__/date-picker-utils.spec.ts +70 -0
  53. package/src/__tests__/dialog-viewport.spec.ts +38 -0
  54. package/src/__tests__/iso-local-datetime.spec.ts +58 -0
  55. package/src/__tests__/rs-clipboard.spec.ts +142 -0
  56. package/src/__tests__/setup.ts +12 -0
  57. package/src/__tests__/split-pane-utils.spec.ts +136 -0
  58. package/src/__tests__/table-drag.spec.ts +35 -0
  59. package/src/__tests__/table-edit-utils.spec.ts +194 -0
  60. package/src/__tests__/table-utils.spec.ts +286 -0
  61. package/src/__tests__/theme.spec.ts +22 -0
  62. package/src/__tests__/tree-utils.spec.ts +234 -0
  63. package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
  64. package/src/components/RsAvatar.vue +115 -0
  65. package/src/components/RsBadge.vue +59 -0
  66. package/src/components/RsBreadcrumb.vue +65 -0
  67. package/src/components/RsBreadcrumbItems.vue +36 -0
  68. package/src/components/RsButton.vue +393 -0
  69. package/src/components/RsCalendarGrid.vue +284 -0
  70. package/src/components/RsCard.vue +237 -0
  71. package/src/components/RsCheckbox.vue +189 -0
  72. package/src/components/RsCodeBlock.vue +278 -0
  73. package/src/components/RsCodeEditor.vue +597 -0
  74. package/src/components/RsConfigProvider.vue +92 -0
  75. package/src/components/RsConfirmDialog.vue +178 -0
  76. package/src/components/RsContainer.vue +235 -0
  77. package/src/components/RsContextMenu.vue +238 -0
  78. package/src/components/RsContextMenuItems.vue +77 -0
  79. package/src/components/RsDatePicker.vue +756 -0
  80. package/src/components/RsDateTimePicker.vue +42 -0
  81. package/src/components/RsDialog.vue +486 -0
  82. package/src/components/RsDrawer.vue +175 -0
  83. package/src/components/RsDropdown.vue +252 -0
  84. package/src/components/RsDropdownItems.vue +90 -0
  85. package/src/components/RsEmpty.vue +107 -0
  86. package/src/components/RsForm.vue +131 -0
  87. package/src/components/RsIcon.vue +104 -0
  88. package/src/components/RsInput.vue +1101 -0
  89. package/src/components/RsInputNumber.vue +532 -0
  90. package/src/components/RsLabel.vue +54 -0
  91. package/src/components/RsLink.vue +106 -0
  92. package/src/components/RsLoading.vue +171 -0
  93. package/src/components/RsMarkdown.vue +329 -0
  94. package/src/components/RsMenu.vue +256 -0
  95. package/src/components/RsMenuItems.vue +125 -0
  96. package/src/components/RsMonacoEditor.vue +541 -0
  97. package/src/components/RsPagination.vue +184 -0
  98. package/src/components/RsPopover.vue +77 -0
  99. package/src/components/RsProseEditor.vue +160 -0
  100. package/src/components/RsScrollbar.vue +171 -0
  101. package/src/components/RsSelect.vue +1045 -0
  102. package/src/components/RsSidebar.vue +121 -0
  103. package/src/components/RsSidebarGroup.vue +32 -0
  104. package/src/components/RsSidebarItem.vue +72 -0
  105. package/src/components/RsSplitPane.vue +619 -0
  106. package/src/components/RsStatCard.vue +85 -0
  107. package/src/components/RsSteps.vue +138 -0
  108. package/src/components/RsTable.vue +2664 -0
  109. package/src/components/RsTableColgroup.vue +17 -0
  110. package/src/components/RsTableHeaderRow.vue +30 -0
  111. package/src/components/RsTabs.vue +951 -0
  112. package/src/components/RsTerminal.vue +697 -0
  113. package/src/components/RsTimePicker.vue +554 -0
  114. package/src/components/RsTimePickerColumns.vue +226 -0
  115. package/src/components/RsToaster.vue +396 -0
  116. package/src/components/RsToolbar.vue +135 -0
  117. package/src/components/RsTooltip.vue +142 -0
  118. package/src/components/RsTooltipProvider.vue +20 -0
  119. package/src/components/RsTree.vue +1248 -0
  120. package/src/components/RsUpload.vue +279 -0
  121. package/src/components/RsVirtualList.vue +217 -0
  122. package/src/components/avatar-utils.ts +19 -0
  123. package/src/components/breadcrumb-utils.ts +19 -0
  124. package/src/components/class-utils.ts +29 -0
  125. package/src/components/code-editor-utils.ts +106 -0
  126. package/src/components/code-mirror-completion.ts +40 -0
  127. package/src/components/code-mirror-diagnostics.ts +52 -0
  128. package/src/components/code-mirror-ghost.ts +158 -0
  129. package/src/components/code-mirror-inline-edit.ts +55 -0
  130. package/src/components/code-mirror-intel.ts +81 -0
  131. package/src/components/code-mirror-lang.ts +128 -0
  132. package/src/components/context-menu-utils.ts +20 -0
  133. package/src/components/date-picker-utils.ts +284 -0
  134. package/src/components/date-range-validation.ts +29 -0
  135. package/src/components/date-validation.ts +38 -0
  136. package/src/components/dialog-viewport.ts +48 -0
  137. package/src/components/dialog-window.ts +253 -0
  138. package/src/components/dropdown-utils.ts +31 -0
  139. package/src/components/form-utils.ts +77 -0
  140. package/src/components/input-number-utils.ts +128 -0
  141. package/src/components/input-rules.ts +84 -0
  142. package/src/components/loading-utils.ts +8 -0
  143. package/src/components/markdown-utils.ts +126 -0
  144. package/src/components/menu-utils.ts +48 -0
  145. package/src/components/overlay-utils.ts +47 -0
  146. package/src/components/pagination-utils.ts +38 -0
  147. package/src/components/popover-utils.ts +8 -0
  148. package/src/components/reka.ts +106 -0
  149. package/src/components/resolve-radius.ts +22 -0
  150. package/src/components/resolve-size.ts +29 -0
  151. package/src/components/scrollbar-utils.ts +10 -0
  152. package/src/components/select-utils.ts +72 -0
  153. package/src/components/split-pane-utils.ts +187 -0
  154. package/src/components/steps-utils.ts +22 -0
  155. package/src/components/table/RsTableBodyRow.vue +287 -0
  156. package/src/components/table/RsTableCell.vue +239 -0
  157. package/src/components/table/RsTableCellContent.ts +18 -0
  158. package/src/components/table/RsTableCellEditor.vue +351 -0
  159. package/src/components/table/RsTableEditGutter.vue +54 -0
  160. package/src/components/table/RsTableHeaderFilter.vue +107 -0
  161. package/src/components/table/RsTableRowStatus.vue +69 -0
  162. package/src/components/table/rs-table.css +1141 -0
  163. package/src/components/table/table-context-menu.ts +53 -0
  164. package/src/components/table/table-edit-utils.ts +496 -0
  165. package/src/components/table/table-keys.ts +12 -0
  166. package/src/components/table-drag.ts +215 -0
  167. package/src/components/table-utils.ts +950 -0
  168. package/src/components/tabs-utils.ts +107 -0
  169. package/src/components/terminal-utils.ts +301 -0
  170. package/src/components/terminal-wheel.ts +49 -0
  171. package/src/components/time-picker-utils.ts +167 -0
  172. package/src/components/time-range-validation.ts +23 -0
  173. package/src/components/time-validation.ts +20 -0
  174. package/src/components/tree-utils.ts +518 -0
  175. package/src/components/upload-utils.ts +55 -0
  176. package/src/components/virtual-list-utils.ts +17 -0
  177. package/src/composables/useClickOutside.ts +22 -0
  178. package/src/composables/useRsConfig.ts +91 -0
  179. package/src/composables/useRsI18n.ts +22 -0
  180. package/src/composables/useRsTabsNav.ts +187 -0
  181. package/src/composables/useRsToast.ts +48 -0
  182. package/src/composables/useTableCellTooltip.ts +239 -0
  183. package/src/composables/useTableEdit.ts +271 -0
  184. package/src/composables/useTableRowHighlight.ts +50 -0
  185. package/src/dev/vite-codemirror-deps.ts +20 -0
  186. package/src/icons/custom/api.ts +54 -0
  187. package/src/icons/custom/clickhouse.ts +52 -0
  188. package/src/icons/custom/dameng.ts +83 -0
  189. package/src/icons/custom/ftp.ts +61 -0
  190. package/src/icons/custom/kingbase.ts +52 -0
  191. package/src/icons/custom/mongodb.ts +50 -0
  192. package/src/icons/custom/mysql.ts +51 -0
  193. package/src/icons/custom/oracle.ts +52 -0
  194. package/src/icons/custom/redis.ts +51 -0
  195. package/src/icons/custom/sqlite.ts +50 -0
  196. package/src/icons/custom/sqlserver.ts +52 -0
  197. package/src/icons/custom/vastbase.ts +57 -0
  198. package/src/icons/lucide.ts +78 -0
  199. package/src/icons/registry.ts +35 -0
  200. package/src/index.ts +425 -0
  201. package/src/lib/iso-local-datetime.ts +152 -0
  202. package/src/lib/rs-dayjs.ts +56 -0
  203. package/src/locale/messages.ts +362 -0
  204. package/src/locale/types.ts +5 -0
  205. package/src/monaco/debug-decorations.css +20 -0
  206. package/src/monaco/debug-decorations.ts +70 -0
  207. package/src/monaco/index.ts +22 -0
  208. package/src/monaco/languages/index.ts +21 -0
  209. package/src/monaco/languages/mongodb-shell.ts +125 -0
  210. package/src/monaco/languages/types.ts +55 -0
  211. package/src/monaco/setup.ts +101 -0
  212. package/src/styles.css +1040 -0
  213. package/src/theme/apply.ts +12 -0
  214. package/src/theme/brand.example.css +37 -0
  215. package/src/theme/presets.ts +69 -0
  216. package/src/theme/types.ts +81 -0
  217. package/src/utils/rs-clipboard.ts +272 -0
  218. package/src/vite-env.d.ts +7 -0
  219. package/vite-plugins/monaco-zh-nls.ts +101 -0
  220. package/vite-plugins/silence-antlr-parse-console.ts +29 -0
@@ -0,0 +1,951 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, ref } from 'vue'
3
+ import {
4
+ DropdownMenuContent,
5
+ DropdownMenuItem,
6
+ DropdownMenuPortal,
7
+ DropdownMenuRoot,
8
+ DropdownMenuTrigger,
9
+ TabsContent,
10
+ TabsList,
11
+ TabsRoot,
12
+ TabsTrigger,
13
+ } from './reka'
14
+ import { useRsI18n } from '../composables/useRsI18n'
15
+ import { useRsTabsNav } from '../composables/useRsTabsNav'
16
+ import RsIcon from './RsIcon.vue'
17
+ import {
18
+ getNextTabAfterClose,
19
+ isTabClosable,
20
+ isTabRenamable,
21
+ type RsTabItem,
22
+ type RsTabsOverflow,
23
+ type RsTabsSize,
24
+ type RsTabsVariant,
25
+ } from './tabs-utils'
26
+
27
+ const model = defineModel<string>({ required: true })
28
+
29
+ const props = withDefaults(
30
+ defineProps<{
31
+ items: RsTabItem[]
32
+ size?: RsTabsSize
33
+ variant?: RsTabsVariant
34
+ panelless?: boolean
35
+ closable?: boolean
36
+ addable?: boolean
37
+ maxCount?: number
38
+ /** 双击标签重命名 */
39
+ renamable?: boolean
40
+ /** 拖拽调整标签顺序 */
41
+ draggable?: boolean
42
+ /** 标签过多:scroll 横向滚动 · dropdown 折叠到「更多」 */
43
+ overflow?: RsTabsOverflow | false
44
+ }>(),
45
+ {
46
+ size: 'md',
47
+ variant: 'line',
48
+ panelless: false,
49
+ closable: false,
50
+ addable: false,
51
+ renamable: false,
52
+ draggable: false,
53
+ overflow: false,
54
+ },
55
+ )
56
+
57
+ const emit = defineEmits<{
58
+ close: [value: string]
59
+ add: []
60
+ rename: [value: string, label: string]
61
+ reorder: [dragValue: string, dropValue: string]
62
+ }>()
63
+
64
+ const { t } = useRsI18n()
65
+ const itemsRef = computed(() => props.items)
66
+ const overflowMode = computed(() => props.overflow)
67
+ const canAdd = computed(() => {
68
+ if (!props.addable) return false
69
+ if (props.maxCount == null) return true
70
+ return props.items.length < props.maxCount
71
+ })
72
+
73
+ const navRef = ref<HTMLElement | null>(null)
74
+ const measureRef = ref<HTMLElement | null>(null)
75
+ const overflowWrapRef = ref<HTMLElement | null>(null)
76
+
77
+ const {
78
+ navViewportRef,
79
+ useDropdownOverflow,
80
+ useScrollOverflow,
81
+ visibleTabItems,
82
+ hiddenItems,
83
+ overflowMenuItems,
84
+ activeInHidden,
85
+ showOverflowMenu,
86
+ canScrollPrev,
87
+ canScrollNext,
88
+ scrollNav,
89
+ onNavScroll,
90
+ scheduleLayout,
91
+ } = useRsTabsNav({
92
+ items: itemsRef,
93
+ activeValue: model,
94
+ overflow: overflowMode,
95
+ canAdd,
96
+ navRef,
97
+ measureRef,
98
+ overflowRef: overflowWrapRef,
99
+ addButtonWidth: 36,
100
+ })
101
+
102
+ const renamingValue = ref<string | null>(null)
103
+ const renameDraft = ref('')
104
+ const dragValue = ref<string | null>(null)
105
+ const dragOverValue = ref<string | null>(null)
106
+ let renameInputEl: HTMLInputElement | null = null
107
+
108
+ function setRenameInputRef(el: unknown, item: RsTabItem) {
109
+ if (!(el instanceof HTMLInputElement) || renamingValue.value !== item.value) return
110
+ renameInputEl = el
111
+ void nextTick(() => {
112
+ renameInputEl?.focus()
113
+ renameInputEl?.select()
114
+ })
115
+ }
116
+
117
+ const tabBarItems = computed(() =>
118
+ useDropdownOverflow.value ? visibleTabItems.value : props.items,
119
+ )
120
+ const moreLabel = computed(() => {
121
+ if (activeInHidden.value) {
122
+ return props.items.find((item) => item.value === model.value)?.label ?? t('tabs.more')
123
+ }
124
+ if (hiddenItems.value.length > 0) {
125
+ return `${t('tabs.more')} (${hiddenItems.value.length})`
126
+ }
127
+ return t('tabs.more')
128
+ })
129
+
130
+ function closeAriaLabel(item: RsTabItem): string {
131
+ return t('tabs.close', 'Close {label}').replace('{label}', item.label)
132
+ }
133
+
134
+ function onCloseTab(item: RsTabItem): void {
135
+ if (item.disabled || !isTabClosable(item, props.closable)) return
136
+ const next = getNextTabAfterClose(props.items, item.value, model.value)
137
+ if (next && model.value === item.value) {
138
+ model.value = next
139
+ }
140
+ emit('close', item.value)
141
+ }
142
+
143
+ function onAddTab(): void {
144
+ if (!canAdd.value) return
145
+ emit('add')
146
+ }
147
+
148
+ function onOverflowSelect(value: string): void {
149
+ model.value = value
150
+ }
151
+
152
+ async function startRename(item: RsTabItem): Promise<void> {
153
+ if (!isTabRenamable(item, props.renamable) || item.disabled) return
154
+ renamingValue.value = item.value
155
+ renameDraft.value = item.label
156
+ }
157
+
158
+ function cancelRename(): void {
159
+ renamingValue.value = null
160
+ renameDraft.value = ''
161
+ renameInputEl = null
162
+ }
163
+
164
+ function commitRename(item: RsTabItem): void {
165
+ if (renamingValue.value !== item.value) return
166
+ const label = renameDraft.value.trim()
167
+ if (label && label !== item.label) {
168
+ emit('rename', item.value, label)
169
+ }
170
+ cancelRename()
171
+ }
172
+
173
+ function onDragStart(item: RsTabItem, event: DragEvent): void {
174
+ if (!props.draggable || item.disabled) return
175
+ dragValue.value = item.value
176
+ event.dataTransfer?.setData('text/plain', item.value)
177
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'
178
+ }
179
+
180
+ function onDragOver(item: RsTabItem, event: DragEvent): void {
181
+ if (!props.draggable || !dragValue.value || dragValue.value === item.value) return
182
+ event.preventDefault()
183
+ dragOverValue.value = item.value
184
+ }
185
+
186
+ function onDragLeave(item: RsTabItem): void {
187
+ if (dragOverValue.value === item.value) dragOverValue.value = null
188
+ }
189
+
190
+ function onDrop(item: RsTabItem, event: DragEvent): void {
191
+ if (!props.draggable || !dragValue.value) return
192
+ event.preventDefault()
193
+ if (dragValue.value !== item.value) {
194
+ emit('reorder', dragValue.value, item.value)
195
+ }
196
+ dragValue.value = null
197
+ dragOverValue.value = null
198
+ scheduleLayout()
199
+ }
200
+
201
+ function onDragEnd(): void {
202
+ dragValue.value = null
203
+ dragOverValue.value = null
204
+ }
205
+ </script>
206
+
207
+ <template>
208
+ <TabsRoot
209
+ v-model="model"
210
+ class="rs-tabs"
211
+ :class="[
212
+ `rs-tabs--${variant}`,
213
+ `rs-tabs--${size}`,
214
+ {
215
+ 'rs-tabs--panelless': panelless,
216
+ 'rs-tabs--scrollable': useScrollOverflow,
217
+ 'rs-tabs--dropdown-overflow': useDropdownOverflow,
218
+ },
219
+ ]"
220
+ :aria-label="t('tabs.label')"
221
+ >
222
+ <div :class="panelless ? 'rs-tabs__shell' : 'rs-tabs__body'">
223
+ <div
224
+ v-if="useDropdownOverflow"
225
+ ref="measureRef"
226
+ class="rs-tabs__measure"
227
+ aria-hidden="true"
228
+ >
229
+ <button
230
+ v-for="item in items"
231
+ :key="`measure-${item.value}`"
232
+ type="button"
233
+ class="rs-tabs__trigger rs-tabs__measure-trigger"
234
+ :class="`rs-tabs__trigger--${size}`"
235
+ :data-tab-value="item.value"
236
+ tabindex="-1"
237
+ >
238
+ <RsIcon v-if="item.icon" :name="item.icon" :size="14" class="rs-tabs__icon" />
239
+ <span class="rs-tabs__label">{{ item.label }}</span>
240
+ <span v-if="item.badge != null && item.badge !== ''" class="rs-tabs__badge">
241
+ {{ item.badge }}
242
+ </span>
243
+ </button>
244
+ </div>
245
+
246
+ <div ref="navRef" class="rs-tabs__nav">
247
+ <button
248
+ v-if="useScrollOverflow && canScrollPrev"
249
+ type="button"
250
+ class="rs-tabs__scroll-btn rs-tabs__scroll-btn--prev"
251
+ :aria-label="t('tabs.scrollPrev')"
252
+ @click="scrollNav(-1)"
253
+ >
254
+ <RsIcon name="chevron-left" :size="14" />
255
+ </button>
256
+
257
+ <div
258
+ ref="navViewportRef"
259
+ class="rs-tabs__nav-viewport"
260
+ @scroll="onNavScroll"
261
+ >
262
+ <TabsList class="rs-tabs__list">
263
+ <TabsTrigger
264
+ v-for="item in tabBarItems"
265
+ :key="item.value"
266
+ :value="item.value"
267
+ :disabled="item.disabled"
268
+ class="rs-tabs__trigger"
269
+ :class="{
270
+ 'rs-tabs__trigger--dragging': dragValue === item.value,
271
+ 'rs-tabs__trigger--drag-over': dragOverValue === item.value,
272
+ }"
273
+ :data-tab-value="item.value"
274
+ @dragover="onDragOver(item, $event)"
275
+ @dragleave="onDragLeave(item)"
276
+ @drop="onDrop(item, $event)"
277
+ >
278
+ <span
279
+ v-if="draggable"
280
+ class="rs-tabs__drag-handle"
281
+ :aria-label="t('tabs.dragHandle')"
282
+ draggable="true"
283
+ @dragstart="onDragStart(item, $event)"
284
+ @dragend="onDragEnd"
285
+ @mousedown.stop
286
+ @click.stop
287
+ >
288
+ <RsIcon name="grip-vertical" :size="12" />
289
+ </span>
290
+ <RsIcon v-if="item.icon" :name="item.icon" :size="14" class="rs-tabs__icon" />
291
+ <input
292
+ v-if="renamingValue === item.value"
293
+ :ref="(el) => setRenameInputRef(el, item)"
294
+ v-model="renameDraft"
295
+ class="rs-tabs__rename-input"
296
+ :aria-label="t('tabs.rename')"
297
+ :placeholder="t('tabs.renamePlaceholder')"
298
+ @keydown.enter.prevent="commitRename(item)"
299
+ @keydown.escape.prevent="cancelRename"
300
+ @blur="commitRename(item)"
301
+ @mousedown.stop
302
+ @click.stop
303
+ @dblclick.stop
304
+ />
305
+ <span
306
+ v-else
307
+ class="rs-tabs__label"
308
+ @dblclick.stop="startRename(item)"
309
+ >
310
+ {{ item.label }}
311
+ </span>
312
+ <span v-if="item.badge != null && item.badge !== ''" class="rs-tabs__badge">
313
+ {{ item.badge }}
314
+ </span>
315
+ <button
316
+ v-if="isTabClosable(item, closable)"
317
+ type="button"
318
+ class="rs-tabs__close"
319
+ :aria-label="closeAriaLabel(item)"
320
+ :disabled="item.disabled"
321
+ @mousedown.stop.prevent
322
+ @click.stop="onCloseTab(item)"
323
+ >
324
+ <RsIcon name="x" :size="12" />
325
+ </button>
326
+ </TabsTrigger>
327
+ </TabsList>
328
+ </div>
329
+
330
+ <div
331
+ v-if="showOverflowMenu"
332
+ ref="overflowWrapRef"
333
+ class="rs-tabs__overflow-wrap"
334
+ >
335
+ <DropdownMenuRoot>
336
+ <DropdownMenuTrigger
337
+ class="rs-tabs__more"
338
+ :class="{ 'rs-tabs__more--active': activeInHidden }"
339
+ >
340
+ <span class="rs-tabs__more-label">{{ moreLabel }}</span>
341
+ <RsIcon name="chevron-down" :size="14" class="rs-tabs__more-icon" />
342
+ </DropdownMenuTrigger>
343
+ <DropdownMenuPortal>
344
+ <DropdownMenuContent
345
+ class="rs-tabs__more-menu"
346
+ :side-offset="4"
347
+ align="end"
348
+ >
349
+ <DropdownMenuItem
350
+ v-for="item in overflowMenuItems"
351
+ :key="item.value"
352
+ class="rs-tabs__more-item"
353
+ :class="{ 'rs-tabs__more-item--active': model === item.value }"
354
+ @select="onOverflowSelect(item.value)"
355
+ >
356
+ {{ item.label }}
357
+ </DropdownMenuItem>
358
+ </DropdownMenuContent>
359
+ </DropdownMenuPortal>
360
+ </DropdownMenuRoot>
361
+ </div>
362
+
363
+ <button
364
+ v-if="canAdd"
365
+ type="button"
366
+ class="rs-tabs__add"
367
+ :aria-label="t('tabs.add')"
368
+ @click="onAddTab"
369
+ >
370
+ <RsIcon name="plus" :size="14" />
371
+ </button>
372
+
373
+ <button
374
+ v-if="useScrollOverflow && canScrollNext"
375
+ type="button"
376
+ class="rs-tabs__scroll-btn rs-tabs__scroll-btn--next"
377
+ :aria-label="t('tabs.scrollNext')"
378
+ @click="scrollNav(1)"
379
+ >
380
+ <RsIcon name="chevron-right" :size="14" />
381
+ </button>
382
+ </div>
383
+
384
+ <template v-if="!panelless">
385
+ <TabsContent
386
+ v-for="item in items"
387
+ :key="`panel-${item.value}`"
388
+ :value="item.value"
389
+ class="rs-tabs__panel"
390
+ >
391
+ <div class="rs-tabs__panel-inner">
392
+ <slot :name="item.value" />
393
+ </div>
394
+ </TabsContent>
395
+ </template>
396
+ </div>
397
+ </TabsRoot>
398
+ </template>
399
+
400
+ <style>
401
+ .rs-tabs {
402
+ display: flex;
403
+ flex-direction: column;
404
+ width: 100%;
405
+ }
406
+
407
+ .rs-tabs__shell,
408
+ .rs-tabs__body {
409
+ display: flex;
410
+ flex-direction: column;
411
+ width: 100%;
412
+ position: relative;
413
+ }
414
+
415
+ .rs-tabs__nav {
416
+ display: flex;
417
+ align-items: stretch;
418
+ min-width: 0;
419
+ }
420
+
421
+ .rs-tabs__nav-viewport {
422
+ flex: 1;
423
+ min-width: 0;
424
+ }
425
+
426
+ .rs-tabs--dropdown-overflow .rs-tabs__nav-viewport {
427
+ overflow: hidden;
428
+ }
429
+
430
+ .rs-tabs__measure {
431
+ position: absolute;
432
+ visibility: hidden;
433
+ pointer-events: none;
434
+ height: 0;
435
+ overflow: hidden;
436
+ white-space: nowrap;
437
+ }
438
+
439
+ .rs-tabs__measure-trigger {
440
+ pointer-events: none;
441
+ }
442
+
443
+ .rs-tabs--scrollable .rs-tabs__nav-viewport {
444
+ overflow-x: auto;
445
+ scrollbar-width: none;
446
+ }
447
+
448
+ .rs-tabs--scrollable .rs-tabs__nav-viewport::-webkit-scrollbar {
449
+ display: none;
450
+ }
451
+
452
+ .rs-tabs__list {
453
+ display: flex;
454
+ flex-wrap: wrap;
455
+ align-items: stretch;
456
+ margin: 0;
457
+ padding: 0;
458
+ list-style: none;
459
+ }
460
+
461
+ .rs-tabs--scrollable .rs-tabs__list,
462
+ .rs-tabs--dropdown-overflow .rs-tabs__list {
463
+ flex-wrap: nowrap;
464
+ }
465
+
466
+ .rs-tabs__trigger {
467
+ display: inline-flex;
468
+ align-items: center;
469
+ gap: 0.375rem;
470
+ border: none;
471
+ background: transparent;
472
+ color: var(--rs-muted);
473
+ font-size: var(--rs-font-size-sm);
474
+ line-height: var(--rs-line-height-tight);
475
+ white-space: nowrap;
476
+ cursor: pointer;
477
+ transition:
478
+ color var(--rs-transition-fast),
479
+ background var(--rs-transition-fast),
480
+ box-shadow var(--rs-transition-fast);
481
+ }
482
+
483
+ .rs-tabs__trigger:focus-visible {
484
+ outline: none;
485
+ box-shadow: inset 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
486
+ border-radius: var(--rs-radius-xs);
487
+ }
488
+
489
+ .rs-tabs__trigger:disabled {
490
+ opacity: 0.38;
491
+ cursor: not-allowed;
492
+ }
493
+
494
+ .rs-tabs__trigger--dragging {
495
+ opacity: 0.45;
496
+ }
497
+
498
+ .rs-tabs__trigger--drag-over {
499
+ box-shadow: inset 0 -2px 0 0 var(--rs-primary);
500
+ }
501
+
502
+ .rs-tabs__drag-handle {
503
+ display: inline-flex;
504
+ align-items: center;
505
+ color: var(--rs-placeholder);
506
+ cursor: grab;
507
+ }
508
+
509
+ .rs-tabs__drag-handle:active {
510
+ cursor: grabbing;
511
+ }
512
+
513
+ .rs-tabs__icon {
514
+ flex: 0 0 auto;
515
+ color: currentColor;
516
+ }
517
+
518
+ .rs-tabs__label {
519
+ min-width: 0;
520
+ }
521
+
522
+ .rs-tabs__rename-input {
523
+ width: 6.5rem;
524
+ max-width: 12rem;
525
+ min-height: 1.5rem;
526
+ padding: 0 0.375rem;
527
+ border: 1px solid var(--rs-border);
528
+ border-radius: var(--rs-radius-xs);
529
+ background: var(--rs-bg);
530
+ color: var(--rs-text);
531
+ font: inherit;
532
+ }
533
+
534
+ .rs-tabs__rename-input:focus {
535
+ outline: none;
536
+ border-color: var(--rs-focus-border, var(--rs-primary));
537
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
538
+ }
539
+
540
+ .rs-tabs__badge {
541
+ padding: 0 0.375rem;
542
+ border-radius: var(--rs-radius-full);
543
+ background: var(--rs-surface-hover);
544
+ color: var(--rs-muted);
545
+ font-size: var(--rs-font-size-xs);
546
+ font-weight: 500;
547
+ font-variant-numeric: tabular-nums;
548
+ line-height: 1.4;
549
+ }
550
+
551
+ .rs-tabs__trigger[data-state='active'] .rs-tabs__badge {
552
+ background: color-mix(in srgb, var(--rs-primary) 12%, transparent);
553
+ color: var(--rs-primary);
554
+ }
555
+
556
+ .rs-tabs__close {
557
+ display: inline-flex;
558
+ align-items: center;
559
+ justify-content: center;
560
+ width: 1rem;
561
+ height: 1rem;
562
+ flex: 0 0 auto;
563
+ margin-inline-start: 0.125rem;
564
+ padding: 0;
565
+ border: none;
566
+ border-radius: var(--rs-radius-xs);
567
+ background: transparent;
568
+ color: var(--rs-muted);
569
+ cursor: pointer;
570
+ opacity: 0.72;
571
+ transition:
572
+ opacity var(--rs-transition-fast),
573
+ background var(--rs-transition-fast),
574
+ color var(--rs-transition-fast);
575
+ }
576
+
577
+ .rs-tabs__trigger:hover .rs-tabs__close,
578
+ .rs-tabs__trigger[data-state='active'] .rs-tabs__close {
579
+ opacity: 1;
580
+ }
581
+
582
+ .rs-tabs__close:hover:not(:disabled) {
583
+ background: var(--rs-surface-hover);
584
+ color: var(--rs-text);
585
+ }
586
+
587
+ .rs-tabs__close:focus-visible {
588
+ outline: none;
589
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
590
+ }
591
+
592
+ .rs-tabs__close:disabled {
593
+ opacity: 0.38;
594
+ cursor: not-allowed;
595
+ }
596
+
597
+ .rs-tabs__add,
598
+ .rs-tabs__scroll-btn {
599
+ display: inline-flex;
600
+ align-items: center;
601
+ justify-content: center;
602
+ flex: 0 0 auto;
603
+ margin: 0;
604
+ padding: 0 var(--rs-space-sm);
605
+ border: none;
606
+ border-radius: var(--rs-radius-xs);
607
+ background: transparent;
608
+ color: var(--rs-muted);
609
+ cursor: pointer;
610
+ transition:
611
+ color var(--rs-transition-fast),
612
+ background var(--rs-transition-fast);
613
+ }
614
+
615
+ .rs-tabs__add:focus-visible,
616
+ .rs-tabs__scroll-btn:focus-visible {
617
+ outline: none;
618
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
619
+ }
620
+
621
+ .rs-tabs__add:hover,
622
+ .rs-tabs__scroll-btn:hover {
623
+ color: var(--rs-primary);
624
+ background: var(--rs-surface-hover);
625
+ }
626
+
627
+ .rs-tabs__overflow-wrap {
628
+ display: flex;
629
+ flex: 0 0 auto;
630
+ align-items: stretch;
631
+ align-self: stretch;
632
+ margin-inline-start: var(--rs-space-xs);
633
+ padding-inline-start: var(--rs-space-sm);
634
+ border-inline-start: 1px solid var(--rs-border-subtle);
635
+ }
636
+
637
+ .rs-tabs__more {
638
+ display: inline-flex;
639
+ align-items: center;
640
+ gap: 0.25rem;
641
+ min-height: var(--rs-control-height-md);
642
+ padding: 0 var(--rs-space-md);
643
+ border: none;
644
+ border-radius: 0;
645
+ background: transparent;
646
+ color: var(--rs-muted);
647
+ font-size: var(--rs-font-size-sm);
648
+ line-height: var(--rs-line-height-tight);
649
+ white-space: nowrap;
650
+ cursor: pointer;
651
+ position: relative;
652
+ transition:
653
+ color var(--rs-transition-fast),
654
+ background var(--rs-transition-fast);
655
+ }
656
+
657
+ .rs-tabs__more:hover {
658
+ color: var(--rs-text);
659
+ }
660
+
661
+ .rs-tabs__more:focus-visible {
662
+ outline: none;
663
+ box-shadow: inset 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
664
+ border-radius: var(--rs-radius-xs);
665
+ }
666
+
667
+ .rs-tabs__more-label {
668
+ overflow: hidden;
669
+ text-overflow: ellipsis;
670
+ white-space: nowrap;
671
+ max-width: 7rem;
672
+ }
673
+
674
+ .rs-tabs__more-icon {
675
+ flex-shrink: 0;
676
+ color: currentColor;
677
+ opacity: 0.72;
678
+ }
679
+
680
+ .rs-tabs__more--active {
681
+ color: var(--rs-primary);
682
+ font-weight: 500;
683
+ }
684
+
685
+ .rs-tabs__more-menu {
686
+ z-index: var(--rs-z-dropdown);
687
+ min-width: 8rem;
688
+ max-width: 14rem;
689
+ padding: var(--rs-space-xs);
690
+ border-radius: var(--rs-radius);
691
+ border: 1px solid var(--rs-border);
692
+ background: var(--rs-surface-elevated);
693
+ box-shadow: var(--rs-shadow);
694
+ outline: none;
695
+ }
696
+
697
+ .rs-tabs__more-item {
698
+ display: flex;
699
+ align-items: center;
700
+ padding: var(--rs-space-sm) var(--rs-space-md);
701
+ border-radius: var(--rs-radius-sm);
702
+ font-size: var(--rs-font-size-sm);
703
+ line-height: var(--rs-line-height-tight);
704
+ color: var(--rs-text);
705
+ cursor: pointer;
706
+ outline: none;
707
+ }
708
+
709
+ .rs-tabs__more-item[data-highlighted] {
710
+ background: var(--rs-item-hover);
711
+ color: var(--rs-text);
712
+ }
713
+
714
+ .rs-tabs__more-item--active {
715
+ color: var(--rs-primary);
716
+ font-weight: 500;
717
+ background: color-mix(in srgb, var(--rs-primary) 12%, transparent);
718
+ }
719
+
720
+ /* —— line —— */
721
+ .rs-tabs--line .rs-tabs__body {
722
+ border: 1px solid var(--rs-border);
723
+ border-radius: var(--rs-radius);
724
+ background: var(--rs-surface);
725
+ overflow: hidden;
726
+ }
727
+
728
+ .rs-tabs--line .rs-tabs__list {
729
+ gap: 0;
730
+ padding: 0 var(--rs-space-sm);
731
+ border-bottom: 1px solid var(--rs-border-subtle);
732
+ }
733
+
734
+ .rs-tabs--dropdown-overflow.rs-tabs--line .rs-tabs__nav {
735
+ align-items: stretch;
736
+ padding-inline: 0;
737
+ }
738
+
739
+ .rs-tabs--dropdown-overflow.rs-tabs--line .rs-tabs__list {
740
+ flex: 1;
741
+ min-width: 0;
742
+ padding-inline: var(--rs-space-sm);
743
+ border-bottom: none;
744
+ }
745
+
746
+ .rs-tabs--dropdown-overflow.rs-tabs--line .rs-tabs__body {
747
+ padding-top: 0;
748
+ }
749
+
750
+ .rs-tabs--dropdown-overflow.rs-tabs--line .rs-tabs__more {
751
+ margin-bottom: -1px;
752
+ }
753
+
754
+ .rs-tabs--dropdown-overflow.rs-tabs--line .rs-tabs__more--active::after {
755
+ content: '';
756
+ position: absolute;
757
+ inset-inline: var(--rs-space-sm);
758
+ bottom: 0;
759
+ height: 2px;
760
+ border-radius: 2px 2px 0 0;
761
+ background: var(--rs-primary);
762
+ }
763
+
764
+ .rs-tabs--dropdown-overflow.rs-tabs--line .rs-tabs__nav {
765
+ border-bottom: 1px solid var(--rs-border-subtle);
766
+ }
767
+
768
+ .rs-tabs--line.rs-tabs--sm .rs-tabs__more {
769
+ min-height: var(--rs-control-height-sm);
770
+ padding: 0 var(--rs-space-sm);
771
+ font-size: var(--rs-font-size-xs);
772
+ }
773
+
774
+ .rs-tabs--line .rs-tabs__trigger {
775
+ position: relative;
776
+ margin-bottom: -1px;
777
+ border-radius: 0;
778
+ }
779
+
780
+ .rs-tabs--line.rs-tabs--sm .rs-tabs__trigger {
781
+ min-height: var(--rs-control-height-sm);
782
+ padding: 0 var(--rs-space-sm);
783
+ font-size: var(--rs-font-size-xs);
784
+ }
785
+
786
+ .rs-tabs--line.rs-tabs--md .rs-tabs__trigger {
787
+ min-height: var(--rs-control-height-md);
788
+ padding: 0 var(--rs-space-md);
789
+ }
790
+
791
+ .rs-tabs--line .rs-tabs__add,
792
+ .rs-tabs--line .rs-tabs__scroll-btn {
793
+ min-height: var(--rs-control-height-md);
794
+ }
795
+
796
+ .rs-tabs--line.rs-tabs--sm .rs-tabs__add,
797
+ .rs-tabs--line.rs-tabs--sm .rs-tabs__scroll-btn {
798
+ min-height: var(--rs-control-height-sm);
799
+ }
800
+
801
+ .rs-tabs--line .rs-tabs__trigger:hover:not(:disabled):not([data-state='active']) {
802
+ color: var(--rs-text);
803
+ }
804
+
805
+ .rs-tabs--line .rs-tabs__trigger[data-state='active'] {
806
+ color: var(--rs-primary);
807
+ font-weight: 500;
808
+ }
809
+
810
+ .rs-tabs--line .rs-tabs__trigger[data-state='active']::after {
811
+ content: '';
812
+ position: absolute;
813
+ inset-inline: var(--rs-space-sm);
814
+ bottom: 0;
815
+ height: 2px;
816
+ border-radius: 2px 2px 0 0;
817
+ background: var(--rs-primary);
818
+ }
819
+
820
+ .rs-tabs--line .rs-tabs__panel-inner {
821
+ padding: var(--rs-space-md) var(--rs-space-lg);
822
+ color: var(--rs-text);
823
+ font-size: var(--rs-font-size-sm);
824
+ line-height: var(--rs-line-height-normal);
825
+ }
826
+
827
+ .rs-tabs--line.rs-tabs--sm .rs-tabs__panel-inner {
828
+ padding: var(--rs-space-sm) var(--rs-space-md);
829
+ font-size: var(--rs-font-size-xs);
830
+ }
831
+
832
+ .rs-tabs--line.rs-tabs--panelless .rs-tabs__list {
833
+ border-bottom: 1px solid var(--rs-border);
834
+ }
835
+
836
+ /* —— segmented —— */
837
+ .rs-tabs--segmented .rs-tabs__body {
838
+ border: 1px solid var(--rs-border);
839
+ border-radius: var(--rs-radius);
840
+ background: var(--rs-surface);
841
+ overflow: hidden;
842
+ }
843
+
844
+ .rs-tabs--segmented .rs-tabs__list {
845
+ gap: 0.25rem;
846
+ width: fit-content;
847
+ max-width: 100%;
848
+ margin: var(--rs-space-sm);
849
+ padding: 0.25rem;
850
+ border-radius: var(--rs-radius-sm);
851
+ background: var(--rs-surface-hover);
852
+ }
853
+
854
+ .rs-tabs--segmented .rs-tabs__trigger {
855
+ border-radius: var(--rs-radius-xs);
856
+ }
857
+
858
+ .rs-tabs--segmented.rs-tabs--sm .rs-tabs__trigger {
859
+ min-height: var(--rs-control-height-sm);
860
+ padding: 0 0.5rem;
861
+ font-size: var(--rs-font-size-xs);
862
+ }
863
+
864
+ .rs-tabs--segmented.rs-tabs--md .rs-tabs__trigger {
865
+ min-height: var(--rs-control-height-md);
866
+ padding: 0 0.75rem;
867
+ }
868
+
869
+ .rs-tabs--segmented .rs-tabs__trigger:hover:not(:disabled):not([data-state='active']) {
870
+ color: var(--rs-text);
871
+ background: var(--rs-surface);
872
+ }
873
+
874
+ .rs-tabs--segmented .rs-tabs__trigger[data-state='active'] {
875
+ color: var(--rs-primary);
876
+ background: var(--rs-surface);
877
+ font-weight: 500;
878
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--rs-text) 8%, transparent);
879
+ }
880
+
881
+ .rs-tabs--segmented .rs-tabs__panel-inner {
882
+ padding: var(--rs-space-md) var(--rs-space-lg);
883
+ color: var(--rs-text);
884
+ font-size: var(--rs-font-size-sm);
885
+ line-height: var(--rs-line-height-normal);
886
+ }
887
+
888
+ .rs-tabs--segmented.rs-tabs--panelless .rs-tabs__list {
889
+ margin: 0;
890
+ background: var(--rs-surface-hover);
891
+ }
892
+
893
+ /* —— card(Ant editable-card) —— */
894
+ .rs-tabs--card .rs-tabs__body {
895
+ border: 1px solid var(--rs-border);
896
+ border-radius: var(--rs-radius);
897
+ background: var(--rs-surface);
898
+ overflow: hidden;
899
+ }
900
+
901
+ .rs-tabs--card .rs-tabs__list {
902
+ gap: 0.25rem;
903
+ padding: var(--rs-space-sm) var(--rs-space-sm) 0;
904
+ }
905
+
906
+ .rs-tabs--card .rs-tabs__trigger {
907
+ position: relative;
908
+ border: 1px solid transparent;
909
+ border-bottom: none;
910
+ border-radius: var(--rs-radius-sm) var(--rs-radius-sm) 0 0;
911
+ background: var(--rs-surface-hover);
912
+ }
913
+
914
+ .rs-tabs--card.rs-tabs--sm .rs-tabs__trigger {
915
+ min-height: var(--rs-control-height-sm);
916
+ padding: 0 var(--rs-space-sm);
917
+ font-size: var(--rs-font-size-xs);
918
+ }
919
+
920
+ .rs-tabs--card.rs-tabs--md .rs-tabs__trigger {
921
+ min-height: var(--rs-control-height-md);
922
+ padding: 0 var(--rs-space-md);
923
+ }
924
+
925
+ .rs-tabs--card .rs-tabs__trigger:hover:not(:disabled):not([data-state='active']) {
926
+ color: var(--rs-text);
927
+ background: var(--rs-surface);
928
+ border-color: var(--rs-border-subtle);
929
+ }
930
+
931
+ .rs-tabs--card .rs-tabs__trigger[data-state='active'] {
932
+ color: var(--rs-primary);
933
+ font-weight: 500;
934
+ background: var(--rs-surface);
935
+ border-color: var(--rs-border);
936
+ margin-bottom: -1px;
937
+ z-index: 1;
938
+ }
939
+
940
+ .rs-tabs--card .rs-tabs__panel-inner {
941
+ padding: var(--rs-space-md) var(--rs-space-lg);
942
+ border-top: 1px solid var(--rs-border-subtle);
943
+ color: var(--rs-text);
944
+ font-size: var(--rs-font-size-sm);
945
+ line-height: var(--rs-line-height-normal);
946
+ }
947
+
948
+ .rs-tabs--card.rs-tabs--panelless .rs-tabs__list {
949
+ padding-bottom: var(--rs-space-sm);
950
+ }
951
+ </style>