niuma-ui 1.0.0 → 1.0.2

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 (159) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/NOTICE +2 -2
  3. package/package.json +13 -5
  4. package/src/__tests__/RsButton.spec.ts +47 -0
  5. package/src/__tests__/RsCard.spec.ts +160 -1
  6. package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
  7. package/src/__tests__/RsContextMenu.spec.ts +12 -0
  8. package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
  9. package/src/__tests__/RsDescriptions.spec.ts +50 -0
  10. package/src/__tests__/RsDialog.spec.ts +117 -0
  11. package/src/__tests__/RsDrawer.spec.ts +96 -3
  12. package/src/__tests__/RsDynamicTags.spec.ts +52 -0
  13. package/src/__tests__/RsForm.spec.ts +245 -1
  14. package/src/__tests__/RsInput.spec.ts +15 -1
  15. package/src/__tests__/RsMenu.spec.ts +406 -14
  16. package/src/__tests__/RsPagination.spec.ts +67 -2
  17. package/src/__tests__/RsSelect.spec.ts +42 -1
  18. package/src/__tests__/RsSwitch.spec.ts +44 -0
  19. package/src/__tests__/RsTable.a11y.spec.ts +83 -0
  20. package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
  21. package/src/__tests__/RsTable.perf.spec.ts +23 -4
  22. package/src/__tests__/RsTable.spec.ts +63 -8
  23. package/src/__tests__/RsTable.ssr.spec.ts +71 -0
  24. package/src/__tests__/RsTable.summary.spec.ts +45 -0
  25. package/src/__tests__/RsTabs.spec.ts +102 -4
  26. package/src/__tests__/RsTerminal.spec.ts +1 -1
  27. package/src/__tests__/RsTimePicker.spec.ts +11 -0
  28. package/src/__tests__/RsUpload.spec.ts +62 -6
  29. package/src/__tests__/createRsDialog.spec.ts +145 -0
  30. package/src/__tests__/form-rules.spec.ts +76 -0
  31. package/src/__tests__/perf-budgets.ts +32 -0
  32. package/src/__tests__/rs-table-api.spec.ts +206 -0
  33. package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
  34. package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
  35. package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
  36. package/src/__tests__/rs-table-isolation.spec.ts +158 -0
  37. package/src/__tests__/split-pane-utils.spec.ts +21 -0
  38. package/src/__tests__/table-chart-utils.spec.ts +88 -0
  39. package/src/__tests__/table-summary-utils.spec.ts +62 -0
  40. package/src/__tests__/table-utils.spec.ts +124 -0
  41. package/src/__tests__/theme.spec.ts +25 -0
  42. package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
  43. package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
  44. package/src/components/RsAlert.vue +212 -0
  45. package/src/components/RsButton.vue +145 -21
  46. package/src/components/RsCard.vue +200 -16
  47. package/src/components/RsConfirmDialog.vue +215 -28
  48. package/src/components/RsContextMenu.vue +51 -13
  49. package/src/components/RsDatePicker.vue +267 -34
  50. package/src/components/RsDateTimePicker.vue +19 -2
  51. package/src/components/RsDescriptions.vue +186 -0
  52. package/src/components/RsDescriptionsItem.vue +32 -0
  53. package/src/components/RsDialog.vue +363 -51
  54. package/src/components/RsDivider.vue +89 -0
  55. package/src/components/RsDrawer.vue +558 -33
  56. package/src/components/RsDropdown.vue +33 -2
  57. package/src/components/RsDynamicTags.vue +517 -0
  58. package/src/components/RsForm.vue +97 -8
  59. package/src/components/RsInput.vue +80 -82
  60. package/src/components/RsInputNumber.vue +64 -23
  61. package/src/components/RsLabel.vue +20 -5
  62. package/src/components/RsLoadingBar.vue +151 -0
  63. package/src/components/RsMenu.vue +73 -15
  64. package/src/components/RsMenuItems.vue +147 -9
  65. package/src/components/RsPagination.vue +93 -14
  66. package/src/components/RsRadio.vue +81 -0
  67. package/src/components/RsRadioItem.vue +156 -0
  68. package/src/components/RsSelect.vue +67 -58
  69. package/src/components/RsSplitPane.vue +69 -1
  70. package/src/components/RsSwitch.vue +179 -0
  71. package/src/components/RsTable.vue +653 -2555
  72. package/src/components/RsTabs.vue +415 -83
  73. package/src/components/RsTag.vue +191 -0
  74. package/src/components/RsTerminal.vue +3 -3
  75. package/src/components/RsTimePicker.vue +44 -5
  76. package/src/components/RsTimePickerColumns.vue +136 -77
  77. package/src/components/RsToaster.vue +58 -18
  78. package/src/components/RsTree.vue +39 -21
  79. package/src/components/RsUpload.vue +145 -12
  80. package/src/components/RsVNodeHost.vue +20 -0
  81. package/src/components/button-utils.ts +39 -0
  82. package/src/components/context-menu-utils.ts +6 -0
  83. package/src/components/date-picker-utils.ts +84 -4
  84. package/src/components/descriptions-utils.ts +24 -0
  85. package/src/components/dialog-utils.ts +147 -0
  86. package/src/components/dialog-window.ts +74 -9
  87. package/src/components/drawer-utils.ts +73 -0
  88. package/src/components/form-rules.ts +257 -0
  89. package/src/components/form-utils.ts +52 -2
  90. package/src/components/menu-utils.ts +11 -0
  91. package/src/components/overlay-utils.ts +3 -0
  92. package/src/components/radio-utils.ts +12 -0
  93. package/src/components/reka.ts +5 -0
  94. package/src/components/select-utils.ts +21 -2
  95. package/src/components/split-pane-utils.ts +65 -12
  96. package/src/components/table/RsTableBody.vue +217 -0
  97. package/src/components/table/RsTableBodyRow.vue +90 -1
  98. package/src/components/table/RsTableColGroup.vue +41 -0
  99. package/src/components/table/RsTableHeader.vue +158 -0
  100. package/src/components/table/RsTableSummaryRow.vue +52 -0
  101. package/src/components/table/rs-table-api.ts +99 -0
  102. package/src/components/table/rs-table-compat-matrix.ts +71 -0
  103. package/src/components/table/rs-table-echarts-adapter.ts +81 -0
  104. package/src/components/table/rs-table-feature-host.ts +178 -0
  105. package/src/components/table/rs-table-grid-nav.ts +122 -0
  106. package/src/components/table/rs-table-header-types.ts +47 -0
  107. package/src/components/table/rs-table-module-registry.ts +72 -0
  108. package/src/components/table/rs-table-props.ts +274 -0
  109. package/src/components/table/rs-table-view-context.ts +258 -0
  110. package/src/components/table/rs-table.css +84 -6
  111. package/src/components/table/table-chart-utils.ts +165 -0
  112. package/src/components/table/table-features.ts +284 -0
  113. package/src/components/table/table-summary-utils.ts +194 -0
  114. package/src/components/table-utils.ts +452 -2
  115. package/src/components/tabs-utils.ts +142 -2
  116. package/src/components/terminal-utils.ts +1 -1
  117. package/src/components/terminal-wheel.ts +1 -1
  118. package/src/components/time-picker-utils.ts +76 -3
  119. package/src/components/upload-utils.ts +122 -0
  120. package/src/components/virtual-list-utils.ts +15 -2
  121. package/src/composables/assembleRsTableApi.ts +79 -0
  122. package/src/composables/bindRsTableViewContext.ts +269 -0
  123. package/src/composables/createRsDialog.ts +357 -0
  124. package/src/composables/createRsTableEditEmitBridge.ts +38 -0
  125. package/src/composables/useRsLoadingBar.ts +36 -0
  126. package/src/composables/useRsTable.ts +134 -0
  127. package/src/composables/useRsTableA11y.ts +52 -0
  128. package/src/composables/useRsTableChartBridge.ts +79 -0
  129. package/src/composables/useRsTableColumnLayout.ts +320 -0
  130. package/src/composables/useRsTableColumnResize.ts +244 -0
  131. package/src/composables/useRsTableColumnVirtual.ts +180 -0
  132. package/src/composables/useRsTableColumns.ts +200 -0
  133. package/src/composables/useRsTableContextMenu.ts +257 -0
  134. package/src/composables/useRsTableCore.ts +256 -0
  135. package/src/composables/useRsTableEditActions.ts +606 -0
  136. package/src/composables/useRsTableEditLayer.ts +251 -0
  137. package/src/composables/useRsTableEngine.ts +357 -0
  138. package/src/composables/useRsTableGridKeyboard.ts +123 -0
  139. package/src/composables/useRsTableHeadless.ts +137 -0
  140. package/src/composables/useRsTableInteraction.ts +433 -0
  141. package/src/composables/useRsTableScrollHost.ts +75 -0
  142. package/src/composables/useRsTableScrollLayout.ts +255 -0
  143. package/src/composables/useRsTableSelectionSource.ts +89 -0
  144. package/src/composables/useRsTableShell.ts +416 -0
  145. package/src/composables/useRsTableShellChrome.ts +170 -0
  146. package/src/composables/useRsTableSummary.ts +113 -0
  147. package/src/composables/useRsTableViewProvide.ts +263 -0
  148. package/src/composables/useRsTableVirtual.ts +154 -0
  149. package/src/composables/useRsTabsNav.ts +4 -0
  150. package/src/composables/useTableEdit.ts +1 -1
  151. package/src/icons/lucide.ts +2 -2
  152. package/src/index.ts +309 -9
  153. package/src/locale/messages.ts +30 -0
  154. package/src/styles.css +305 -52
  155. package/src/theme/brand.example.css +21 -2
  156. package/src/theme/presets.ts +4 -0
  157. package/src/theme/types.ts +67 -0
  158. package/src/components/RsTableColgroup.vue +0 -17
  159. package/src/components/RsTableHeaderRow.vue +0 -30
@@ -0,0 +1,191 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useRsI18n } from '../composables/useRsI18n'
4
+ import type { RsComponentSize, RsRadius } from '../theme/types'
5
+ import { rsRadiusCss, useResolvedRsRadius } from './resolve-radius'
6
+ import { useResolvedRsComponentSize } from './resolve-size'
7
+ import RsIcon from './RsIcon.vue'
8
+
9
+ export type RsTagVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
10
+
11
+ const props = withDefaults(
12
+ defineProps<{
13
+ variant?: RsTagVariant
14
+ closable?: boolean
15
+ disabled?: boolean
16
+ /** 胶囊圆角快捷开关;等价于 radius="full" */
17
+ round?: boolean
18
+ size?: RsComponentSize
19
+ radius?: RsRadius
20
+ }>(),
21
+ {
22
+ variant: 'default',
23
+ closable: false,
24
+ disabled: false,
25
+ round: false,
26
+ },
27
+ )
28
+
29
+ const emit = defineEmits<{
30
+ close: [event: MouseEvent]
31
+ }>()
32
+
33
+ const { t } = useRsI18n()
34
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
35
+ const resolvedRadius = useResolvedRsRadius(
36
+ () => (props.round ? 'full' : props.radius),
37
+ 'sm',
38
+ )
39
+
40
+ const rootClass = computed(() => [
41
+ 'rs-tag',
42
+ `rs-tag--${props.variant}`,
43
+ `rs-tag--${resolvedSize.value}`,
44
+ {
45
+ 'rs-tag--closable': props.closable,
46
+ 'rs-tag--disabled': props.disabled,
47
+ 'rs-tag--round': props.round || resolvedRadius.value === 'full',
48
+ },
49
+ ])
50
+
51
+ const rootStyle = computed(() => ({
52
+ '--rs-tag-radius': rsRadiusCss(resolvedRadius.value),
53
+ }))
54
+
55
+ function onClose(event: MouseEvent): void {
56
+ if (props.disabled) return
57
+ event.stopPropagation()
58
+ emit('close', event)
59
+ }
60
+ </script>
61
+
62
+ <template>
63
+ <span :class="rootClass" :style="rootStyle">
64
+ <span class="rs-tag__content">
65
+ <slot />
66
+ </span>
67
+ <button
68
+ v-if="closable"
69
+ type="button"
70
+ class="rs-tag__close"
71
+ :disabled="disabled"
72
+ :aria-label="t('tag.close')"
73
+ @click="onClose"
74
+ >
75
+ <RsIcon name="x" :size="12" />
76
+ </button>
77
+ </span>
78
+ </template>
79
+
80
+ <style scoped>
81
+ .rs-tag {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ gap: var(--rs-space-xs);
85
+ max-width: 100%;
86
+ border-radius: var(--rs-tag-radius, var(--rs-radius-sm));
87
+ font-weight: var(--rs-tag-font-weight, 500);
88
+ line-height: var(--rs-line-height-tight);
89
+ box-sizing: border-box;
90
+ vertical-align: middle;
91
+ }
92
+
93
+ .rs-tag--ssm {
94
+ padding: 0 var(--rs-space-xs);
95
+ font-size: var(--rs-font-size-xs);
96
+ }
97
+
98
+ .rs-tag--sm {
99
+ padding: 0.0625rem var(--rs-space-xs);
100
+ font-size: var(--rs-font-size-xs);
101
+ }
102
+
103
+ .rs-tag--md {
104
+ padding: 0.125rem var(--rs-space-sm);
105
+ font-size: var(--rs-font-size-xs);
106
+ }
107
+
108
+ .rs-tag--lg {
109
+ padding: var(--rs-space-xs) var(--rs-space-sm);
110
+ font-size: var(--rs-font-size-sm);
111
+ }
112
+
113
+ .rs-tag--round {
114
+ border-radius: var(--rs-radius-full);
115
+ }
116
+
117
+ .rs-tag--disabled {
118
+ opacity: 0.55;
119
+ cursor: not-allowed;
120
+ }
121
+
122
+ .rs-tag__content {
123
+ overflow: hidden;
124
+ text-overflow: ellipsis;
125
+ white-space: nowrap;
126
+ }
127
+
128
+ .rs-tag__close {
129
+ display: inline-flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ margin: 0;
133
+ padding: 0;
134
+ border: 0;
135
+ border-radius: var(--rs-radius-full);
136
+ background: transparent;
137
+ color: inherit;
138
+ cursor: pointer;
139
+ opacity: 0.75;
140
+ outline: none;
141
+ }
142
+
143
+ .rs-tag__close:hover:not(:disabled) {
144
+ opacity: 1;
145
+ background: color-mix(in srgb, currentColor 12%, transparent);
146
+ }
147
+
148
+ .rs-tag__close:focus-visible {
149
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
150
+ }
151
+
152
+ .rs-tag__close:disabled {
153
+ cursor: not-allowed;
154
+ }
155
+
156
+ .rs-tag--default {
157
+ background: var(--rs-surface-hover);
158
+ color: var(--rs-text);
159
+ border: 1px solid var(--rs-border);
160
+ }
161
+
162
+ .rs-tag--primary {
163
+ background: var(--rs-primary-container);
164
+ color: var(--rs-on-primary-container);
165
+ border: 1px solid color-mix(in srgb, var(--rs-primary) var(--rs-tag-border-alpha, 40%), transparent);
166
+ }
167
+
168
+ .rs-tag--success {
169
+ background: var(--rs-success-container);
170
+ color: var(--rs-on-success-container);
171
+ border: 1px solid color-mix(in srgb, var(--rs-success) var(--rs-tag-border-alpha, 40%), transparent);
172
+ }
173
+
174
+ .rs-tag--warning {
175
+ background: var(--rs-warning-container);
176
+ color: var(--rs-on-warning-container);
177
+ border: 1px solid color-mix(in srgb, var(--rs-warning) var(--rs-tag-border-alpha, 40%), transparent);
178
+ }
179
+
180
+ .rs-tag--danger {
181
+ background: var(--rs-danger-container);
182
+ color: var(--rs-on-danger-container);
183
+ border: 1px solid color-mix(in srgb, var(--rs-danger) var(--rs-tag-border-alpha, 40%), transparent);
184
+ }
185
+
186
+ .rs-tag--info {
187
+ background: var(--rs-info-container);
188
+ color: var(--rs-on-info-container);
189
+ border: 1px solid color-mix(in srgb, var(--rs-info) var(--rs-tag-border-alpha, 40%), transparent);
190
+ }
191
+ </style>
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { FitAddon } from '@xterm/addon-fit'
3
- import type { ITheme } from 'xterm'
4
- import { Terminal } from 'xterm'
5
- import 'xterm/css/xterm.css'
3
+ import type { ITheme } from '@xterm/xterm'
4
+ import { Terminal } from '@xterm/xterm'
5
+ import '@xterm/xterm/css/xterm.css'
6
6
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
7
7
  import { useRsI18n } from '../composables/useRsI18n'
8
8
  import {
@@ -4,6 +4,8 @@ import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from './re
4
4
  import RsIcon from './RsIcon.vue'
5
5
  import RsTimePickerColumns from './RsTimePickerColumns.vue'
6
6
  import { useRsI18n } from '../composables/useRsI18n'
7
+ import { RS_COMPONENT_SIZE_ICON_PX, type RsComponentSize } from '../theme/types'
8
+ import { useResolvedRsComponentSize } from './resolve-size'
7
9
  import {
8
10
  EMPTY_TIME_RANGE,
9
11
  formatTimeFromParts,
@@ -16,6 +18,7 @@ import {
16
18
  pickEarlierTime,
17
19
  pickLaterTime,
18
20
  type RsTimeRangeValue,
21
+ type RsTimeUnit,
19
22
  } from './time-picker-utils'
20
23
 
21
24
  defineOptions({ name: 'RsTimePicker' })
@@ -43,6 +46,7 @@ const props = withDefaults(
43
46
  maxTime?: string
44
47
  embedded?: boolean
45
48
  labelPosition?: RsTimePickerLabelPosition
49
+ size?: RsComponentSize
46
50
  }>(),
47
51
  {
48
52
  disabled: false,
@@ -56,6 +60,8 @@ const props = withDefaults(
56
60
 
57
61
  const fieldId = useId()
58
62
  const { t } = useRsI18n()
63
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
64
+ const triggerIconSize = computed(() => RS_COMPONENT_SIZE_ICON_PX[resolvedSize.value])
59
65
  const columnsRef = useTemplateRef<InstanceType<typeof RsTimePickerColumns>>('columnsRef')
60
66
 
61
67
  const draftHour = ref(0)
@@ -171,7 +177,7 @@ function syncDraftFromModel(): void {
171
177
  syncSingleDraft()
172
178
  }
173
179
 
174
- function isUnitDisabled(unit: 'hour' | 'minute' | 'second', value: number): boolean {
180
+ function isUnitDisabled(unit: RsTimeUnit, value: number): boolean {
175
181
  const hour = unit === 'hour' ? value : draftHour.value
176
182
  const minute = unit === 'minute' ? value : draftMinute.value
177
183
  const second = unit === 'second' ? value : draftSecond.value
@@ -275,6 +281,7 @@ watch(open, async (isOpen) => {
275
281
  <div
276
282
  :class="[
277
283
  embedded ? 'rs-time-picker-embedded' : 'rs-field',
284
+ `rs-time-picker--${resolvedSize}`,
278
285
  !embedded && `rs-field--label-${labelPosition}`,
279
286
  ]"
280
287
  >
@@ -297,10 +304,15 @@ watch(open, async (isOpen) => {
297
304
  :disabled="disabled"
298
305
  >
299
306
  <span class="rs-time-picker__leading">
300
- <RsIcon v-if="!embedded" name="clock" :size="16" class="rs-time-picker__icon" />
307
+ <RsIcon
308
+ v-if="!embedded"
309
+ name="clock"
310
+ :size="triggerIconSize"
311
+ class="rs-time-picker__icon"
312
+ />
301
313
  <span class="rs-time-picker__value">{{ displayValue }}</span>
302
314
  </span>
303
- <RsIcon name="chevron-down" :size="16" class="rs-time-picker__chevron" />
315
+ <RsIcon name="chevron-down" :size="triggerIconSize" class="rs-time-picker__chevron" />
304
316
  </PopoverTrigger>
305
317
 
306
318
  <PopoverPortal>
@@ -321,6 +333,7 @@ watch(open, async (isOpen) => {
321
333
  <RsTimePicker
322
334
  v-model="draftStartTime"
323
335
  embedded
336
+ :size="resolvedSize"
324
337
  :with-seconds="withSeconds"
325
338
  :min-time="minTime"
326
339
  :max-time="startMaxTime"
@@ -333,6 +346,7 @@ watch(open, async (isOpen) => {
333
346
  <RsTimePicker
334
347
  v-model="draftEndTime"
335
348
  embedded
349
+ :size="resolvedSize"
336
350
  :with-seconds="withSeconds"
337
351
  :min-time="endMinTime"
338
352
  :max-time="maxTime"
@@ -347,6 +361,7 @@ watch(open, async (isOpen) => {
347
361
  ref="columnsRef"
348
362
  v-model="draftTime"
349
363
  :second="withSeconds"
364
+ :size="resolvedSize"
350
365
  :disabled="disabled"
351
366
  :is-unit-disabled="isUnitDisabled"
352
367
  />
@@ -392,7 +407,9 @@ watch(open, async (isOpen) => {
392
407
  align-items: center;
393
408
  justify-content: space-between;
394
409
  gap: var(--rs-space-sm);
410
+ box-sizing: border-box;
395
411
  width: 100%;
412
+ height: var(--rs-control-height-md);
396
413
  min-height: var(--rs-control-height-md);
397
414
  padding: 0 var(--rs-space-md);
398
415
  border: 1px solid var(--rs-input-border, var(--rs-border));
@@ -401,6 +418,7 @@ watch(open, async (isOpen) => {
401
418
  color: var(--rs-text);
402
419
  font: inherit;
403
420
  font-size: var(--rs-font-size-sm);
421
+ line-height: var(--rs-line-height-tight);
404
422
  text-align: left;
405
423
  cursor: pointer;
406
424
  box-shadow: var(--rs-input-shadow, none);
@@ -409,13 +427,34 @@ watch(open, async (isOpen) => {
409
427
  box-shadow var(--rs-transition-fast),
410
428
  background var(--rs-transition-fast);
411
429
  }
412
- .rs-time-picker__trigger--embedded {
430
+ .rs-time-picker--ssm .rs-time-picker__trigger {
431
+ height: var(--rs-control-height-ssm);
432
+ min-height: var(--rs-control-height-ssm);
433
+ padding: 0 var(--rs-space-xs);
434
+ font-size: var(--rs-font-size-xs);
435
+ }
436
+ .rs-time-picker--sm .rs-time-picker__trigger {
437
+ height: var(--rs-control-height-sm);
413
438
  min-height: var(--rs-control-height-sm);
414
- min-width: 5.5rem;
415
439
  padding: 0 var(--rs-space-sm);
416
440
  font-size: var(--rs-font-size-xs);
441
+ }
442
+ .rs-time-picker--lg .rs-time-picker__trigger {
443
+ height: var(--rs-control-height-lg);
444
+ min-height: var(--rs-control-height-lg);
445
+ padding: 0 var(--rs-space-lg);
446
+ font-size: var(--rs-font-size-base);
447
+ }
448
+ .rs-time-picker__trigger--embedded {
449
+ min-width: 5.5rem;
417
450
  background: color-mix(in srgb, var(--rs-surface) 72%, transparent);
418
451
  }
452
+ .rs-time-picker--ssm .rs-time-picker__trigger--embedded {
453
+ min-width: 4.75rem;
454
+ }
455
+ .rs-time-picker--lg .rs-time-picker__trigger--embedded {
456
+ min-width: 6.25rem;
457
+ }
419
458
  .rs-time-picker__trigger:hover:not(:disabled) {
420
459
  border-color: var(--rs-input-border-hover, var(--rs-border));
421
460
  }
@@ -1,12 +1,21 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, nextTick, onMounted, onUnmounted, useTemplateRef, watch } from 'vue'
3
3
  import { useRsI18n } from '../composables/useRsI18n'
4
+ import type { RsComponentSize } from '../theme/types'
5
+ import { useResolvedRsComponentSize } from './resolve-size'
4
6
  import {
7
+ formatTimeFromParts,
8
+ getTimeMinuteOptions,
5
9
  parseTimeValue,
6
- rangeInclusive,
7
10
  scheduleAfterPaint,
11
+ scrollTimeColumnToValue,
12
+ TIME_HOUR_OPTIONS,
13
+ TIME_SECOND_OPTIONS,
14
+ type RsTimeUnit,
8
15
  } from './time-picker-utils'
9
16
 
17
+ defineOptions({ name: 'RsTimePickerColumns' })
18
+
10
19
  const model = defineModel<string>({ default: '00:00' })
11
20
 
12
21
  const props = withDefaults(
@@ -14,7 +23,8 @@ const props = withDefaults(
14
23
  minuteStep?: number
15
24
  second?: boolean
16
25
  disabled?: boolean
17
- isUnitDisabled?: (unit: 'hour' | 'minute' | 'second', value: number) => boolean
26
+ size?: RsComponentSize
27
+ isUnitDisabled?: (unit: RsTimeUnit, value: number) => boolean
18
28
  }>(),
19
29
  {
20
30
  minuteStep: 1,
@@ -24,6 +34,7 @@ const props = withDefaults(
24
34
  )
25
35
 
26
36
  const { t } = useRsI18n()
37
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
27
38
 
28
39
  const hourListRef = useTemplateRef<HTMLDivElement>('hourListRef')
29
40
  const minuteListRef = useTemplateRef<HTMLDivElement>('minuteListRef')
@@ -31,11 +42,9 @@ const secondListRef = useTemplateRef<HTMLDivElement>('secondListRef')
31
42
 
32
43
  let cancelScheduledScroll: (() => void) | undefined
33
44
 
34
- const hourOptions = computed(() => rangeInclusive(23))
35
- const minuteOptions = computed(() =>
36
- rangeInclusive(59).filter((value) => value % props.minuteStep === 0),
37
- )
38
- const secondOptions = computed(() => rangeInclusive(59))
45
+ const hourOptions = TIME_HOUR_OPTIONS
46
+ const secondOptions = TIME_SECOND_OPTIONS
47
+ const minuteOptions = computed(() => getTimeMinuteOptions(props.minuteStep))
39
48
 
40
49
  const parts = computed(() => {
41
50
  const parsed = parseTimeValue(model.value, props.second)
@@ -45,27 +54,19 @@ const parts = computed(() => {
45
54
  return { hour: 0, minute: 0, second: 0 }
46
55
  })
47
56
 
48
- function isDisabled(unit: 'hour' | 'minute' | 'second', value: number): boolean {
49
- return props.isUnitDisabled?.(unit, value) ?? false
57
+ function isDisabled(unit: RsTimeUnit, value: number): boolean {
58
+ return props.disabled || (props.isUnitDisabled?.(unit, value) ?? false)
50
59
  }
51
60
 
52
- function scrollColumn(
53
- container: HTMLDivElement | null,
54
- unit: 'hour' | 'minute' | 'second',
55
- value: number,
56
- ): void {
57
- if (!container) return
58
- const selected = container.querySelector<HTMLElement>(
59
- `[data-unit="${unit}"][data-value="${value}"]`,
60
- )
61
- selected?.scrollIntoView({ block: 'center', behavior: 'auto' })
61
+ function scrollColumn(container: HTMLDivElement | null, value: number): void {
62
+ scrollTimeColumnToValue(container, value)
62
63
  }
63
64
 
64
65
  function scrollToSelection(): void {
65
- scrollColumn(hourListRef.value, 'hour', parts.value.hour)
66
- scrollColumn(minuteListRef.value, 'minute', parts.value.minute)
66
+ scrollColumn(hourListRef.value, parts.value.hour)
67
+ scrollColumn(minuteListRef.value, parts.value.minute)
67
68
  if (props.second) {
68
- scrollColumn(secondListRef.value, 'second', parts.value.second)
69
+ scrollColumn(secondListRef.value, parts.value.second)
69
70
  }
70
71
  }
71
72
 
@@ -74,12 +75,10 @@ function scrollToSelectionAfterPaint(): void {
74
75
  cancelScheduledScroll = scheduleAfterPaint(scrollToSelection)
75
76
  }
76
77
 
77
- function selectUnit(unit: 'hour' | 'minute' | 'second', value: number): void {
78
- if (isDisabled(unit, value) || props.disabled) return
78
+ function selectUnit(unit: RsTimeUnit, value: number): void {
79
+ if (isDisabled(unit, value)) return
79
80
  const next = { ...parts.value, [unit]: value }
80
- model.value = props.second
81
- ? `${String(next.hour).padStart(2, '0')}:${String(next.minute).padStart(2, '0')}:${String(next.second).padStart(2, '0')}`
82
- : `${String(next.hour).padStart(2, '0')}:${String(next.minute).padStart(2, '0')}`
81
+ model.value = formatTimeFromParts(next.hour, next.minute, next.second, props.second)
83
82
  }
84
83
 
85
84
  watch(
@@ -101,65 +100,79 @@ onUnmounted(() => {
101
100
  defineExpose({
102
101
  scrollToSelection,
103
102
  scrollToSelectionAfterPaint,
103
+ resolvedSize,
104
104
  })
105
105
  </script>
106
106
 
107
107
  <template>
108
- <div class="rs-time-columns" :class="{ 'rs-time-columns--seconds': second }">
108
+ <div
109
+ class="rs-time-columns"
110
+ :class="[
111
+ `rs-time-columns--${resolvedSize}`,
112
+ { 'rs-time-columns--seconds': second },
113
+ ]"
114
+ >
109
115
  <div class="rs-time-columns__column">
110
116
  <span class="rs-time-columns__label">{{ t('timePicker.hour') }}</span>
111
- <div ref="hourListRef" class="rs-time-columns__list">
112
- <button
113
- v-for="option in hourOptions"
114
- :key="option"
115
- type="button"
116
- class="rs-time-columns__item"
117
- :class="{ 'rs-time-columns__item--active': parts.hour === option }"
118
- data-unit="hour"
119
- :data-value="option"
120
- :disabled="disabled || isDisabled('hour', option)"
121
- @click="selectUnit('hour', option)"
122
- >
123
- {{ String(option).padStart(2, '0') }}
124
- </button>
117
+ <!-- shell:overflow:hidden 裁剪滚动条,避免盖住圆角 -->
118
+ <div class="rs-time-columns__list-shell">
119
+ <div ref="hourListRef" class="rs-time-columns__list">
120
+ <button
121
+ v-for="option in hourOptions"
122
+ :key="option.value"
123
+ type="button"
124
+ class="rs-time-columns__item"
125
+ :class="{ 'rs-time-columns__item--active': parts.hour === option.value }"
126
+ data-unit="hour"
127
+ :data-value="option.value"
128
+ :disabled="isDisabled('hour', option.value)"
129
+ @click="selectUnit('hour', option.value)"
130
+ >
131
+ {{ option.label }}
132
+ </button>
133
+ </div>
125
134
  </div>
126
135
  </div>
127
136
 
128
137
  <div class="rs-time-columns__column">
129
138
  <span class="rs-time-columns__label">{{ t('timePicker.minute') }}</span>
130
- <div ref="minuteListRef" class="rs-time-columns__list">
131
- <button
132
- v-for="option in minuteOptions"
133
- :key="option"
134
- type="button"
135
- class="rs-time-columns__item"
136
- :class="{ 'rs-time-columns__item--active': parts.minute === option }"
137
- data-unit="minute"
138
- :data-value="option"
139
- :disabled="disabled || isDisabled('minute', option)"
140
- @click="selectUnit('minute', option)"
141
- >
142
- {{ String(option).padStart(2, '0') }}
143
- </button>
139
+ <div class="rs-time-columns__list-shell">
140
+ <div ref="minuteListRef" class="rs-time-columns__list">
141
+ <button
142
+ v-for="option in minuteOptions"
143
+ :key="option.value"
144
+ type="button"
145
+ class="rs-time-columns__item"
146
+ :class="{ 'rs-time-columns__item--active': parts.minute === option.value }"
147
+ data-unit="minute"
148
+ :data-value="option.value"
149
+ :disabled="isDisabled('minute', option.value)"
150
+ @click="selectUnit('minute', option.value)"
151
+ >
152
+ {{ option.label }}
153
+ </button>
154
+ </div>
144
155
  </div>
145
156
  </div>
146
157
 
147
158
  <div v-if="second" class="rs-time-columns__column">
148
159
  <span class="rs-time-columns__label">{{ t('timePicker.second') }}</span>
149
- <div ref="secondListRef" class="rs-time-columns__list">
150
- <button
151
- v-for="option in secondOptions"
152
- :key="option"
153
- type="button"
154
- class="rs-time-columns__item"
155
- :class="{ 'rs-time-columns__item--active': parts.second === option }"
156
- data-unit="second"
157
- :data-value="option"
158
- :disabled="disabled || isDisabled('second', option)"
159
- @click="selectUnit('second', option)"
160
- >
161
- {{ String(option).padStart(2, '0') }}
162
- </button>
160
+ <div class="rs-time-columns__list-shell">
161
+ <div ref="secondListRef" class="rs-time-columns__list">
162
+ <button
163
+ v-for="option in secondOptions"
164
+ :key="option.value"
165
+ type="button"
166
+ class="rs-time-columns__item"
167
+ :class="{ 'rs-time-columns__item--active': parts.second === option.value }"
168
+ data-unit="second"
169
+ :data-value="option.value"
170
+ :disabled="isDisabled('second', option.value)"
171
+ @click="selectUnit('second', option.value)"
172
+ >
173
+ {{ option.label }}
174
+ </button>
175
+ </div>
163
176
  </div>
164
177
  </div>
165
178
  </div>
@@ -170,10 +183,33 @@ defineExpose({
170
183
  display: grid;
171
184
  grid-template-columns: repeat(2, minmax(0, 1fr));
172
185
  gap: var(--rs-space-sm);
186
+ --rs-time-columns-list-max-height: 10rem;
187
+ --rs-time-columns-item-padding-y: 0.375rem;
188
+ --rs-time-columns-item-font-size: var(--rs-font-size-sm);
189
+ --rs-time-columns-label-font-size: 0.6875rem;
173
190
  }
174
191
  .rs-time-columns--seconds {
175
192
  grid-template-columns: repeat(3, minmax(0, 1fr));
176
193
  }
194
+ .rs-time-columns--ssm {
195
+ --rs-time-columns-list-max-height: 7.5rem;
196
+ --rs-time-columns-item-padding-y: 0.2rem;
197
+ --rs-time-columns-item-font-size: var(--rs-font-size-xs);
198
+ --rs-time-columns-label-font-size: 0.625rem;
199
+ gap: var(--rs-space-xs);
200
+ }
201
+ .rs-time-columns--sm {
202
+ --rs-time-columns-list-max-height: 8.5rem;
203
+ --rs-time-columns-item-padding-y: 0.28rem;
204
+ --rs-time-columns-item-font-size: var(--rs-font-size-xs);
205
+ --rs-time-columns-label-font-size: 0.625rem;
206
+ }
207
+ .rs-time-columns--lg {
208
+ --rs-time-columns-list-max-height: 12rem;
209
+ --rs-time-columns-item-padding-y: 0.5rem;
210
+ --rs-time-columns-item-font-size: var(--rs-font-size-base);
211
+ --rs-time-columns-label-font-size: var(--rs-font-size-xs);
212
+ }
177
213
  .rs-time-columns__column {
178
214
  display: flex;
179
215
  min-width: 0;
@@ -182,19 +218,41 @@ defineExpose({
182
218
  }
183
219
  .rs-time-columns__label {
184
220
  text-align: center;
185
- font-size: 0.6875rem;
221
+ font-size: var(--rs-time-columns-label-font-size);
186
222
  font-weight: 500;
187
223
  letter-spacing: 0.02em;
188
224
  color: var(--rs-muted);
189
225
  }
190
- .rs-time-columns__list {
191
- max-height: 10rem;
192
- overflow-y: auto;
193
- padding: 0.25rem;
226
+ .rs-time-columns__list-shell {
227
+ overflow: hidden;
194
228
  border: 1px solid var(--rs-border-subtle);
195
229
  border-radius: var(--rs-radius-sm);
196
230
  background: color-mix(in srgb, var(--rs-surface) 72%, transparent);
197
231
  }
232
+ .rs-time-columns__list {
233
+ position: relative;
234
+ max-height: var(--rs-time-columns-list-max-height);
235
+ overflow-y: auto;
236
+ padding: 0.25rem;
237
+ scrollbar-width: thin;
238
+ scrollbar-color: color-mix(in srgb, var(--rs-muted) 42%, transparent) transparent;
239
+ }
240
+ /* 去掉 Windows 原生滚动条上下三角,避免盖住圆角描边 */
241
+ .rs-time-columns__list::-webkit-scrollbar {
242
+ width: 6px;
243
+ }
244
+ .rs-time-columns__list::-webkit-scrollbar-button {
245
+ display: none;
246
+ width: 0;
247
+ height: 0;
248
+ }
249
+ .rs-time-columns__list::-webkit-scrollbar-thumb {
250
+ border-radius: 999px;
251
+ background: color-mix(in srgb, var(--rs-muted) 42%, transparent);
252
+ }
253
+ .rs-time-columns__list::-webkit-scrollbar-track {
254
+ background: transparent;
255
+ }
198
256
  .rs-time-columns__item {
199
257
  display: flex;
200
258
  width: 100%;
@@ -202,10 +260,11 @@ defineExpose({
202
260
  justify-content: center;
203
261
  border: 0;
204
262
  border-radius: var(--rs-radius-sm);
205
- padding: 0.375rem 0;
263
+ padding: var(--rs-time-columns-item-padding-y) 0;
206
264
  background: transparent;
207
265
  color: var(--rs-text);
208
- font-size: var(--rs-font-size-sm);
266
+ font-size: var(--rs-time-columns-item-font-size);
267
+ line-height: var(--rs-line-height-tight);
209
268
  cursor: pointer;
210
269
  transition: background var(--rs-transition-fast), color var(--rs-transition-fast);
211
270
  }