one-design-next 0.0.61 → 0.0.63

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.
@@ -0,0 +1,49 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
+ import { PREVIEW_CSV_MAX_ROWS } from "./parseCsv";
3
+ /** 预览用只读表格;列数按首行对齐,短行补空、长行多出的列仍展示。 */
4
+ export function CsvPreviewTable(_ref) {
5
+ var data = _ref.data;
6
+ var headers = data.headers,
7
+ rows = data.rows,
8
+ truncated = data.truncated,
9
+ totalDataRows = data.totalDataRows;
10
+ var colCount = Math.max.apply(Math, [headers.length].concat(_toConsumableArray(rows.map(function (r) {
11
+ return r.length;
12
+ })), [1]));
13
+ var displayHeaders = headers.length > 0 ? Array.from({
14
+ length: colCount
15
+ }, function (_, i) {
16
+ var _headers$i;
17
+ return (_headers$i = headers[i]) !== null && _headers$i !== void 0 ? _headers$i : "\u5217 ".concat(i + 1);
18
+ }) : Array.from({
19
+ length: colCount
20
+ }, function (_, i) {
21
+ return "\u5217 ".concat(i + 1);
22
+ });
23
+ return /*#__PURE__*/React.createElement("div", {
24
+ "data-odn-preview-panel-csv": true
25
+ }, /*#__PURE__*/React.createElement("div", {
26
+ "data-odn-preview-panel-csv-scroll": true
27
+ }, /*#__PURE__*/React.createElement("table", {
28
+ "data-odn-preview-panel-csv-table": true
29
+ }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, displayHeaders.map(function (h, i) {
30
+ return /*#__PURE__*/React.createElement("th", {
31
+ key: "h-".concat(i),
32
+ scope: "col"
33
+ }, h || ' ');
34
+ }))), /*#__PURE__*/React.createElement("tbody", null, rows.map(function (row, ri) {
35
+ return /*#__PURE__*/React.createElement("tr", {
36
+ key: "r-".concat(ri)
37
+ }, Array.from({
38
+ length: colCount
39
+ }, function (_, ci) {
40
+ var _row$ci;
41
+ return /*#__PURE__*/React.createElement("td", {
42
+ key: "c-".concat(ri, "-").concat(ci)
43
+ }, (_row$ci = row[ci]) !== null && _row$ci !== void 0 ? _row$ci : '');
44
+ }));
45
+ })))), truncated && /*#__PURE__*/React.createElement("div", {
46
+ "data-odn-preview-panel-csv-truncated": true,
47
+ role: "status"
48
+ }, "\u4EC5\u9884\u89C8\u524D ".concat(PREVIEW_CSV_MAX_ROWS, " \u884C\uFF08\u5171 ").concat(totalDataRows, " \u884C\uFF09")));
49
+ }
@@ -1,12 +1,15 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
1
2
  import _extends from "@babel/runtime/helpers/esm/extends";
2
3
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
3
4
  var _excluded = ["tooltip"];
4
- import { useRef } from 'react';
5
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
6
  import Button from "../button";
6
7
  import Icon from "../icon";
7
8
  import Tooltip from "../tooltip";
8
9
  import StreamText from "../stream-text";
9
10
  import { OdnOverlayVerticalScrollbar, useOverlayScrollbarReveal } from "../overlay-vertical-scrollbar";
11
+ import { CsvPreviewTable } from "./CsvPreviewTable";
12
+ import { isCsvTab, parseCsv } from "./parseCsv";
10
13
  import "./style";
11
14
  /** 内部辅助:有 tooltip 文案时包裹 Tooltip,否则仅渲染按钮 */
12
15
  function ActionButton(_ref) {
@@ -48,14 +51,84 @@ export function PreviewPanel(_ref2) {
48
51
  var isOpen = open !== null && open !== void 0 ? open : tabs.length > 0;
49
52
  var isEmpty = tabs.length === 0;
50
53
  var previewScrollRef = useRef(null);
54
+ var tabsScrollRef = useRef(null);
55
+ var _useState = useState({
56
+ canLeft: false,
57
+ canRight: false
58
+ }),
59
+ _useState2 = _slicedToArray(_useState, 2),
60
+ tabScroll = _useState2[0],
61
+ setTabScroll = _useState2[1];
51
62
  var _useOverlayScrollbarR = useOverlayScrollbarReveal(),
52
63
  previewScrollbarReveal = _useOverlayScrollbarR.reveal,
53
64
  handlePreviewScroll = _useOverlayScrollbarR.onScroll;
65
+ var updateTabScrollState = function updateTabScrollState() {
66
+ var el = tabsScrollRef.current;
67
+ if (!el) {
68
+ setTabScroll({
69
+ canLeft: false,
70
+ canRight: false
71
+ });
72
+ return;
73
+ }
74
+ var max = el.scrollWidth - el.clientWidth;
75
+ var left = el.scrollLeft;
76
+ // 亚像素容差,避免 Win/高分屏来回抖
77
+ setTabScroll({
78
+ canLeft: left > 1,
79
+ canRight: max - left > 1
80
+ });
81
+ };
82
+ var scrollTabsBy = function scrollTabsBy(direction) {
83
+ var el = tabsScrollRef.current;
84
+ if (!el) return;
85
+ var step = Math.max(120, Math.floor(el.clientWidth * 0.7));
86
+ el.scrollBy({
87
+ left: direction * step,
88
+ behavior: 'smooth'
89
+ });
90
+ };
91
+ useLayoutEffect(function () {
92
+ updateTabScrollState();
93
+ }, [tabs, isOpen, activeTab === null || activeTab === void 0 ? void 0 : activeTab.id]);
94
+ useEffect(function () {
95
+ var el = tabsScrollRef.current;
96
+ if (!el) return;
97
+ var onScroll = function onScroll() {
98
+ return updateTabScrollState();
99
+ };
100
+ el.addEventListener('scroll', onScroll, {
101
+ passive: true
102
+ });
103
+ var ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
104
+ return updateTabScrollState();
105
+ }) : null;
106
+ ro === null || ro === void 0 || ro.observe(el);
107
+ return function () {
108
+ el.removeEventListener('scroll', onScroll);
109
+ ro === null || ro === void 0 || ro.disconnect();
110
+ };
111
+ }, [isOpen, tabs.length]);
112
+ useEffect(function () {
113
+ var root = tabsScrollRef.current;
114
+ if (!root || !activeTab) return;
115
+ var activeEl = root.querySelector('[data-odn-preview-panel-tab-active]');
116
+ activeEl === null || activeEl === void 0 || activeEl.scrollIntoView({
117
+ inline: 'nearest',
118
+ block: 'nearest',
119
+ behavior: 'smooth'
120
+ });
121
+ }, [activeTab === null || activeTab === void 0 ? void 0 : activeTab.id, tabs.length]);
122
+ var showTabScrollButtons = tabScroll.canLeft || tabScroll.canRight;
54
123
  var isMarkdownTab = function isMarkdownTab(tab) {
55
124
  var type = tab.type.toLowerCase();
56
125
  var name = tab.name.toLowerCase();
57
126
  return type === 'text/markdown' || type === 'text/x-markdown' || type === 'application/markdown' || type === 'application/x-markdown' || name.endsWith('.md') || name.endsWith('.markdown');
58
127
  };
128
+ var csvPreview = useMemo(function () {
129
+ if (!activeTab || !isCsvTab(activeTab.type, activeTab.name)) return null;
130
+ return parseCsv(activeTab.content);
131
+ }, [activeTab]);
59
132
  var handleOpenInBrowser = function handleOpenInBrowser() {
60
133
  if (!activeTab) return;
61
134
  if (onOpenInBrowser) {
@@ -79,6 +152,20 @@ export function PreviewPanel(_ref2) {
79
152
  }, isOpen && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
80
153
  "data-odn-preview-panel-header": true
81
154
  }, /*#__PURE__*/React.createElement("div", {
155
+ "data-odn-preview-panel-tabs-rail": true
156
+ }, showTabScrollButtons && /*#__PURE__*/React.createElement(Button, {
157
+ light: true,
158
+ size: "medium",
159
+ icon: "chevron-left",
160
+ disabled: !tabScroll.canLeft,
161
+ onClick: function onClick() {
162
+ return scrollTabsBy(-1);
163
+ },
164
+ "data-odn-preview-panel-tabs-prev": true,
165
+ title: "\u5411\u5DE6\u67E5\u770B\u6807\u7B7E",
166
+ "aria-label": "\u5411\u5DE6\u67E5\u770B\u6807\u7B7E"
167
+ }), /*#__PURE__*/React.createElement("div", {
168
+ ref: tabsScrollRef,
82
169
  "data-odn-preview-panel-tabs": true,
83
170
  role: "tablist",
84
171
  "aria-label": "\u9884\u89C8\u6807\u7B7E"
@@ -121,6 +208,17 @@ export function PreviewPanel(_ref2) {
121
208
  name: "x",
122
209
  size: 12
123
210
  })));
211
+ })), showTabScrollButtons && /*#__PURE__*/React.createElement(Button, {
212
+ light: true,
213
+ size: "medium",
214
+ icon: "chevron-right",
215
+ disabled: !tabScroll.canRight,
216
+ onClick: function onClick() {
217
+ return scrollTabsBy(1);
218
+ },
219
+ "data-odn-preview-panel-tabs-next": true,
220
+ title: "\u5411\u53F3\u67E5\u770B\u6807\u7B7E",
221
+ "aria-label": "\u5411\u53F3\u67E5\u770B\u6807\u7B7E"
124
222
  })), /*#__PURE__*/React.createElement("div", {
125
223
  "data-odn-preview-panel-actions": true
126
224
  }, /*#__PURE__*/React.createElement(ActionButton, {
@@ -182,7 +280,9 @@ export function PreviewPanel(_ref2) {
182
280
  "data-odn-preview-panel-markdown": true
183
281
  }, /*#__PURE__*/React.createElement(StreamText, {
184
282
  content: activeTab.content
185
- })) : /*#__PURE__*/React.createElement("pre", {
283
+ })) : csvPreview ? /*#__PURE__*/React.createElement(CsvPreviewTable, {
284
+ data: csvPreview
285
+ }) : /*#__PURE__*/React.createElement("pre", {
186
286
  "data-odn-preview-panel-pre": true
187
287
  }, activeTab.content)), /*#__PURE__*/React.createElement(OdnOverlayVerticalScrollbar, {
188
288
  containerRef: previewScrollRef,
@@ -0,0 +1,16 @@
1
+ /** PreviewPanel 内 CSV 预览用的轻量解析,不引入 papaparse 等依赖。 */
2
+ export declare const PREVIEW_CSV_MAX_ROWS = 500;
3
+ export interface ParsedCsv {
4
+ /** 表头;无表头时为空数组,此时 rows 含全部行 */
5
+ headers: string[];
6
+ rows: string[][];
7
+ /** 是否因超过 PREVIEW_CSV_MAX_ROWS 被截断(不含表头) */
8
+ truncated: boolean;
9
+ totalDataRows: number;
10
+ }
11
+ /**
12
+ * 按 RFC 4180 常见子集解析:逗号分隔、双引号转义、CRLF/LF。
13
+ * 空输入返回 null(由调用方回退到纯文本)。
14
+ */
15
+ export declare function parseCsv(text: string, maxRows?: number): ParsedCsv | null;
16
+ export declare function isCsvTab(type: string, name: string): boolean;
@@ -0,0 +1,103 @@
1
+ import _toArray from "@babel/runtime/helpers/esm/toArray";
2
+ /** PreviewPanel 内 CSV 预览用的轻量解析,不引入 papaparse 等依赖。 */
3
+
4
+ export var PREVIEW_CSV_MAX_ROWS = 500;
5
+ function parseCsvMatrix(text) {
6
+ var rows = [];
7
+ var row = [];
8
+ var field = '';
9
+ var inQuotes = false;
10
+ for (var i = 0; i < text.length; i += 1) {
11
+ var ch = text[i];
12
+ var next = text[i + 1];
13
+ if (inQuotes) {
14
+ if (ch === '"') {
15
+ if (next === '"') {
16
+ field += '"';
17
+ i += 1;
18
+ } else {
19
+ inQuotes = false;
20
+ }
21
+ } else {
22
+ field += ch;
23
+ }
24
+ continue;
25
+ }
26
+ if (ch === '"') {
27
+ inQuotes = true;
28
+ continue;
29
+ }
30
+ if (ch === ',') {
31
+ row.push(field);
32
+ field = '';
33
+ continue;
34
+ }
35
+ if (ch === '\n') {
36
+ row.push(field);
37
+ rows.push(row);
38
+ row = [];
39
+ field = '';
40
+ continue;
41
+ }
42
+ if (ch === '\r') {
43
+ if (next === '\n') continue;
44
+ row.push(field);
45
+ rows.push(row);
46
+ row = [];
47
+ field = '';
48
+ continue;
49
+ }
50
+ field += ch;
51
+ }
52
+
53
+ // 文件末尾无换行时收尾;全空则忽略
54
+ if (field.length > 0 || row.length > 0) {
55
+ row.push(field);
56
+ rows.push(row);
57
+ }
58
+
59
+ // 去掉末尾全空行(常见于编辑器保存)
60
+ while (rows.length > 0 && rows[rows.length - 1].every(function (c) {
61
+ return c === '';
62
+ })) {
63
+ rows.pop();
64
+ }
65
+ return rows;
66
+ }
67
+
68
+ /**
69
+ * 按 RFC 4180 常见子集解析:逗号分隔、双引号转义、CRLF/LF。
70
+ * 空输入返回 null(由调用方回退到纯文本)。
71
+ */
72
+ export function parseCsv(text) {
73
+ var maxRows = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : PREVIEW_CSV_MAX_ROWS;
74
+ var source = text.replace(/^\uFEFF/, '');
75
+ if (!source.trim()) return null;
76
+ var matrix = parseCsvMatrix(source);
77
+ if (matrix.length === 0) return null;
78
+ var _matrix = _toArray(matrix),
79
+ headerRow = _matrix[0],
80
+ body = _matrix.slice(1);
81
+ // 单行:当作无表头的数据行
82
+ if (body.length === 0) {
83
+ return {
84
+ headers: [],
85
+ rows: [headerRow],
86
+ truncated: false,
87
+ totalDataRows: 1
88
+ };
89
+ }
90
+ var totalDataRows = body.length;
91
+ var truncated = totalDataRows > maxRows;
92
+ return {
93
+ headers: headerRow,
94
+ rows: truncated ? body.slice(0, maxRows) : body,
95
+ truncated: truncated,
96
+ totalDataRows: totalDataRows
97
+ };
98
+ }
99
+ export function isCsvTab(type, name) {
100
+ var t = type.toLowerCase();
101
+ var n = name.toLowerCase();
102
+ return t === 'text/csv' || t === 'application/csv' || t === 'text/comma-separated-values' || n.endsWith('.csv');
103
+ }
@@ -32,6 +32,14 @@
32
32
  box-sizing: border-box;
33
33
  }
34
34
 
35
+ [data-odn-preview-panel-tabs-rail] {
36
+ flex: 1;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 2px;
40
+ min-width: 0;
41
+ }
42
+
35
43
  [data-odn-preview-panel-tabs] {
36
44
  flex: 1;
37
45
  display: flex;
@@ -40,12 +48,18 @@
40
48
  min-width: 0;
41
49
  overflow-x: auto;
42
50
  scrollbar-width: none;
51
+ /* 主要靠左右按钮翻页;保留 overflow 以兼容触控板,但隐藏滚动条避免「可滑却看不见」 */
43
52
  }
44
53
 
45
54
  [data-odn-preview-panel-tabs]::-webkit-scrollbar {
46
55
  display: none;
47
56
  }
48
57
 
58
+ [data-odn-preview-panel-tabs-prev],
59
+ [data-odn-preview-panel-tabs-next] {
60
+ flex-shrink: 0;
61
+ }
62
+
49
63
  [data-odn-preview-panel-tab] {
50
64
  display: flex;
51
65
  align-items: center;
@@ -99,14 +113,14 @@
99
113
  transition: opacity 0.15s, background-color 0.15s;
100
114
  }
101
115
 
102
- [data-odn-preview-panel-tab]:hover [data-odn-preview-panel-tab-close] {
103
- opacity: 1;
104
- }
105
-
106
116
  [data-odn-preview-panel-tab-close]:hover {
107
117
  background-color: var(--odn-color-black-3);
108
118
  }
109
119
 
120
+ [data-odn-preview-panel-tab]:hover [data-odn-preview-panel-tab-close] {
121
+ opacity: 1;
122
+ }
123
+
110
124
  /* header 右侧操作区:下载按钮 + 分割线 + 收起按钮。
111
125
  * 使用 flex 居中,分割线通过伪元素/span 实现 1px 浅色竖线;
112
126
  * 所有按钮改用 <Button light />,样式由 token 控制,这里只保证布局。
@@ -183,8 +197,7 @@
183
197
  position: absolute;
184
198
  inset: 0;
185
199
  box-sizing: border-box;
186
- overflow-x: hidden;
187
- overflow-y: auto;
200
+ overflow: hidden auto;
188
201
  scrollbar-width: none;
189
202
  -ms-overflow-style: none;
190
203
  }
@@ -215,11 +228,11 @@
215
228
  padding: 20px;
216
229
  font-size: var(--odn-font-size-text-mn);
217
230
  line-height: var(--odn-font-leading-text-mn);
218
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
231
+ font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
219
232
  color: var(--odn-color-black-10);
220
233
  background-color: var(--odn-color-solid-black-1);
221
234
  white-space: pre-wrap;
222
- word-break: break-word;
235
+ overflow-wrap: break-word;
223
236
  }
224
237
 
225
238
  [data-odn-preview-panel-markdown] {
@@ -231,4 +244,59 @@
231
244
 
232
245
  [data-odn-preview-panel-markdown] [data-odn-stream-text] {
233
246
  max-width: none;
247
+ }
248
+
249
+ [data-odn-preview-panel-csv] {
250
+ min-width: 0;
251
+ width: 100%;
252
+ box-sizing: border-box;
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 8px;
256
+ padding: 12px 16px 16px;
257
+ }
258
+
259
+ [data-odn-preview-panel-csv-scroll] {
260
+ min-width: 0;
261
+ overflow-x: auto;
262
+ border: 1px solid var(--odn-color-black-4);
263
+ border-radius: var(--odn-border-radius, 6px);
264
+ }
265
+
266
+ [data-odn-preview-panel-csv-table] {
267
+ width: max-content;
268
+ min-width: 100%;
269
+ border-collapse: collapse;
270
+ font-size: var(--odn-font-size-text-mn, 12px);
271
+ line-height: var(--odn-font-leading-text-mn, 1.5);
272
+ color: var(--odn-color-black-12);
273
+ }
274
+
275
+ [data-odn-preview-panel-csv-table] th,
276
+ [data-odn-preview-panel-csv-table] td {
277
+ padding: 8px 12px;
278
+ text-align: left;
279
+ white-space: nowrap;
280
+ max-width: 280px;
281
+ overflow: hidden;
282
+ text-overflow: ellipsis;
283
+ border-bottom: 1px solid var(--odn-color-black-3);
284
+ vertical-align: top;
285
+ }
286
+
287
+ [data-odn-preview-panel-csv-table] th {
288
+ background-color: var(--odn-color-solid-black-1, #f7f7f8);
289
+ color: var(--odn-color-black-10);
290
+ font-weight: 500;
291
+ border-bottom-color: var(--odn-color-black-4);
292
+ }
293
+
294
+ [data-odn-preview-panel-csv-table] tbody tr:last-child td {
295
+ border-bottom: none;
296
+ }
297
+
298
+ [data-odn-preview-panel-csv-truncated] {
299
+ flex-shrink: 0;
300
+ font-size: var(--odn-font-size-text-mn, 12px);
301
+ color: var(--odn-color-black-8);
234
302
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "one-design-next",
3
- "version": "0.0.61",
3
+ "version": "0.0.63",
4
4
  "description": "One Design Next from TAD@tencent.com",
5
5
  "packageManager": "pnpm@11.8.0",
6
6
  "module": "dist/index.js",
@@ -9,6 +9,7 @@
9
9
  "scripts": {
10
10
  "start": "npm run dev",
11
11
  "dev": "npm run build:variables && npm run build:benchmark && npm run build:feedback && dumi dev",
12
+ "dev:recover": "bash scripts/after-git-switch.sh && npm run dev",
12
13
  "build": "father build && npm run build:styles",
13
14
  "build:skills": "bash skills/build.sh",
14
15
  "build:watch": "father dev",
@@ -59,7 +60,7 @@
59
60
  "*.{md,json}": [
60
61
  "prettier --write --no-error-on-unmatched-pattern"
61
62
  ],
62
- "*.{css,scss}": [
63
+ "{components,test}/**/*.{css,scss}": [
63
64
  "stylelint --fix",
64
65
  "prettier --write"
65
66
  ],
@@ -114,7 +115,7 @@
114
115
  "dumi": "latest",
115
116
  "echarts": "^6.1.0",
116
117
  "echarts-for-react": "^3.0.6",
117
- "eslint": "^9.32.0",
118
+ "eslint": "^8.57.1",
118
119
  "father": "^4.1.0",
119
120
  "fs-extra": "^11.3.5",
120
121
  "github-slugger": "^2.0.0",