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,178 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ AlertDialogAction,
4
+ AlertDialogCancel,
5
+ AlertDialogContent,
6
+ AlertDialogDescription,
7
+ AlertDialogOverlay,
8
+ AlertDialogPortal,
9
+ AlertDialogRoot,
10
+ AlertDialogTitle,
11
+ } from './reka'
12
+ import RsButton from './RsButton.vue'
13
+ import { useRsI18n } from '../composables/useRsI18n'
14
+ import type { RsFeedbackTone } from './overlay-utils'
15
+
16
+ const open = defineModel<boolean>('open', { default: false })
17
+
18
+ withDefaults(
19
+ defineProps<{
20
+ title?: string
21
+ description?: string
22
+ tone?: RsFeedbackTone
23
+ confirmText?: string
24
+ cancelText?: string
25
+ confirmVariant?: 'primary' | 'danger'
26
+ showOverlay?: boolean
27
+ /** AlertDialogPortal 挂载目标(id 选择器或 Element) */
28
+ teleportTo?: string | HTMLElement
29
+ }>(),
30
+ {
31
+ tone: 'danger',
32
+ confirmVariant: 'danger',
33
+ showOverlay: false,
34
+ },
35
+ )
36
+
37
+ const emit = defineEmits<{
38
+ confirm: []
39
+ cancel: []
40
+ }>()
41
+
42
+ const { t } = useRsI18n()
43
+
44
+ function onConfirm(): void {
45
+ emit('confirm')
46
+ open.value = false
47
+ }
48
+
49
+ function onCancel(): void {
50
+ emit('cancel')
51
+ }
52
+ </script>
53
+
54
+ <template>
55
+ <AlertDialogRoot v-model:open="open">
56
+ <AlertDialogPortal :to="teleportTo">
57
+ <AlertDialogOverlay v-if="showOverlay" class="rs-confirm-dialog__overlay" />
58
+ <AlertDialogContent
59
+ class="rs-confirm-dialog__content"
60
+ :disable-outside-pointer-events="true"
61
+ >
62
+ <div class="rs-confirm-dialog__icon" :class="`rs-confirm-dialog__icon--${tone}`">
63
+ !
64
+ </div>
65
+ <div class="rs-confirm-dialog__main">
66
+ <AlertDialogTitle class="rs-confirm-dialog__title">
67
+ {{ title ?? t('confirm.title') }}
68
+ </AlertDialogTitle>
69
+ <AlertDialogDescription class="rs-confirm-dialog__description">
70
+ {{ description ?? t('confirm.description') }}
71
+ </AlertDialogDescription>
72
+ <footer class="rs-confirm-dialog__footer">
73
+ <AlertDialogCancel as-child>
74
+ <RsButton variant="default" @click="onCancel">
75
+ {{ cancelText ?? t('common.cancel') }}
76
+ </RsButton>
77
+ </AlertDialogCancel>
78
+ <AlertDialogAction as-child>
79
+ <RsButton :variant="confirmVariant" @click="onConfirm">
80
+ {{ confirmText ?? t('common.confirm') }}
81
+ </RsButton>
82
+ </AlertDialogAction>
83
+ </footer>
84
+ </div>
85
+ </AlertDialogContent>
86
+ </AlertDialogPortal>
87
+ </AlertDialogRoot>
88
+ </template>
89
+
90
+ <style>
91
+ .rs-confirm-dialog__overlay {
92
+ position: fixed;
93
+ inset: 0;
94
+ z-index: var(--rs-z-modal);
95
+ background: var(--rs-dialog-overlay-bg);
96
+ backdrop-filter: blur(var(--rs-dialog-overlay-blur)) saturate(120%);
97
+ -webkit-backdrop-filter: blur(var(--rs-dialog-overlay-blur)) saturate(120%);
98
+ }
99
+ .rs-confirm-dialog__content {
100
+ position: fixed;
101
+ left: 50%;
102
+ top: calc(
103
+ var(--rs-dialog-inset-top, 1rem) +
104
+ (100vh - var(--rs-dialog-inset-top, 1rem) - var(--rs-dialog-inset-bottom, 1rem)) / 2
105
+ );
106
+ z-index: calc(var(--rs-z-modal) + 1);
107
+ display: flex;
108
+ gap: var(--rs-space-lg);
109
+ width: calc(100vw - 2 * var(--rs-dialog-inset-x, 1rem));
110
+ max-width: 28rem;
111
+ transform: translate(-50%, -50%);
112
+ box-sizing: border-box;
113
+ padding: var(--rs-space-xl);
114
+ border: 1px solid var(--rs-dialog-border);
115
+ border-radius: var(--rs-radius-lg);
116
+ background: var(--rs-dialog-body-bg);
117
+ color: var(--rs-text);
118
+ box-shadow: var(--rs-dialog-shadow);
119
+ outline: none;
120
+ }
121
+ [data-rs-theme='light'] .rs-confirm-dialog__content {
122
+ background: color-mix(in srgb, var(--rs-dialog-bg) 94%, transparent);
123
+ backdrop-filter: blur(24px) saturate(180%);
124
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
125
+ }
126
+ .rs-confirm-dialog__icon {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ width: 2.5rem;
131
+ height: 2.5rem;
132
+ flex: 0 0 auto;
133
+ border-radius: var(--rs-radius);
134
+ font-weight: 700;
135
+ }
136
+ .rs-confirm-dialog__icon--danger {
137
+ background: var(--rs-danger-container);
138
+ color: var(--rs-on-danger-container);
139
+ }
140
+ .rs-confirm-dialog__icon--warning {
141
+ background: var(--rs-warning-container);
142
+ color: var(--rs-on-warning-container);
143
+ }
144
+ .rs-confirm-dialog__icon--success {
145
+ background: var(--rs-success-container);
146
+ color: var(--rs-on-success-container);
147
+ }
148
+ .rs-confirm-dialog__icon--info,
149
+ .rs-confirm-dialog__icon--default {
150
+ background: var(--rs-info-container);
151
+ color: var(--rs-on-info-container);
152
+ }
153
+ .rs-confirm-dialog__main {
154
+ min-width: 0;
155
+ flex: 1;
156
+ }
157
+ .rs-confirm-dialog__title {
158
+ margin: 0;
159
+ font-size: var(--rs-font-size-base);
160
+ font-weight: 600;
161
+ letter-spacing: -0.01em;
162
+ color: var(--rs-dialog-title-fg);
163
+ }
164
+ .rs-confirm-dialog__description {
165
+ margin: var(--rs-space-sm) 0 0;
166
+ color: var(--rs-dialog-description-fg);
167
+ font-size: var(--rs-font-size-sm);
168
+ line-height: var(--rs-line-height-normal);
169
+ }
170
+ .rs-confirm-dialog__footer {
171
+ display: flex;
172
+ justify-content: flex-end;
173
+ gap: var(--rs-space-sm);
174
+ margin-top: var(--rs-space-xl);
175
+ padding-top: var(--rs-space-lg);
176
+ border-top: 1px solid var(--rs-dialog-separator);
177
+ }
178
+ </style>
@@ -0,0 +1,235 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ /** 支持的响应式断点,语义与常见 SaaS 前端一致。 */
5
+ export type RsContainerBreakpoint = 'sm' | 'md' | 'lg' | 'xl'
6
+
7
+ /** 内容区最大宽度档位,对应 `--rs-container-max-*` token。 */
8
+ export type RsContainerMaxWidth = 'sm' | 'md' | 'lg' | 'xl' | 'full'
9
+
10
+ /** 水平内边距档位,对应 `--rs-space-*` token。 */
11
+ export type RsContainerPadding = 'none' | 'sm' | 'md' | 'lg'
12
+
13
+ /** 栅格间距档位,对应 `--rs-space-*` token。 */
14
+ export type RsContainerGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
15
+
16
+ /** 断点映射配置,用于 maxWidth、padding、columns、gap。 */
17
+ export type RsContainerResponsive<T> = Partial<Record<RsContainerBreakpoint, T>>
18
+
19
+ /** 支持固定值或断点映射。 */
20
+ export type RsContainerMaybeResponsive<T> = T | RsContainerResponsive<T>
21
+
22
+ const props = withDefaults(
23
+ defineProps<{
24
+ /** 最大内容宽度;`fluid` 为 true 时忽略。 */
25
+ maxWidth?: RsContainerMaybeResponsive<RsContainerMaxWidth>
26
+ /** 水平内边距。 */
27
+ padding?: RsContainerMaybeResponsive<RsContainerPadding>
28
+ /** 是否在父级内水平居中。 */
29
+ centered?: boolean
30
+ /** 占满父级宽度,忽略 `maxWidth`。 */
31
+ fluid?: boolean
32
+ /** 根元素标签名。 */
33
+ tag?: string
34
+ /** 启用栅格布局;未传 `columns` 时默认为 12 栅格。 */
35
+ grid?: boolean
36
+ /** 栅格列数,支持响应式。 */
37
+ columns?: RsContainerMaybeResponsive<number>
38
+ /** 栅格间距,支持响应式。 */
39
+ gap?: RsContainerMaybeResponsive<RsContainerGap>
40
+ }>(),
41
+ {
42
+ maxWidth: 'lg',
43
+ padding: 'md',
44
+ centered: true,
45
+ fluid: false,
46
+ tag: 'div',
47
+ grid: false,
48
+ columns: 12,
49
+ gap: 'md',
50
+ },
51
+ )
52
+
53
+ const BREAKPOINTS: RsContainerBreakpoint[] = ['sm', 'md', 'lg', 'xl']
54
+
55
+ const isResponsiveObject = <T,>(value: RsContainerMaybeResponsive<T>): value is RsContainerResponsive<T> =>
56
+ typeof value === 'object' && value !== null
57
+
58
+ const tokenToSpaceVar: Record<RsContainerGap | RsContainerPadding, string> = {
59
+ none: '0',
60
+ xs: 'var(--rs-space-xs)',
61
+ sm: 'var(--rs-space-sm)',
62
+ md: 'var(--rs-space-md)',
63
+ lg: 'var(--rs-space-lg)',
64
+ xl: 'var(--rs-space-xl)',
65
+ }
66
+
67
+ const tokenToContainerVar: Record<RsContainerMaxWidth, string> = {
68
+ sm: 'var(--rs-container-max-sm)',
69
+ md: 'var(--rs-container-max-md)',
70
+ lg: 'var(--rs-container-max-lg)',
71
+ xl: 'var(--rs-container-max-xl)',
72
+ full: 'var(--rs-container-max-full)',
73
+ }
74
+
75
+ const rootClass = computed(() => [
76
+ 'rs-container',
77
+ { 'rs-container--centered': props.centered, 'rs-container--grid': props.grid },
78
+ ])
79
+
80
+ const rootStyle = computed(() => {
81
+ const style: Record<string, string | number> = {}
82
+
83
+ if (props.fluid) {
84
+ style['--rs-container-max-current'] = 'none'
85
+ } else if (isResponsiveObject(props.maxWidth)) {
86
+ const responsiveMaxWidth = props.maxWidth as RsContainerResponsive<RsContainerMaxWidth>
87
+ BREAKPOINTS.forEach((bp) => {
88
+ const value = responsiveMaxWidth[bp]
89
+ if (value) style[`--rs-container-max-${bp}`] = tokenToContainerVar[value]
90
+ })
91
+ } else {
92
+ style['--rs-container-max-current'] = tokenToContainerVar[props.maxWidth]
93
+ }
94
+
95
+ if (isResponsiveObject(props.padding)) {
96
+ const responsivePadding = props.padding as RsContainerResponsive<RsContainerPadding>
97
+ BREAKPOINTS.forEach((bp) => {
98
+ const value = responsivePadding[bp]
99
+ if (value) style[`--rs-container-padding-${bp}`] = tokenToSpaceVar[value]
100
+ })
101
+ } else {
102
+ style['--rs-container-padding-current'] = tokenToSpaceVar[props.padding]
103
+ }
104
+
105
+ if (props.grid) {
106
+ if (isResponsiveObject(props.columns)) {
107
+ const responsiveColumns = props.columns as RsContainerResponsive<number>
108
+ BREAKPOINTS.forEach((bp) => {
109
+ const value = responsiveColumns[bp]
110
+ if (typeof value === 'number') style[`--rs-container-columns-${bp}`] = value
111
+ })
112
+ } else {
113
+ style['--rs-container-columns-current'] = props.columns
114
+ }
115
+
116
+ if (isResponsiveObject(props.gap)) {
117
+ const responsiveGap = props.gap as RsContainerResponsive<RsContainerGap>
118
+ BREAKPOINTS.forEach((bp) => {
119
+ const value = responsiveGap[bp]
120
+ if (value) style[`--rs-container-gap-${bp}`] = tokenToSpaceVar[value]
121
+ })
122
+ } else {
123
+ style['--rs-container-gap-current'] = tokenToSpaceVar[props.gap]
124
+ }
125
+ }
126
+
127
+ return style
128
+ })
129
+ </script>
130
+
131
+ <template>
132
+ <component :is="tag" :class="rootClass" :style="rootStyle">
133
+ <slot />
134
+ </component>
135
+ </template>
136
+
137
+ <style scoped>
138
+ .rs-container {
139
+ width: 100%;
140
+ box-sizing: border-box;
141
+ max-width: var(--rs-container-max-current, var(--rs-container-max-lg));
142
+ padding-inline: var(--rs-container-padding-current, var(--rs-space-md));
143
+ }
144
+ .rs-container--centered {
145
+ margin-inline: auto;
146
+ }
147
+ .rs-container--grid {
148
+ display: grid;
149
+ grid-template-columns: repeat(var(--rs-container-columns-current, 12), minmax(0, 1fr));
150
+ gap: var(--rs-container-gap-current, var(--rs-space-md));
151
+ }
152
+
153
+ @media (width >= 40rem) {
154
+ .rs-container {
155
+ max-width: var(--rs-container-max-sm, var(--rs-container-max-current, var(--rs-container-max-lg)));
156
+ padding-inline: var(--rs-container-padding-sm, var(--rs-container-padding-current, var(--rs-space-md)));
157
+ }
158
+ .rs-container--grid {
159
+ grid-template-columns: repeat(
160
+ var(--rs-container-columns-sm, var(--rs-container-columns-current, 12)),
161
+ minmax(0, 1fr)
162
+ );
163
+ gap: var(--rs-container-gap-sm, var(--rs-container-gap-current, var(--rs-space-md)));
164
+ }
165
+ }
166
+
167
+ @media (width >= 48rem) {
168
+ .rs-container {
169
+ max-width: var(--rs-container-max-md, var(--rs-container-max-sm, var(--rs-container-max-current)));
170
+ padding-inline: var(
171
+ --rs-container-padding-md,
172
+ var(--rs-container-padding-sm, var(--rs-container-padding-current))
173
+ );
174
+ }
175
+ .rs-container--grid {
176
+ grid-template-columns: repeat(
177
+ var(--rs-container-columns-md, var(--rs-container-columns-sm, var(--rs-container-columns-current, 12))),
178
+ minmax(0, 1fr)
179
+ );
180
+ gap: var(--rs-container-gap-md, var(--rs-container-gap-sm, var(--rs-container-gap-current)));
181
+ }
182
+ }
183
+
184
+ @media (width >= 64rem) {
185
+ .rs-container {
186
+ max-width: var(
187
+ --rs-container-max-lg,
188
+ var(--rs-container-max-md, var(--rs-container-max-sm, var(--rs-container-max-current)))
189
+ );
190
+ padding-inline: var(
191
+ --rs-container-padding-lg,
192
+ var(--rs-container-padding-md, var(--rs-container-padding-sm, var(--rs-container-padding-current)))
193
+ );
194
+ }
195
+ .rs-container--grid {
196
+ grid-template-columns: repeat(
197
+ var(
198
+ --rs-container-columns-lg,
199
+ var(--rs-container-columns-md, var(--rs-container-columns-sm, var(--rs-container-columns-current, 12)))
200
+ ),
201
+ minmax(0, 1fr)
202
+ );
203
+ gap: var(--rs-container-gap-lg, var(--rs-container-gap-md, var(--rs-container-gap-sm, var(--rs-container-gap-current))));
204
+ }
205
+ }
206
+
207
+ @media (width >= 80rem) {
208
+ .rs-container {
209
+ max-width: var(
210
+ --rs-container-max-xl,
211
+ var(--rs-container-max-lg, var(--rs-container-max-md, var(--rs-container-max-sm, var(--rs-container-max-current))))
212
+ );
213
+ padding-inline: var(
214
+ --rs-container-padding-xl,
215
+ var(--rs-container-padding-lg, var(--rs-container-padding-md, var(--rs-container-padding-sm, var(--rs-container-padding-current))))
216
+ );
217
+ }
218
+ .rs-container--grid {
219
+ grid-template-columns: repeat(
220
+ var(
221
+ --rs-container-columns-xl,
222
+ var(
223
+ --rs-container-columns-lg,
224
+ var(--rs-container-columns-md, var(--rs-container-columns-sm, var(--rs-container-columns-current, 12)))
225
+ )
226
+ ),
227
+ minmax(0, 1fr)
228
+ );
229
+ gap: var(
230
+ --rs-container-gap-xl,
231
+ var(--rs-container-gap-lg, var(--rs-container-gap-md, var(--rs-container-gap-sm, var(--rs-container-gap-current))))
232
+ );
233
+ }
234
+ }
235
+ </style>
@@ -0,0 +1,238 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ ContextMenuContent,
4
+ ContextMenuPortal,
5
+ ContextMenuRoot,
6
+ ContextMenuTrigger,
7
+ } from './reka'
8
+ import type { RsContextMenuItem } from './context-menu-utils'
9
+ import RsContextMenuItems from './RsContextMenuItems.vue'
10
+
11
+ defineProps<{
12
+ items: RsContextMenuItem[]
13
+ /** 禁用时不拦截右键事件,直接透传 slot,浏览器原生菜单正常显示 */
14
+ disabled?: boolean
15
+ }>()
16
+
17
+ /**
18
+ * 展开状态。不绑定时组件自行维护(与原行为一致);
19
+ * 绑定后可由外部主动关闭——例如菜单所依赖的数据已失效时。
20
+ */
21
+ const open = defineModel<boolean>('open', { default: false })
22
+
23
+ const emit = defineEmits<{
24
+ select: [key: string]
25
+ }>()
26
+
27
+ function onSelect(item: RsContextMenuItem) {
28
+ emit('select', item.key)
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <ContextMenuRoot v-if="!disabled" v-model:open="open">
34
+ <ContextMenuTrigger class="rs-ctx-trigger" @contextmenu.stop>
35
+ <slot />
36
+ </ContextMenuTrigger>
37
+ <ContextMenuPortal>
38
+ <ContextMenuContent
39
+ class="rs-context-menu__content rs-native-scrollbar rs-motion-reduce"
40
+ :side-offset="4"
41
+ :collision-padding="8"
42
+ >
43
+ <RsContextMenuItems :items="items" @select="onSelect" />
44
+ </ContextMenuContent>
45
+ </ContextMenuPortal>
46
+ </ContextMenuRoot>
47
+ <slot v-else />
48
+ </template>
49
+
50
+ <style>
51
+ /* 触发器包装:display:contents 令 span 不产生盒模型,不影响父级 flex/grid 布局。
52
+ ContextMenuTrigger 默认渲染为 span,加此 class 使其透明于布局。 */
53
+ .rs-ctx-trigger {
54
+ display: contents;
55
+ }
56
+
57
+ /* ── 菜单容器:macOS 毛玻璃浮层 ── */
58
+ .rs-context-menu__content,
59
+ .rs-context-menu__sub-content {
60
+ z-index: var(--rs-z-dropdown);
61
+ box-sizing: border-box;
62
+ min-width: 11rem;
63
+ max-width: min(22rem, calc(100vw - 1rem));
64
+ /* 多项 / 多级时贴边:用 popper 可用高度限制,避免溢出视口无法点选 */
65
+ max-height: var(--reka-context-menu-content-available-height, calc(100vh - 1rem));
66
+ padding: 5px;
67
+ border-radius: 10px;
68
+ border: 0.5px solid var(--rs-ctx-border);
69
+ background: var(--rs-ctx-bg);
70
+ backdrop-filter: blur(40px) saturate(180%);
71
+ -webkit-backdrop-filter: blur(40px) saturate(180%);
72
+ box-shadow: var(--rs-ctx-shadow);
73
+ outline: none;
74
+ overflow-x: hidden;
75
+ overflow-y: auto;
76
+ overscroll-behavior: contain;
77
+ }
78
+
79
+ /* 主菜单入场动画(从鼠标位置展开) */
80
+ .rs-context-menu__content {
81
+ transform-origin: var(--reka-context-menu-content-transform-origin, top left);
82
+ animation: rs-ctx-in 0.13s cubic-bezier(0.36, 0.07, 0.19, 0.97);
83
+ }
84
+
85
+ /* 子菜单:仅淡入,避免多层快速切换时 scale 造成抖动感 */
86
+ .rs-context-menu__sub-content {
87
+ transform-origin: var(--reka-popper-transform-origin, left center);
88
+ animation: rs-ctx-sub-in 0.1s ease-out;
89
+ }
90
+
91
+ @keyframes rs-ctx-in {
92
+ from {
93
+ opacity: 0;
94
+ transform: scale(0.95);
95
+ }
96
+ to {
97
+ opacity: 1;
98
+ transform: scale(1);
99
+ }
100
+ }
101
+
102
+ @keyframes rs-ctx-sub-in {
103
+ from {
104
+ opacity: 0;
105
+ }
106
+ to {
107
+ opacity: 1;
108
+ }
109
+ }
110
+
111
+ /* ── 菜单项 ── */
112
+ .rs-context-menu__item {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 0;
116
+ padding: 0 8px 0 0;
117
+ height: 26px;
118
+ border-radius: 6px;
119
+ font-size: 13px;
120
+ line-height: 1;
121
+ color: var(--rs-text);
122
+ cursor: default;
123
+ outline: none;
124
+ user-select: none;
125
+ transition:
126
+ background 0.08s,
127
+ color 0.08s;
128
+ }
129
+
130
+ /* 图标占位列(固定 28px,居中对齐) */
131
+ .rs-context-menu__icon-cell {
132
+ flex-shrink: 0;
133
+ width: 28px;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ color: var(--rs-ctx-icon);
138
+ }
139
+
140
+ /* 文本 */
141
+ .rs-context-menu__label {
142
+ flex: 1;
143
+ min-width: 0;
144
+ }
145
+
146
+ /* 键盘快捷键 */
147
+ .rs-context-menu__shortcut {
148
+ flex-shrink: 0;
149
+ margin-left: 16px;
150
+ font-size: 12px;
151
+ color: var(--rs-ctx-shortcut);
152
+ letter-spacing: 0.02em;
153
+ font-family: -apple-system, 'SF Pro Text', system-ui, sans-serif;
154
+ }
155
+
156
+ /* 子菜单展开箭头 */
157
+ .rs-context-menu__arrow {
158
+ flex-shrink: 0;
159
+ margin-left: 6px;
160
+ color: var(--rs-ctx-icon);
161
+ }
162
+
163
+ /* 悬停 / 子菜单已展开:蓝色高亮条(macOS 主色调) */
164
+ .rs-context-menu__item[data-highlighted],
165
+ .rs-context-menu__item[data-state='open'] {
166
+ background: var(--rs-primary);
167
+ color: #ffffff;
168
+ }
169
+
170
+ .rs-context-menu__item[data-highlighted] .rs-context-menu__icon-cell,
171
+ .rs-context-menu__item[data-highlighted] .rs-context-menu__shortcut,
172
+ .rs-context-menu__item[data-highlighted] .rs-context-menu__arrow,
173
+ .rs-context-menu__item[data-state='open'] .rs-context-menu__icon-cell,
174
+ .rs-context-menu__item[data-state='open'] .rs-context-menu__shortcut,
175
+ .rs-context-menu__item[data-state='open'] .rs-context-menu__arrow {
176
+ color: rgba(255 255 255 / 0.75);
177
+ }
178
+
179
+ /* 危险项 */
180
+ .rs-context-menu__item--danger {
181
+ color: var(--rs-danger);
182
+ }
183
+
184
+ .rs-context-menu__item--danger .rs-context-menu__icon-cell {
185
+ color: var(--rs-danger);
186
+ }
187
+
188
+ .rs-context-menu__item--danger[data-highlighted],
189
+ .rs-context-menu__item--danger[data-state='open'] {
190
+ background: var(--rs-danger);
191
+ color: #ffffff;
192
+ }
193
+
194
+ .rs-context-menu__item--danger[data-highlighted] .rs-context-menu__icon-cell,
195
+ .rs-context-menu__item--danger[data-state='open'] .rs-context-menu__icon-cell {
196
+ color: rgba(255 255 255 / 0.75);
197
+ }
198
+
199
+ /* 禁用态 */
200
+ .rs-context-menu__item[data-disabled] {
201
+ opacity: 0.38;
202
+ cursor: not-allowed;
203
+ }
204
+
205
+ /* 分隔线:更细腻 */
206
+ .rs-context-menu__separator {
207
+ height: 1px;
208
+ margin: 5px 0;
209
+ background: var(--rs-ctx-separator);
210
+ }
211
+
212
+ /* ── 主题变量:深色 ── */
213
+ :root,
214
+ [data-rs-theme='dark'] {
215
+ --rs-ctx-bg: rgba(40, 40, 42, 0.85);
216
+ --rs-ctx-border: rgba(255, 255, 255, 0.13);
217
+ --rs-ctx-shadow:
218
+ 0 0 0 0.5px rgba(255, 255, 255, 0.08),
219
+ 0 2px 8px rgba(0, 0, 0, 0.4),
220
+ 0 8px 32px rgba(0, 0, 0, 0.55);
221
+ --rs-ctx-icon: rgba(235, 235, 245, 0.5);
222
+ --rs-ctx-shortcut: rgba(235, 235, 245, 0.4);
223
+ --rs-ctx-separator: rgba(255, 255, 255, 0.1);
224
+ }
225
+
226
+ /* ── 主题变量:亮色 ── */
227
+ [data-rs-theme='light'] {
228
+ --rs-ctx-bg: rgba(255, 255, 255, 0.88);
229
+ --rs-ctx-border: rgba(0, 0, 0, 0.1);
230
+ --rs-ctx-shadow:
231
+ 0 0 0 0.5px rgba(0, 0, 0, 0.06),
232
+ 0 2px 6px rgba(0, 0, 0, 0.12),
233
+ 0 8px 28px rgba(0, 0, 0, 0.18);
234
+ --rs-ctx-icon: rgba(60, 60, 67, 0.5);
235
+ --rs-ctx-shortcut: rgba(60, 60, 67, 0.4);
236
+ --rs-ctx-separator: rgba(60, 60, 67, 0.1);
237
+ }
238
+ </style>