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,42 @@
1
+ <script setup lang="ts">
2
+ import RsDatePicker from './RsDatePicker.vue'
3
+ import type { RsDateRangeValue } from './date-picker-utils'
4
+
5
+ export type RsDateTimeRangeValue = RsDateRangeValue
6
+ export type RsDateTimePickerLabelPosition = 'top' | 'left'
7
+
8
+ const model = defineModel<string | RsDateTimeRangeValue>({ default: '' })
9
+ const open = defineModel<boolean>('open', { default: false })
10
+
11
+ withDefaults(
12
+ defineProps<{
13
+ label?: string
14
+ hint?: string
15
+ placeholder?: string
16
+ disabled?: boolean
17
+ required?: boolean
18
+ range?: boolean
19
+ withSeconds?: boolean
20
+ minDate?: string
21
+ maxDate?: string
22
+ labelPosition?: RsDateTimePickerLabelPosition
23
+ }>(),
24
+ {
25
+ disabled: false,
26
+ required: false,
27
+ range: false,
28
+ withSeconds: false,
29
+ labelPosition: 'top',
30
+ },
31
+ )
32
+ </script>
33
+
34
+ <template>
35
+ <RsDatePicker
36
+ v-bind="$props"
37
+ v-model="model"
38
+ v-model:open="open"
39
+ with-time
40
+ :with-seconds="withSeconds"
41
+ />
42
+ </template>
@@ -0,0 +1,486 @@
1
+ <script setup lang="ts">
2
+ import { computed, onBeforeUnmount, ref, toRef, watch } from 'vue'
3
+ import {
4
+ DialogContent,
5
+ DialogDescription,
6
+ DialogOverlay,
7
+ DialogPortal,
8
+ DialogRoot,
9
+ DialogTitle,
10
+ } from './reka'
11
+ import { useRsI18n } from '../composables/useRsI18n'
12
+ import RsButton from './RsButton.vue'
13
+ import type { RsFeedbackTone } from './overlay-utils'
14
+ import { useRsDialogWindow } from './dialog-window'
15
+
16
+ const open = defineModel<boolean>('open', { default: false })
17
+
18
+ const props = withDefaults(
19
+ defineProps<{
20
+ title: string
21
+ description?: string
22
+ width?: 'sm' | 'md' | 'lg'
23
+ tone?: RsFeedbackTone
24
+ layout?: 'window' | 'confirm'
25
+ draggable?: boolean
26
+ resizable?: boolean
27
+ fullscreenable?: boolean
28
+ /** 是否模态(false 时不锁焦点/不挡背后交互,适合页签内浮层) */
29
+ modal?: boolean
30
+ showOverlay?: boolean
31
+ showClose?: boolean
32
+ /** 点击外部是否关闭;默认 false。与 modal 独立:非模态也可保持打开。 */
33
+ closeOnOverlayClick?: boolean
34
+ /** DialogPortal 挂载目标(id 选择器或 Element) */
35
+ teleportTo?: string | HTMLElement
36
+ /**
37
+ * 延后挂载 #body 插槽,避免与打开动画/重组件 init 争抢主线程。
38
+ * 默认:window 布局为 true,confirm 为 false。
39
+ */
40
+ deferBodyMount?: boolean
41
+ /** 全屏/还原时是否播放 bounds 过渡(含编辑器时建议保持 false) */
42
+ boundsTransition?: boolean
43
+ }>(),
44
+ {
45
+ width: 'md',
46
+ tone: 'default',
47
+ layout: 'window',
48
+ draggable: false,
49
+ resizable: true,
50
+ fullscreenable: true,
51
+ modal: true,
52
+ showOverlay: false,
53
+ showClose: true,
54
+ closeOnOverlayClick: false,
55
+ boundsTransition: false,
56
+ },
57
+ )
58
+
59
+ const { t } = useRsI18n()
60
+ const isWindowLayout = computed(() => props.layout === 'window')
61
+ const isConfirmLayout = computed(() => props.layout === 'confirm')
62
+ const enableDraggable = computed(() => props.draggable && isWindowLayout.value)
63
+ const enableResizable = computed(() => props.resizable && isWindowLayout.value)
64
+ const deferBodyMount = computed(() => props.deferBodyMount ?? isWindowLayout.value)
65
+
66
+ const bodyReady = ref(false)
67
+ let bodyMountFrameOuter = 0
68
+ let bodyMountFrameInner = 0
69
+
70
+ function resetBodyMount(): void {
71
+ if (bodyMountFrameOuter) {
72
+ cancelAnimationFrame(bodyMountFrameOuter)
73
+ bodyMountFrameOuter = 0
74
+ }
75
+ if (bodyMountFrameInner) {
76
+ cancelAnimationFrame(bodyMountFrameInner)
77
+ bodyMountFrameInner = 0
78
+ }
79
+ bodyReady.value = false
80
+ }
81
+
82
+ function scheduleBodyMount(): void {
83
+ resetBodyMount()
84
+ bodyMountFrameOuter = requestAnimationFrame(() => {
85
+ bodyMountFrameOuter = 0
86
+ bodyMountFrameInner = requestAnimationFrame(() => {
87
+ bodyMountFrameInner = 0
88
+ if (open.value) bodyReady.value = true
89
+ })
90
+ })
91
+ }
92
+
93
+ watch(
94
+ [open, deferBodyMount],
95
+ ([isOpen, defer]) => {
96
+ if (!isOpen || !defer) {
97
+ resetBodyMount()
98
+ if (isOpen) bodyReady.value = true
99
+ return
100
+ }
101
+ scheduleBodyMount()
102
+ },
103
+ { immediate: true },
104
+ )
105
+
106
+ onBeforeUnmount(resetBodyMount)
107
+
108
+ const showBodyContent = computed(() => !deferBodyMount.value || bodyReady.value)
109
+
110
+ /** 是否因外部交互关闭:与 modal 无关;非模态仍允许背后操作,仅控制是否 dismiss。 */
111
+ function onPointerDownOutside(event: Event): void {
112
+ if (!props.closeOnOverlayClick) event.preventDefault()
113
+ }
114
+
115
+ function onInteractOutside(event: Event): void {
116
+ if (!props.closeOnOverlayClick) event.preventDefault()
117
+ }
118
+
119
+ const {
120
+ isFullscreen,
121
+ boundsTransitionEnabled,
122
+ dialogStyle,
123
+ resizeHandles,
124
+ toggleFullscreen,
125
+ onHeaderPointerDown,
126
+ onResizePointerDown,
127
+ } = useRsDialogWindow({
128
+ open,
129
+ widthPreset: toRef(props, 'width'),
130
+ draggable: enableDraggable,
131
+ resizable: enableResizable,
132
+ compact: isConfirmLayout,
133
+ boundsTransition: toRef(props, 'boundsTransition'),
134
+ })
135
+ </script>
136
+
137
+ <template>
138
+ <DialogRoot v-model:open="open" :modal="modal">
139
+ <DialogPortal :to="teleportTo">
140
+ <DialogOverlay v-if="showOverlay" class="rs-dialog__overlay rs-motion-reduce" />
141
+ <DialogContent
142
+ class="rs-dialog__content rs-motion-reduce"
143
+ :class="[
144
+ `rs-dialog__content--${width}`,
145
+ `rs-dialog__content--${layout}`,
146
+ `rs-dialog__content--tone-${tone}`,
147
+ {
148
+ 'rs-dialog__content--fullscreen': isFullscreen,
149
+ 'rs-dialog__content--draggable': enableDraggable,
150
+ 'rs-dialog__content--bounds-transition': boundsTransitionEnabled,
151
+ },
152
+ ]"
153
+ :style="isWindowLayout ? dialogStyle : undefined"
154
+ @pointer-down-outside="onPointerDownOutside"
155
+ @interact-outside="onInteractOutside"
156
+ >
157
+ <template v-if="enableResizable && !isFullscreen">
158
+ <div
159
+ v-for="handle in resizeHandles"
160
+ :key="handle"
161
+ :class="['rs-dialog__resize-handle', `rs-dialog__resize-handle--${handle}`]"
162
+ :aria-hidden="true"
163
+ @pointerdown.stop="onResizePointerDown(handle, $event)"
164
+ />
165
+ </template>
166
+ <header class="rs-dialog__header" @pointerdown="onHeaderPointerDown">
167
+ <div class="rs-dialog__heading">
168
+ <DialogTitle class="rs-dialog__title">{{ title }}</DialogTitle>
169
+ <DialogDescription
170
+ class="rs-dialog__description"
171
+ :class="{ 'rs-dialog__description--sr-only': !description }"
172
+ >
173
+ {{ description || title }}
174
+ </DialogDescription>
175
+ </div>
176
+ <div class="rs-dialog__actions">
177
+ <RsButton
178
+ v-if="fullscreenable && isWindowLayout"
179
+ variant="ghost"
180
+ size="sm"
181
+ icon-only
182
+ :icon="isFullscreen ? 'minimize-2' : 'maximize-2'"
183
+ :tooltip="isFullscreen ? t('dialog.restore') : t('dialog.fullscreen')"
184
+ @click="toggleFullscreen"
185
+ />
186
+ <RsButton
187
+ v-if="showClose"
188
+ variant="ghost"
189
+ size="sm"
190
+ icon-only
191
+ icon="x"
192
+ :tooltip="t('dialog.close')"
193
+ @click="open = false"
194
+ />
195
+ </div>
196
+ </header>
197
+ <div class="rs-dialog__body" :aria-busy="deferBodyMount && !bodyReady ? 'true' : undefined">
198
+ <slot v-if="showBodyContent" name="body" />
199
+ <div v-else class="rs-dialog__body-placeholder">
200
+ <slot name="body-placeholder" />
201
+ </div>
202
+ </div>
203
+ <footer v-if="$slots.footer" class="rs-dialog__footer">
204
+ <slot name="footer" />
205
+ </footer>
206
+ </DialogContent>
207
+ </DialogPortal>
208
+ </DialogRoot>
209
+ </template>
210
+
211
+ <style>
212
+ .rs-dialog__overlay {
213
+ position: fixed;
214
+ inset: 0;
215
+ z-index: var(--rs-z-modal);
216
+ background: var(--rs-dialog-overlay-bg);
217
+ backdrop-filter: blur(var(--rs-dialog-overlay-blur)) saturate(120%);
218
+ -webkit-backdrop-filter: blur(var(--rs-dialog-overlay-blur)) saturate(120%);
219
+ }
220
+ .rs-dialog__overlay[data-state='open'] {
221
+ animation: rs-dialog-overlay-in 220ms ease;
222
+ }
223
+ .rs-dialog__overlay[data-state='closed'] {
224
+ animation: rs-dialog-overlay-out 160ms ease;
225
+ }
226
+ .rs-dialog__content {
227
+ position: fixed;
228
+ left: 50%;
229
+ top: calc(
230
+ var(--rs-dialog-inset-top, 1rem) +
231
+ (100vh - var(--rs-dialog-inset-top, 1rem) - var(--rs-dialog-inset-bottom, 1rem)) / 2
232
+ );
233
+ z-index: calc(var(--rs-z-modal) + 1);
234
+ display: flex;
235
+ max-height: min(
236
+ calc(100vh - var(--rs-dialog-inset-top, 1rem) - var(--rs-dialog-inset-bottom, 1rem) - 2rem),
237
+ 40rem
238
+ );
239
+ width: calc(100vw - 2 * var(--rs-dialog-inset-x, 1rem));
240
+ max-width: calc(100vw - 2 * var(--rs-dialog-inset-x, 1rem));
241
+ flex-direction: column;
242
+ transform: translate(-50%, -50%);
243
+ overflow: hidden;
244
+ border-radius: var(--rs-radius-lg);
245
+ border: 1px solid var(--rs-dialog-border);
246
+ background: var(--rs-dialog-header-bg);
247
+ box-shadow: var(--rs-dialog-shadow);
248
+ outline: none;
249
+ }
250
+
251
+ /* 亮色 confirm:轻微 vibrancy;window 用实底避免 CEF 下 blur 合成开销 */
252
+ [data-rs-theme='light'] .rs-dialog__content--confirm {
253
+ background: color-mix(in srgb, var(--rs-dialog-bg) 94%, transparent);
254
+ backdrop-filter: blur(24px) saturate(180%);
255
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
256
+ }
257
+ [data-rs-theme='light'] .rs-dialog__content--window {
258
+ background: var(--rs-dialog-bg);
259
+ }
260
+ /* confirm 布局:缩放 + 淡入(居中,含 translate);苹果 sheet 缓动曲线 */
261
+ .rs-dialog__content--confirm[data-state='open'] {
262
+ animation: rs-dialog-pop-in 240ms cubic-bezier(0.32, 0.72, 0, 1);
263
+ }
264
+ .rs-dialog__content--confirm[data-state='closed'] {
265
+ animation: rs-dialog-pop-out 180ms ease;
266
+ }
267
+ /* window 布局:位置由拖拽内联样式控制,仅淡入避免与 transform 冲突 */
268
+ .rs-dialog__content--window[data-state='open'] {
269
+ animation: rs-dialog-fade-in 220ms ease;
270
+ }
271
+ .rs-dialog__content--window[data-state='closed'] {
272
+ animation: rs-dialog-fade-out 150ms ease;
273
+ }
274
+ @keyframes rs-dialog-overlay-in {
275
+ from {
276
+ opacity: 0;
277
+ }
278
+ to {
279
+ opacity: 1;
280
+ }
281
+ }
282
+ @keyframes rs-dialog-overlay-out {
283
+ from {
284
+ opacity: 1;
285
+ }
286
+ to {
287
+ opacity: 0;
288
+ }
289
+ }
290
+ @keyframes rs-dialog-pop-in {
291
+ from {
292
+ opacity: 0;
293
+ transform: translate(-50%, -46%) scale(0.96);
294
+ }
295
+ to {
296
+ opacity: 1;
297
+ transform: translate(-50%, -50%) scale(1);
298
+ }
299
+ }
300
+ @keyframes rs-dialog-pop-out {
301
+ from {
302
+ opacity: 1;
303
+ transform: translate(-50%, -50%) scale(1);
304
+ }
305
+ to {
306
+ opacity: 0;
307
+ transform: translate(-50%, -47%) scale(0.97);
308
+ }
309
+ }
310
+ @keyframes rs-dialog-fade-in {
311
+ from {
312
+ opacity: 0;
313
+ }
314
+ to {
315
+ opacity: 1;
316
+ }
317
+ }
318
+ @keyframes rs-dialog-fade-out {
319
+ from {
320
+ opacity: 1;
321
+ }
322
+ to {
323
+ opacity: 0;
324
+ }
325
+ }
326
+ .rs-dialog__content--window {
327
+ left: 50%;
328
+ top: 50%;
329
+ max-width: none;
330
+ max-height: none;
331
+ transform: none;
332
+ }
333
+ .rs-dialog__content--window.rs-dialog__content--bounds-transition {
334
+ transition:
335
+ left 220ms cubic-bezier(0.32, 0.72, 0, 1),
336
+ top 220ms cubic-bezier(0.32, 0.72, 0, 1),
337
+ width 220ms cubic-bezier(0.32, 0.72, 0, 1),
338
+ height 220ms cubic-bezier(0.32, 0.72, 0, 1),
339
+ border-radius 220ms cubic-bezier(0.32, 0.72, 0, 1);
340
+ }
341
+ .rs-dialog__content--sm {
342
+ max-width: 24rem;
343
+ }
344
+ .rs-dialog__content--md {
345
+ max-width: 32rem;
346
+ }
347
+ .rs-dialog__content--lg {
348
+ max-width: 42rem;
349
+ }
350
+ .rs-dialog__content--window.rs-dialog__content--sm,
351
+ .rs-dialog__content--window.rs-dialog__content--md,
352
+ .rs-dialog__content--window.rs-dialog__content--lg {
353
+ max-width: none;
354
+ }
355
+ .rs-dialog__content--fullscreen {
356
+ border-radius: var(--rs-radius);
357
+ }
358
+ .rs-dialog__header {
359
+ display: flex;
360
+ align-items: center;
361
+ justify-content: space-between;
362
+ gap: 0.75rem;
363
+ flex-shrink: 0;
364
+ padding: 0.625rem 0.75rem 0.625rem 1.25rem;
365
+ background: var(--rs-dialog-header-bg);
366
+ border-bottom: 1px solid var(--rs-dialog-separator);
367
+ box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.05);
368
+ }
369
+ .rs-dialog__content--window.rs-dialog__content--draggable .rs-dialog__header {
370
+ cursor: grab;
371
+ user-select: none;
372
+ }
373
+ .rs-dialog__title {
374
+ margin: 0;
375
+ font-size: var(--rs-font-size-base);
376
+ font-weight: 600;
377
+ letter-spacing: -0.01em;
378
+ color: var(--rs-dialog-title-fg);
379
+ }
380
+ .rs-dialog__description {
381
+ margin: 0.25rem 0 0;
382
+ font-size: var(--rs-font-size-sm);
383
+ color: var(--rs-dialog-description-fg);
384
+ line-height: var(--rs-line-height-normal);
385
+ }
386
+ .rs-dialog__description--sr-only {
387
+ position: absolute;
388
+ width: 1px;
389
+ height: 1px;
390
+ padding: 0;
391
+ margin: -1px;
392
+ overflow: hidden;
393
+ clip: rect(0, 0, 0, 0);
394
+ white-space: nowrap;
395
+ border: 0;
396
+ }
397
+ .rs-dialog__body {
398
+ flex: 1;
399
+ min-height: 0;
400
+ overflow: auto;
401
+ padding: 1.25rem;
402
+ background: var(--rs-dialog-body-bg);
403
+ }
404
+ .rs-dialog__content--window .rs-dialog__body {
405
+ display: flex;
406
+ flex-direction: column;
407
+ }
408
+ .rs-dialog__body-placeholder {
409
+ flex: 1;
410
+ min-height: 0;
411
+ }
412
+ .rs-dialog__footer {
413
+ display: flex;
414
+ justify-content: flex-end;
415
+ gap: 0.5rem;
416
+ flex-shrink: 0;
417
+ padding: 0.75rem 1.25rem;
418
+ background: var(--rs-dialog-footer-bg);
419
+ border-top: 1px solid var(--rs-dialog-separator);
420
+ }
421
+ [data-rs-theme='light'] .rs-dialog__header {
422
+ box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.75);
423
+ }
424
+ .rs-dialog__actions {
425
+ display: inline-flex;
426
+ align-items: center;
427
+ gap: var(--rs-space-xs);
428
+ }
429
+ .rs-dialog__resize-handle {
430
+ position: absolute;
431
+ z-index: 2;
432
+ }
433
+ .rs-dialog__resize-handle--n,
434
+ .rs-dialog__resize-handle--s {
435
+ left: 0.5rem;
436
+ right: 0.5rem;
437
+ height: 0.5rem;
438
+ cursor: ns-resize;
439
+ }
440
+ .rs-dialog__resize-handle--n {
441
+ top: -0.25rem;
442
+ }
443
+ .rs-dialog__resize-handle--s {
444
+ bottom: -0.25rem;
445
+ }
446
+ .rs-dialog__resize-handle--e,
447
+ .rs-dialog__resize-handle--w {
448
+ top: 0.5rem;
449
+ bottom: 0.5rem;
450
+ width: 0.5rem;
451
+ cursor: ew-resize;
452
+ }
453
+ .rs-dialog__resize-handle--e {
454
+ right: -0.25rem;
455
+ }
456
+ .rs-dialog__resize-handle--w {
457
+ left: -0.25rem;
458
+ }
459
+ .rs-dialog__resize-handle--ne,
460
+ .rs-dialog__resize-handle--nw,
461
+ .rs-dialog__resize-handle--se,
462
+ .rs-dialog__resize-handle--sw {
463
+ width: 0.75rem;
464
+ height: 0.75rem;
465
+ }
466
+ .rs-dialog__resize-handle--ne {
467
+ top: -0.25rem;
468
+ right: -0.25rem;
469
+ cursor: nesw-resize;
470
+ }
471
+ .rs-dialog__resize-handle--nw {
472
+ top: -0.25rem;
473
+ left: -0.25rem;
474
+ cursor: nwse-resize;
475
+ }
476
+ .rs-dialog__resize-handle--se {
477
+ right: -0.25rem;
478
+ bottom: -0.25rem;
479
+ cursor: nwse-resize;
480
+ }
481
+ .rs-dialog__resize-handle--sw {
482
+ left: -0.25rem;
483
+ bottom: -0.25rem;
484
+ cursor: nesw-resize;
485
+ }
486
+ </style>
@@ -0,0 +1,175 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ DialogClose,
4
+ DialogContent,
5
+ DialogDescription,
6
+ DialogOverlay,
7
+ DialogPortal,
8
+ DialogRoot,
9
+ DialogTitle,
10
+ } from './reka'
11
+ import RsButton from './RsButton.vue'
12
+ import { useRsI18n } from '../composables/useRsI18n'
13
+
14
+ export type RsDrawerSide = 'left' | 'right' | 'top' | 'bottom'
15
+ export type RsDrawerSize = 'sm' | 'md' | 'lg' | 'full'
16
+
17
+ const open = defineModel<boolean>('open', { default: false })
18
+
19
+ withDefaults(
20
+ defineProps<{
21
+ title?: string
22
+ description?: string
23
+ side?: RsDrawerSide
24
+ size?: RsDrawerSize
25
+ showOverlay?: boolean
26
+ showClose?: boolean
27
+ closeOnOverlayClick?: boolean
28
+ }>(),
29
+ {
30
+ side: 'right',
31
+ size: 'md',
32
+ showOverlay: true,
33
+ showClose: true,
34
+ closeOnOverlayClick: true,
35
+ },
36
+ )
37
+
38
+ const { t } = useRsI18n()
39
+ </script>
40
+
41
+ <template>
42
+ <DialogRoot v-model:open="open">
43
+ <DialogPortal>
44
+ <DialogOverlay v-if="showOverlay" class="rs-drawer__overlay" />
45
+ <DialogContent
46
+ class="rs-drawer__content"
47
+ :class="[`rs-drawer__content--${side}`, `rs-drawer__content--${size}`]"
48
+ @pointer-down-outside="closeOnOverlayClick ? undefined : $event.preventDefault()"
49
+ >
50
+ <header v-if="title || description || showClose || $slots.header" class="rs-drawer__header">
51
+ <slot name="header">
52
+ <div class="rs-drawer__heading">
53
+ <DialogTitle v-if="title" class="rs-drawer__title">{{ title }}</DialogTitle>
54
+ <DialogDescription v-if="description" class="rs-drawer__description">
55
+ {{ description }}
56
+ </DialogDescription>
57
+ </div>
58
+ </slot>
59
+ <DialogClose v-if="showClose" as-child>
60
+ <RsButton variant="ghost" size="sm" icon="x" :tooltip="t('dialog.close')" />
61
+ </DialogClose>
62
+ </header>
63
+ <div class="rs-drawer__body">
64
+ <slot />
65
+ </div>
66
+ <footer v-if="$slots.footer" class="rs-drawer__footer">
67
+ <slot name="footer" />
68
+ </footer>
69
+ </DialogContent>
70
+ </DialogPortal>
71
+ </DialogRoot>
72
+ </template>
73
+
74
+ <style>
75
+ .rs-drawer__overlay {
76
+ position: fixed;
77
+ inset: 0;
78
+ z-index: var(--rs-z-modal);
79
+ background: rgb(0 0 0 / 0.45);
80
+ backdrop-filter: blur(4px);
81
+ }
82
+ .rs-drawer__content {
83
+ position: fixed;
84
+ z-index: calc(var(--rs-z-modal) + 1);
85
+ display: flex;
86
+ flex-direction: column;
87
+ overflow: hidden;
88
+ box-sizing: border-box;
89
+ border: 1px solid var(--rs-border);
90
+ background: var(--rs-surface-elevated);
91
+ color: var(--rs-text);
92
+ box-shadow: var(--rs-shadow-lg);
93
+ outline: none;
94
+ }
95
+ .rs-drawer__content--right,
96
+ .rs-drawer__content--left {
97
+ top: 0;
98
+ bottom: 0;
99
+ width: min(100vw, 28rem);
100
+ }
101
+ .rs-drawer__content--right {
102
+ right: 0;
103
+ border-right: 0;
104
+ border-radius: var(--rs-radius) 0 0 var(--rs-radius);
105
+ }
106
+ .rs-drawer__content--left {
107
+ left: 0;
108
+ border-left: 0;
109
+ border-radius: 0 var(--rs-radius) var(--rs-radius) 0;
110
+ }
111
+ .rs-drawer__content--top,
112
+ .rs-drawer__content--bottom {
113
+ left: 0;
114
+ right: 0;
115
+ height: min(100vh, 22rem);
116
+ }
117
+ .rs-drawer__content--top {
118
+ top: 0;
119
+ border-top: 0;
120
+ border-radius: 0 0 var(--rs-radius) var(--rs-radius);
121
+ }
122
+ .rs-drawer__content--bottom {
123
+ bottom: 0;
124
+ border-bottom: 0;
125
+ border-radius: var(--rs-radius) var(--rs-radius) 0 0;
126
+ }
127
+ .rs-drawer__content--sm:is(.rs-drawer__content--left, .rs-drawer__content--right) {
128
+ width: min(100vw, 20rem);
129
+ }
130
+ .rs-drawer__content--lg:is(.rs-drawer__content--left, .rs-drawer__content--right) {
131
+ width: min(100vw, 36rem);
132
+ }
133
+ .rs-drawer__content--full:is(.rs-drawer__content--left, .rs-drawer__content--right) {
134
+ width: 100vw;
135
+ }
136
+ .rs-drawer__content--sm:is(.rs-drawer__content--top, .rs-drawer__content--bottom) {
137
+ height: min(100vh, 16rem);
138
+ }
139
+ .rs-drawer__content--lg:is(.rs-drawer__content--top, .rs-drawer__content--bottom) {
140
+ height: min(100vh, 32rem);
141
+ }
142
+ .rs-drawer__content--full:is(.rs-drawer__content--top, .rs-drawer__content--bottom) {
143
+ height: 100vh;
144
+ }
145
+ .rs-drawer__header,
146
+ .rs-drawer__footer {
147
+ display: flex;
148
+ align-items: flex-start;
149
+ justify-content: space-between;
150
+ gap: var(--rs-space-md);
151
+ padding: var(--rs-space-lg) var(--rs-space-xl);
152
+ border-bottom: 1px solid var(--rs-border-subtle);
153
+ }
154
+ .rs-drawer__footer {
155
+ justify-content: flex-end;
156
+ border-top: 1px solid var(--rs-border-subtle);
157
+ border-bottom: 0;
158
+ }
159
+ .rs-drawer__body {
160
+ flex: 1;
161
+ min-height: 0;
162
+ overflow: auto;
163
+ padding: var(--rs-space-xl);
164
+ }
165
+ .rs-drawer__title {
166
+ margin: 0;
167
+ font-size: var(--rs-font-size-base);
168
+ font-weight: 600;
169
+ }
170
+ .rs-drawer__description {
171
+ margin: var(--rs-space-xs) 0 0;
172
+ color: var(--rs-muted);
173
+ font-size: var(--rs-font-size-sm);
174
+ }
175
+ </style>