niuma-ui 1.0.2 → 1.1.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 (85) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/package.json +1 -1
  3. package/src/__tests__/RsDatePicker.spec.ts +21 -0
  4. package/src/__tests__/RsDialog.spec.ts +61 -0
  5. package/src/__tests__/RsForm.spec.ts +5 -5
  6. package/src/__tests__/RsFormItem.spec.ts +478 -0
  7. package/src/__tests__/RsFormList.spec.ts +109 -0
  8. package/src/__tests__/RsInput.spec.ts +93 -0
  9. package/src/__tests__/RsInputNumber.spec.ts +33 -0
  10. package/src/__tests__/RsSelect.spec.ts +423 -1
  11. package/src/__tests__/RsSwitch.spec.ts +30 -0
  12. package/src/__tests__/RsTimePicker.spec.ts +9 -0
  13. package/src/__tests__/RsTooltip.spec.ts +43 -0
  14. package/src/__tests__/css-token.spec.ts +51 -0
  15. package/src/__tests__/date-picker-utils.spec.ts +49 -0
  16. package/src/__tests__/dialog-utils.spec.ts +43 -0
  17. package/src/__tests__/form-path.spec.ts +46 -0
  18. package/src/__tests__/form-rules.spec.ts +63 -0
  19. package/src/__tests__/theme.spec.ts +2 -0
  20. package/src/components/RsAlert.vue +1 -1
  21. package/src/components/RsAvatar.vue +1 -1
  22. package/src/components/RsBadge.vue +1 -1
  23. package/src/components/RsBreadcrumb.vue +1 -1
  24. package/src/components/RsButton.vue +4 -4
  25. package/src/components/RsCalendarGrid.vue +3 -3
  26. package/src/components/RsCard.vue +4 -4
  27. package/src/components/RsCodeBlock.vue +4 -4
  28. package/src/components/RsCodeEditor.vue +8 -8
  29. package/src/components/RsConfirmDialog.vue +2 -2
  30. package/src/components/RsContextMenu.vue +6 -4
  31. package/src/components/RsDatePicker.vue +75 -31
  32. package/src/components/RsDateTimePicker.vue +9 -1
  33. package/src/components/RsDescriptions.vue +2 -2
  34. package/src/components/RsDialog.vue +26 -16
  35. package/src/components/RsDrawer.vue +1 -1
  36. package/src/components/RsDropdown.vue +1 -1
  37. package/src/components/RsDynamicTags.vue +18 -4
  38. package/src/components/RsEmpty.vue +1 -1
  39. package/src/components/RsForm.vue +68 -1
  40. package/src/components/RsFormItem.vue +423 -0
  41. package/src/components/RsFormItemControl.vue +21 -0
  42. package/src/components/RsFormList.vue +117 -0
  43. package/src/components/RsInput.vue +411 -18
  44. package/src/components/RsInputNumber.vue +13 -4
  45. package/src/components/RsLink.vue +1 -1
  46. package/src/components/RsMarkdown.vue +5 -5
  47. package/src/components/RsMenu.vue +2 -2
  48. package/src/components/RsMonacoEditor.vue +9 -3
  49. package/src/components/RsPagination.vue +1 -1
  50. package/src/components/RsProseEditor.vue +3 -3
  51. package/src/components/RsSelect.vue +332 -611
  52. package/src/components/RsSidebarGroup.vue +1 -1
  53. package/src/components/RsSplitPane.vue +22 -1
  54. package/src/components/RsStatCard.vue +2 -2
  55. package/src/components/RsSteps.vue +2 -2
  56. package/src/components/RsSwitch.vue +20 -7
  57. package/src/components/RsTabs.vue +9 -9
  58. package/src/components/RsTag.vue +1 -1
  59. package/src/components/RsTerminal.vue +58 -19
  60. package/src/components/RsTimePicker.vue +28 -4
  61. package/src/components/RsTimePickerColumns.vue +4 -4
  62. package/src/components/RsToaster.vue +7 -7
  63. package/src/components/RsTooltip.vue +10 -1
  64. package/src/components/RsTooltipProvider.vue +11 -1
  65. package/src/components/RsTree.vue +6 -6
  66. package/src/components/RsUpload.vue +2 -2
  67. package/src/components/date-picker-utils.ts +113 -2
  68. package/src/components/dialog-utils.ts +37 -2
  69. package/src/components/form-item-control.ts +55 -0
  70. package/src/components/form-path.ts +92 -0
  71. package/src/components/form-rules.ts +64 -10
  72. package/src/components/form-utils.ts +94 -2
  73. package/src/components/select-utils.ts +272 -12
  74. package/src/components/table/RsTableHeaderFilter.vue +1 -1
  75. package/src/components/table/rs-table.css +1 -1
  76. package/src/components/use-rs-select.ts +436 -0
  77. package/src/composables/useRsConfig.ts +10 -3
  78. package/src/composables/useRsI18n.ts +16 -5
  79. package/src/index.ts +69 -1
  80. package/src/locale/interpolate.ts +32 -0
  81. package/src/locale/messages.ts +10 -0
  82. package/src/styles.css +68 -12
  83. package/src/theme/brand.example.css +4 -1
  84. package/src/theme/css-token.ts +107 -0
  85. package/src/theme/types.ts +1 -0
@@ -7,6 +7,7 @@ export const zhCN: RsLocaleMessages = {
7
7
  'select.loading': '加载中…',
8
8
  'select.clear': '清空',
9
9
  'select.createOption': '使用 “{query}”',
10
+ 'select.maxTagPlaceholder': '+{count}',
10
11
  'dropdown.placeholder': '请选择',
11
12
  'breadcrumb.label': '面包屑',
12
13
  'breadcrumb.separator': '分隔符',
@@ -14,6 +15,10 @@ export const zhCN: RsLocaleMessages = {
14
15
  'menu.expandSubmenu': '展开子菜单',
15
16
  'menu.collapseSubmenu': '收起子菜单',
16
17
  'input.required': '此项为必填',
18
+ 'form.field': '此项',
19
+ 'form.validate.required': '请填写{label}',
20
+ 'form.list.add': '添加',
21
+ 'form.list.remove': '删除',
17
22
  'input.formatInvalid': '格式不正确',
18
23
  'input.rule.email': '请输入有效的邮箱地址',
19
24
  'input.rule.number': '请输入有效数字',
@@ -200,6 +205,7 @@ export const enUS: RsLocaleMessages = {
200
205
  'select.loading': 'Loading…',
201
206
  'select.clear': 'Clear',
202
207
  'select.createOption': 'Use “{query}”',
208
+ 'select.maxTagPlaceholder': '+{count}',
203
209
  'dropdown.placeholder': 'Select',
204
210
  'breadcrumb.label': 'Breadcrumb',
205
211
  'breadcrumb.separator': 'Separator',
@@ -207,6 +213,10 @@ export const enUS: RsLocaleMessages = {
207
213
  'menu.expandSubmenu': 'Expand submenu',
208
214
  'menu.collapseSubmenu': 'Collapse submenu',
209
215
  'input.required': 'This field is required',
216
+ 'form.field': 'This field',
217
+ 'form.validate.required': 'Please enter {label}',
218
+ 'form.list.add': 'Add',
219
+ 'form.list.remove': 'Remove',
210
220
  'input.formatInvalid': 'Invalid format',
211
221
  'input.rule.email': 'Enter a valid email address',
212
222
  'input.rule.number': 'Enter a valid number',
package/src/styles.css CHANGED
@@ -45,8 +45,31 @@
45
45
  'Microsoft YaHei',
46
46
  sans-serif;
47
47
  --rs-font-mono: ui-monospace, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
48
+ --rs-font-serif:
49
+ 'Noto Serif SC',
50
+ 'Source Han Serif SC',
51
+ 'Songti SC',
52
+ Georgia,
53
+ 'Times New Roman',
54
+ serif;
48
55
  --rs-font-rounded: ui-rounded, 'SF Pro Rounded', 'Segoe UI', system-ui, sans-serif;
49
56
 
57
+ /* 代码编辑器(CodeMirror / Monaco):JS API 从这些 token 读取 */
58
+ --rs-code-font-family: var(--rs-font-mono);
59
+ --rs-code-font-size: var(--rs-font-size-sm);
60
+ --rs-code-line-height: var(--rs-line-height-relaxed);
61
+
62
+ /* 富文本阅读面(RsProseEditor) */
63
+ --rs-prose-font-family: var(--rs-font-serif);
64
+ --rs-prose-font-size: var(--rs-font-size-base);
65
+ --rs-prose-line-height: var(--rs-line-height-relaxed);
66
+
67
+ /* 终端排版(xterm 构造时读取;宿主可单独覆盖) */
68
+ --rs-terminal-font-family: var(--rs-font-mono);
69
+ --rs-terminal-font-size: var(--rs-font-size-sm);
70
+ --rs-terminal-font-weight: var(--rs-font-weight-regular);
71
+ --rs-terminal-font-weight-bold: var(--rs-font-weight-medium);
72
+
50
73
  --rs-control-height-ssm: 1.25rem;
51
74
  --rs-control-height-sm: 1.5rem;
52
75
  --rs-control-height-md: 2rem;
@@ -115,7 +138,7 @@
115
138
  --rs-menu-item-height: var(--rs-control-height-md);
116
139
  --rs-menu-item-font-size: var(--rs-font-size-sm);
117
140
  --rs-menu-item-radius: var(--rs-radius-sm);
118
- --rs-menu-item-active-weight: 500;
141
+ --rs-menu-item-active-weight: var(--rs-font-weight-medium);
119
142
 
120
143
  /*
121
144
  * 表单标签(RsLabel / .rs-field__label 共用)
@@ -194,7 +217,7 @@
194
217
  --rs-table-body-bg: #1f1f1f;
195
218
  --rs-table-header-bg: #1f1f1f;
196
219
  --rs-table-header-fg: #f3f3f3;
197
- --rs-table-header-weight: 600;
220
+ --rs-table-header-weight: var(--rs-font-weight-semibold);
198
221
  --rs-table-header-size: var(--rs-table-cell-size, var(--rs-font-size-xs));
199
222
  --rs-table-cell-size: var(--rs-font-size-xs);
200
223
  --rs-table-cell-weight: 400;
@@ -320,7 +343,7 @@
320
343
  --rs-drawer-overlay-blur: var(--rs-dialog-overlay-blur);
321
344
 
322
345
  /* ── Tag / DynamicTags(可被业务覆盖)── */
323
- --rs-tag-font-weight: var(--rs-font-weight-medium);
346
+ --rs-tag-font-weight: var(--rs-font-weight-regular);
324
347
  --rs-tag-border-alpha: 40%;
325
348
  --rs-dynamic-tags-bg: var(--rs-input-bg);
326
349
  --rs-dynamic-tags-border: var(--rs-input-border);
@@ -436,7 +459,7 @@
436
459
  --rs-table-body-bg: #ffffff;
437
460
  --rs-table-header-bg: #f8f8f8;
438
461
  --rs-table-header-fg: #1a1a1a;
439
- --rs-table-header-weight: 600;
462
+ --rs-table-header-weight: var(--rs-font-weight-semibold);
440
463
  --rs-table-header-size: var(--rs-table-cell-size, var(--rs-font-size-xs));
441
464
  --rs-table-cell-size: var(--rs-font-size-xs);
442
465
  --rs-table-cell-weight: 400;
@@ -546,7 +569,7 @@
546
569
  --rs-drawer-overlay-blur: var(--rs-dialog-overlay-blur);
547
570
 
548
571
  /* ── Tag / DynamicTags(可被业务覆盖)── */
549
- --rs-tag-font-weight: var(--rs-font-weight-medium);
572
+ --rs-tag-font-weight: var(--rs-font-weight-regular);
550
573
  --rs-tag-border-alpha: 40%;
551
574
  --rs-dynamic-tags-bg: var(--rs-input-bg);
552
575
  --rs-dynamic-tags-border: var(--rs-input-border);
@@ -854,9 +877,29 @@ code {
854
877
  color: var(--rs-placeholder);
855
878
  }
856
879
 
880
+ .rs-select__panel-header,
881
+ .rs-select__panel-footer {
882
+ flex-shrink: 0;
883
+ padding: var(--rs-space-xs) var(--rs-space-sm);
884
+ font-size: var(--rs-font-size-xs);
885
+ line-height: var(--rs-line-height-normal);
886
+ color: var(--rs-muted);
887
+ }
888
+
889
+ .rs-select__panel-header {
890
+ border-bottom: 1px solid var(--rs-border-subtle);
891
+ }
892
+
893
+ .rs-select__panel-footer {
894
+ border-top: 1px solid var(--rs-border-subtle);
895
+ }
896
+
857
897
  .rs-select__viewport {
858
898
  overflow-y: auto;
859
- max-height: inherit;
899
+ max-height: min(
900
+ var(--rs-select-list-height, 256px),
901
+ var(--reka-combobox-content-available-height, 256px)
902
+ );
860
903
  padding: 0 var(--rs-space-xs) var(--rs-space-xs);
861
904
  }
862
905
 
@@ -883,7 +926,7 @@ code {
883
926
  display: block;
884
927
  padding: var(--rs-space-xs) var(--rs-space-sm) var(--rs-space-xs);
885
928
  font-size: var(--rs-font-size-xs);
886
- font-weight: 600;
929
+ font-weight: var(--rs-font-weight-semibold);
887
930
  color: var(--rs-muted);
888
931
  letter-spacing: 0.04em;
889
932
  line-height: var(--rs-line-height-tight);
@@ -970,10 +1013,10 @@ code {
970
1013
  .rs-field--label-left {
971
1014
  display: grid;
972
1015
  /*
973
- * 左侧标签:配置宽为下限,内容更长时按 max-content 撑开,避免中文标签换行。
974
- * 未设置 --rs-field-label-width 时退回 max-content。
1016
+ * 左侧标签:有 --rs-field-label-width 时固定列宽,保证栅格内控件起点对齐。
1017
+ * 未设置时退回 max-content(单字段表单仍可随文案伸缩)。
975
1018
  */
976
- grid-template-columns: minmax(var(--rs-field-label-width, max-content), max-content) minmax(0, 1fr);
1019
+ grid-template-columns: var(--rs-field-label-width, max-content) minmax(0, 1fr);
977
1020
  align-items: start;
978
1021
  column-gap: var(--rs-space-md);
979
1022
  }
@@ -990,10 +1033,14 @@ code {
990
1033
  line-height: var(--rs-label-line-height);
991
1034
  }
992
1035
 
993
- /* 左侧标签默认单行(与常见表单库一致);垂直布局不受影响 */
1036
+ /* 左侧标签默认单行;固定列宽时过长省略,避免撑破栅格对齐 */
994
1037
  .rs-field--label-left > .rs-field__label,
995
1038
  .rs-field--label-left > .rs-label {
1039
+ min-width: 0;
1040
+ max-width: 100%;
996
1041
  white-space: nowrap;
1042
+ overflow: hidden;
1043
+ text-overflow: ellipsis;
997
1044
  }
998
1045
 
999
1046
  .rs-field__required {
@@ -1009,6 +1056,15 @@ code {
1009
1056
  grid-column: 2;
1010
1057
  }
1011
1058
 
1059
+ /*
1060
+ * Form.Item 会把 aria-invalid 注入子控件。Input 用 inheritAttrs:false 落到输入框;
1061
+ * 若落到 .rs-field / .rs-select 外壳,UA 会再画一圈轮廓,和 trigger 边框叠成双框。
1062
+ */
1063
+ .rs-field[aria-invalid='true'],
1064
+ .rs-select[aria-invalid='true'] {
1065
+ outline: none;
1066
+ }
1067
+
1012
1068
  .rs-field__control {
1013
1069
  width: 100%;
1014
1070
  min-height: var(--rs-control-height-md);
@@ -1104,7 +1160,7 @@ code {
1104
1160
 
1105
1161
  .rs-toast__title {
1106
1162
  font-size: var(--rs-font-size-sm);
1107
- font-weight: 600;
1163
+ font-weight: var(--rs-font-weight-semibold);
1108
1164
  }
1109
1165
 
1110
1166
  .rs-toast__description {
@@ -13,7 +13,10 @@
13
13
  * --rs-text-disabled / --rs-text-link(底层亦兼容 --rs-text / --rs-muted)
14
14
  * - 按钮次要态:--rs-btn-outline-border / --rs-btn-secondary-bg /
15
15
  * --rs-btn-secondary-bg-hover / --rs-btn-secondary-bg-active
16
- * - 排版:--rs-font-sans / --rs-font-mono / --rs-font-size-* / --rs-font-weight-*
16
+ * - 排版:--rs-font-sans / --rs-font-mono / --rs-font-serif / --rs-font-size-* / --rs-font-weight-*
17
+ * - 代码编辑器:--rs-code-font-family / --rs-code-font-size / --rs-code-line-height
18
+ * - 富文本:--rs-prose-font-family / --rs-prose-font-size / --rs-prose-line-height
19
+ * - 终端排版:--rs-terminal-font-family / --rs-terminal-font-size / --rs-terminal-font-weight
17
20
  * - 菜单:--rs-menu-item-active-fg|bg|weight、--rs-menu-item-fg、--rs-menu-item-bg-hover
18
21
  * - 表单标签:--rs-label-font-size|font-weight|line-height|color(RsLabel / .rs-field__label)
19
22
  * - 表格专用:--rs-table-*(色值参考 VS Code Dark/Light Modern)
@@ -0,0 +1,107 @@
1
+ const FALLBACK_ROOT_PX = 16
2
+
3
+ function unwrapCssVar(raw: string, el: HTMLElement, depth = 0): string {
4
+ if (!raw || depth > 8) {
5
+ return raw
6
+ }
7
+ const match = /^var\(\s*(--[\w-]+)\s*(?:,\s*((?:.|\s)+))?\)$/.exec(raw)
8
+ if (!match) {
9
+ return raw
10
+ }
11
+ const inner = getComputedStyle(el).getPropertyValue(match[1]).trim()
12
+ if (inner) {
13
+ return unwrapCssVar(inner, el, depth + 1)
14
+ }
15
+ const fallback = (match[2] || '').trim()
16
+ return fallback ? unwrapCssVar(fallback, el, depth + 1) : ''
17
+ }
18
+
19
+ /**
20
+ * 读取已计算的 CSS 自定义属性(不含首尾空白)。
21
+ * 会展开嵌套 `var(--token)`,便于 jsdom 与宿主覆盖。
22
+ * SSR / 无 DOM 时返回空字符串。
23
+ */
24
+ export function readCssVar(name: string, el?: HTMLElement | null): string {
25
+ if (typeof document === 'undefined' || typeof getComputedStyle === 'undefined') {
26
+ return ''
27
+ }
28
+ const target = el ?? document.documentElement
29
+ return unwrapCssVar(getComputedStyle(target).getPropertyValue(name).trim(), target)
30
+ }
31
+
32
+ /** 根元素字号(px),供 rem 换算;失败时按 16。 */
33
+ export function readRootFontSizePx(): number {
34
+ if (typeof document === 'undefined' || typeof getComputedStyle === 'undefined') {
35
+ return FALLBACK_ROOT_PX
36
+ }
37
+ const n = Number.parseFloat(getComputedStyle(document.documentElement).fontSize)
38
+ return Number.isFinite(n) && n > 0 ? n : FALLBACK_ROOT_PX
39
+ }
40
+
41
+ /**
42
+ * 将 `12px` / `0.875rem` / 无单位数字解析为 px。
43
+ * 嵌套 `var()` 须先经 getComputedStyle 展开。
44
+ */
45
+ export function parseCssLengthToPx(raw: string, rootPx = readRootFontSizePx()): number | null {
46
+ const value = raw.trim()
47
+ if (!value) {
48
+ return null
49
+ }
50
+ const n = Number.parseFloat(value)
51
+ if (!Number.isFinite(n)) {
52
+ return null
53
+ }
54
+ if (value.endsWith('px') || /^-?[\d.]+$/.test(value)) {
55
+ return n
56
+ }
57
+ if (value.endsWith('rem') || value.endsWith('em')) {
58
+ return n * rootPx
59
+ }
60
+ return null
61
+ }
62
+
63
+ /** 读取长度类 token 并转为 px,供 Monaco / xterm 等只接受数字的 API。 */
64
+ export function readCssLengthPx(
65
+ name: string,
66
+ fallback: number,
67
+ el?: HTMLElement | null,
68
+ ): number {
69
+ const parsed = parseCssLengthToPx(readCssVar(name, el))
70
+ return parsed == null ? fallback : parsed
71
+ }
72
+
73
+ const CODE_FONT_FALLBACK = 'ui-monospace, SF Mono, Consolas, Liberation Mono, Menlo, monospace'
74
+
75
+ /** 代码编辑器字体族:`--rs-code-font-family` → `--rs-font-mono`。 */
76
+ export function readCodeFontFamily(el?: HTMLElement | null): string {
77
+ return readCssVar('--rs-code-font-family', el) || readCssVar('--rs-font-mono', el) || CODE_FONT_FALLBACK
78
+ }
79
+
80
+ /** 代码编辑器字号(px):`--rs-code-font-size`,缺省按 sm=14。 */
81
+ export function readCodeFontSizePx(el?: HTMLElement | null): number {
82
+ return readCssLengthPx('--rs-code-font-size', 14, el)
83
+ }
84
+
85
+ /** 终端字体族:`--rs-terminal-font-family` → `--rs-font-mono`。 */
86
+ export function readTerminalFontFamily(el?: HTMLElement | null): string {
87
+ return readCssVar('--rs-terminal-font-family', el) || readCssVar('--rs-font-mono', el) || CODE_FONT_FALLBACK
88
+ }
89
+
90
+ /** 终端字号(px):`--rs-terminal-font-size`,缺省按 sm=14。 */
91
+ export function readTerminalFontSizePx(el?: HTMLElement | null): number {
92
+ return readCssLengthPx('--rs-terminal-font-size', 14, el)
93
+ }
94
+
95
+ /** 终端字重:`--rs-terminal-font-weight`,缺省 regular。 */
96
+ export function readTerminalFontWeight(el?: HTMLElement | null): string {
97
+ return readCssVar('--rs-terminal-font-weight', el) || readCssVar('--rs-font-weight-regular', el) || '400'
98
+ }
99
+
100
+ /** 终端粗体字重:`--rs-terminal-font-weight-bold`,缺省 medium。 */
101
+ export function readTerminalFontWeightBold(el?: HTMLElement | null): string {
102
+ return (
103
+ readCssVar('--rs-terminal-font-weight-bold', el) ||
104
+ readCssVar('--rs-font-weight-medium', el) ||
105
+ '500'
106
+ )
107
+ }
@@ -54,6 +54,7 @@ export const RS_RADIUS_CSS: Record<RsRadius, string> = {
54
54
  * - 3xl:展示级标题
55
55
  *
56
56
  * 对应 `--rs-font-size-*`;业务侧优先用 token,勿硬编码 px。
57
+ * 字族走 `--rs-font-sans` / `--rs-font-mono` / `--rs-font-serif`,组件勿硬编码 system 栈。
57
58
  */
58
59
  export type RsFontSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl'
59
60