@windrun-huaiin/third-ui 29.2.0 → 30.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.
- package/dist/fuma/mdx/cheet-table.d.ts +13 -0
- package/dist/fuma/mdx/cheet-table.js +295 -0
- package/dist/fuma/mdx/cheet-table.mjs +293 -0
- package/dist/fuma/mdx/index.d.ts +1 -0
- package/dist/fuma/mdx/index.js +2 -0
- package/dist/fuma/mdx/index.mjs +1 -0
- package/dist/fuma/server/features/widgets.js +2 -0
- package/dist/fuma/server/features/widgets.mjs +2 -0
- package/dist/lib/fuma-schema-check-util.d.ts +1 -1
- package/dist/main/alert-dialog/confirm-dialog.d.ts +2 -1
- package/dist/main/alert-dialog/confirm-dialog.js +3 -3
- package/dist/main/alert-dialog/confirm-dialog.mjs +4 -4
- package/dist/main/alert-dialog/dialog-loading-action.d.ts +2 -1
- package/dist/main/alert-dialog/dialog-loading-action.js +6 -3
- package/dist/main/alert-dialog/dialog-loading-action.mjs +6 -3
- package/dist/main/alert-dialog/dialog-styles.d.ts +4 -2
- package/dist/main/alert-dialog/dialog-styles.js +8 -4
- package/dist/main/alert-dialog/dialog-styles.mjs +7 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +2 -1
- package/dist/main/alert-dialog/high-priority-confirm-dialog.js +7 -7
- package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +8 -8
- package/dist/main/alert-dialog/info-dialog.d.ts +2 -1
- package/dist/main/alert-dialog/info-dialog.js +3 -3
- package/dist/main/alert-dialog/info-dialog.mjs +4 -4
- package/dist/main/alert-dialog/undoable-confirm-dialog.d.ts +2 -1
- package/dist/main/alert-dialog/undoable-confirm-dialog.js +4 -4
- package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +5 -5
- package/dist/main/anime/anime-beam-frame.d.ts +3 -0
- package/dist/main/anime/anime-beam-frame.js +63 -0
- package/dist/main/anime/anime-beam-frame.mjs +61 -0
- package/dist/main/anime/anime-spiral-loading.d.ts +10 -0
- package/dist/main/anime/anime-spiral-loading.js +77 -0
- package/dist/main/anime/anime-spiral-loading.mjs +75 -0
- package/dist/main/anime/index.d.ts +2 -0
- package/dist/main/anime/index.js +10 -0
- package/dist/main/anime/index.mjs +3 -0
- package/dist/main/beam-frame/animate.d.ts +3 -0
- package/dist/main/beam-frame/animate.js +63 -0
- package/dist/main/beam-frame/animate.mjs +61 -0
- package/dist/main/beam-frame/beam-frame.d.ts +4 -0
- package/dist/main/beam-frame/beam-frame.js +262 -0
- package/dist/main/beam-frame/beam-frame.mjs +258 -0
- package/dist/main/beam-frame/index.d.ts +4 -0
- package/dist/main/beam-frame/index.js +11 -0
- package/dist/main/beam-frame/index.mjs +3 -0
- package/dist/main/beam-frame/motion.d.ts +3 -0
- package/dist/main/beam-frame/motion.js +61 -0
- package/dist/main/beam-frame/motion.mjs +59 -0
- package/dist/main/beam-frame/share-config.d.ts +54 -0
- package/dist/main/beam-frame/share-config.js +161 -0
- package/dist/main/beam-frame/share-config.mjs +152 -0
- package/dist/main/beam-frame-config.d.ts +54 -0
- package/dist/main/beam-frame-config.js +161 -0
- package/dist/main/beam-frame-config.mjs +152 -0
- package/dist/main/calendar/random-date-range-dialog.d.ts +5 -2
- package/dist/main/calendar/random-date-range-dialog.js +239 -109
- package/dist/main/calendar/random-date-range-dialog.mjs +242 -112
- package/dist/main/cta.js +17 -1
- package/dist/main/cta.mjs +18 -2
- package/dist/main/delayed-img.d.ts +1 -1
- package/dist/main/delayed-img.js +8 -5
- package/dist/main/delayed-img.mjs +8 -5
- package/dist/main/info-tooltip.js +70 -9
- package/dist/main/info-tooltip.mjs +70 -9
- package/dist/main/loading-frame/index.d.ts +1 -0
- package/dist/main/loading.d.ts +2 -1
- package/dist/main/loading.js +64 -26
- package/dist/main/loading.mjs +64 -26
- package/dist/main/motion/index.d.ts +1 -0
- package/dist/main/motion/index.js +9 -0
- package/dist/main/motion/index.mjs +2 -0
- package/dist/main/motion/motion-beam-frame.d.ts +3 -0
- package/dist/main/motion/motion-beam-frame.js +61 -0
- package/dist/main/motion/motion-beam-frame.mjs +59 -0
- package/dist/main/snake-loading-frame.d.ts +7 -3
- package/dist/main/snake-loading-frame.js +44 -252
- package/dist/main/snake-loading-frame.mjs +46 -254
- package/package.json +16 -5
- package/src/fuma/mdx/cheet-table.tsx +650 -0
- package/src/fuma/mdx/index.ts +1 -0
- package/src/fuma/server/features/widgets.tsx +2 -0
- package/src/main/alert-dialog/confirm-dialog.tsx +5 -2
- package/src/main/alert-dialog/dialog-loading-action.tsx +22 -5
- package/src/main/alert-dialog/dialog-styles.ts +13 -3
- package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +29 -24
- package/src/main/alert-dialog/info-dialog.tsx +5 -2
- package/src/main/alert-dialog/undoable-confirm-dialog.tsx +21 -18
- package/src/main/anime/anime-beam-frame.tsx +128 -0
- package/src/main/anime/anime-spiral-loading.tsx +123 -0
- package/src/main/anime/index.ts +9 -0
- package/src/main/beam-frame-config.tsx +341 -0
- package/src/main/calendar/random-date-range-dialog.tsx +242 -74
- package/src/main/cta.tsx +50 -21
- package/src/main/delayed-img.tsx +9 -4
- package/src/main/info-tooltip.tsx +116 -20
- package/src/main/loading-frame/index.ts +4 -0
- package/src/main/loading.tsx +75 -24
- package/src/main/motion/index.ts +8 -0
- package/src/main/motion/motion-beam-frame.tsx +137 -0
- package/src/main/snake-loading-frame.tsx +95 -496
- package/src/styles/cta.css +21 -4
- package/src/styles/third-ui.css +0 -20
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CheckIcon,
|
|
5
|
+
CopyIcon,
|
|
6
|
+
EyeIcon,
|
|
7
|
+
EyeOffIcon,
|
|
8
|
+
} from '@windrun-huaiin/base-ui/icons';
|
|
9
|
+
import {
|
|
10
|
+
themeIconColor,
|
|
11
|
+
themeRingColor,
|
|
12
|
+
themeSvgIconColor,
|
|
13
|
+
} from '@windrun-huaiin/base-ui/lib';
|
|
14
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
15
|
+
import {
|
|
16
|
+
Children,
|
|
17
|
+
type CSSProperties,
|
|
18
|
+
isValidElement,
|
|
19
|
+
type HTMLAttributes,
|
|
20
|
+
type ReactElement,
|
|
21
|
+
type ReactNode,
|
|
22
|
+
useEffect,
|
|
23
|
+
useMemo,
|
|
24
|
+
useRef,
|
|
25
|
+
useState,
|
|
26
|
+
} from 'react';
|
|
27
|
+
import { InfoTooltip } from '../../main/info-tooltip';
|
|
28
|
+
|
|
29
|
+
const DEFAULT_COLUMN_WIDTH = 180;
|
|
30
|
+
const MIN_COLUMN_WIDTH = 140;
|
|
31
|
+
const CHEET_TABLE_BORDER_COLOR = `color-mix(in srgb, ${themeSvgIconColor} 35%, transparent)`;
|
|
32
|
+
const CHEET_TABLE_BORDER_CLASS = 'border-[color:var(--cheet-table-border-color)]';
|
|
33
|
+
|
|
34
|
+
type ParsedCheetCell = {
|
|
35
|
+
text: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
force: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type CheetTableCell = ParsedCheetCell & {
|
|
41
|
+
rawText: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type CheetTableModel = {
|
|
45
|
+
headers: CheetTableCell[];
|
|
46
|
+
rows: CheetTableCell[][];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type CheetTableProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {
|
|
50
|
+
title?: ReactNode;
|
|
51
|
+
description?: string;
|
|
52
|
+
copyableColumns?: string[];
|
|
53
|
+
defaultOpen?: boolean;
|
|
54
|
+
collapsible?: boolean;
|
|
55
|
+
striped?: boolean;
|
|
56
|
+
stickyHeader?: boolean;
|
|
57
|
+
emptyText?: string;
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function parseCheetCell(raw: string): ParsedCheetCell {
|
|
62
|
+
let value = raw.trim();
|
|
63
|
+
let force = false;
|
|
64
|
+
|
|
65
|
+
if (value.startsWith('!!')) {
|
|
66
|
+
force = true;
|
|
67
|
+
value = value.slice(2).trimStart();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const descriptionIndex = value.indexOf('??');
|
|
71
|
+
|
|
72
|
+
if (descriptionIndex === -1) {
|
|
73
|
+
return {
|
|
74
|
+
text: value,
|
|
75
|
+
force,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const description = value.slice(descriptionIndex + 2).trim();
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
text: value.slice(0, descriptionIndex).trimEnd(),
|
|
83
|
+
description: description ? description : undefined,
|
|
84
|
+
force,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeText(value: string) {
|
|
89
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function collectText(node: ReactNode): string {
|
|
93
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
94
|
+
return String(node);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Array.isArray(node)) {
|
|
98
|
+
return node.map(collectText).join('');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isValidElement<{ children?: ReactNode }>(node)) {
|
|
102
|
+
return collectText(node.props.children);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getElementChildren(element: ReactElement<{ children?: ReactNode }>) {
|
|
109
|
+
return Children.toArray(element.props.children);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isElementType(node: ReactNode, type: string) {
|
|
113
|
+
return isValidElement(node) && node.type === type;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function findFirstElement(node: ReactNode, type: string): ReactElement<{ children?: ReactNode }> | null {
|
|
117
|
+
if (isElementType(node, type)) {
|
|
118
|
+
return node as ReactElement<{ children?: ReactNode }>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(node)) {
|
|
122
|
+
for (const child of node) {
|
|
123
|
+
const match = findFirstElement(child, type);
|
|
124
|
+
|
|
125
|
+
if (match) {
|
|
126
|
+
return match;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isValidElement<{ children?: ReactNode }>(node)) {
|
|
132
|
+
return findFirstElement(node.props.children, type);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function collectRowElements(node: ReactNode): ReactElement<{ children?: ReactNode }>[] {
|
|
139
|
+
if (isElementType(node, 'tr')) {
|
|
140
|
+
return [node as ReactElement<{ children?: ReactNode }>];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Array.isArray(node)) {
|
|
144
|
+
return node.flatMap(collectRowElements);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isValidElement<{ children?: ReactNode }>(node)) {
|
|
148
|
+
return collectRowElements(node.props.children);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseRow(row: ReactElement<{ children?: ReactNode }>) {
|
|
155
|
+
return getElementChildren(row)
|
|
156
|
+
.filter(isValidElement)
|
|
157
|
+
.map((cell) => {
|
|
158
|
+
const rawText = normalizeText(
|
|
159
|
+
collectText((cell as ReactElement<{ children?: ReactNode }>).props.children),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
rawText,
|
|
164
|
+
...parseCheetCell(rawText),
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseCheetTable(children: ReactNode): CheetTableModel | null {
|
|
170
|
+
const rows = collectRowElements(children);
|
|
171
|
+
|
|
172
|
+
if (rows.length > 0) {
|
|
173
|
+
const headerRow = rows[0];
|
|
174
|
+
const dataRows = rows.slice(1);
|
|
175
|
+
const headers = parseRow(headerRow);
|
|
176
|
+
|
|
177
|
+
if (headers.length === 0) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
headers,
|
|
183
|
+
rows: dataRows.map(parseRow).filter((row) => row.length > 0),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const table = findFirstElement(children, 'table');
|
|
188
|
+
|
|
189
|
+
if (!table) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const thead = findFirstElement(table.props.children, 'thead');
|
|
194
|
+
const tbody = findFirstElement(table.props.children, 'tbody');
|
|
195
|
+
const headRows = thead ? collectRowElements(thead.props.children) : [];
|
|
196
|
+
const bodyRows = tbody ? collectRowElements(tbody.props.children) : [];
|
|
197
|
+
const fallbackRows = !thead && !tbody ? collectRowElements(table.props.children) : [];
|
|
198
|
+
const headerRow = headRows[0] ?? fallbackRows[0];
|
|
199
|
+
const dataRows = bodyRows.length > 0 ? bodyRows : fallbackRows.slice(1);
|
|
200
|
+
|
|
201
|
+
if (!headerRow) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const headers = parseRow(headerRow);
|
|
206
|
+
|
|
207
|
+
if (headers.length === 0) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
headers,
|
|
213
|
+
rows: dataRows.map(parseRow).filter((row) => row.length > 0),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseCheetTableElement(element: HTMLElement | null): CheetTableModel | null {
|
|
218
|
+
const table = element?.querySelector('table');
|
|
219
|
+
|
|
220
|
+
if (!table) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const headRow = table.querySelector('thead tr');
|
|
225
|
+
const bodyRows = Array.from(table.querySelectorAll('tbody tr'));
|
|
226
|
+
const fallbackRows = Array.from(table.querySelectorAll('tr'));
|
|
227
|
+
const headerRow = headRow ?? fallbackRows[0];
|
|
228
|
+
const dataRows = bodyRows.length > 0
|
|
229
|
+
? bodyRows
|
|
230
|
+
: fallbackRows.slice(headerRow ? 1 : 0);
|
|
231
|
+
|
|
232
|
+
if (!headerRow) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const parseDomRow = (row: Element) =>
|
|
237
|
+
Array.from(row.querySelectorAll('th,td')).map((cell) => {
|
|
238
|
+
const rawText = normalizeText(cell.textContent ?? '');
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
rawText,
|
|
242
|
+
...parseCheetCell(rawText),
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const headers = parseDomRow(headerRow);
|
|
247
|
+
|
|
248
|
+
if (headers.length === 0) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
headers,
|
|
254
|
+
rows: dataRows.map(parseDomRow).filter((row) => row.length > 0),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function createCopyableColumnSet(headers: CheetTableCell[], copyableColumns?: string[]) {
|
|
259
|
+
const names = copyableColumns ?? [];
|
|
260
|
+
|
|
261
|
+
return new Set(
|
|
262
|
+
names.map((name) => {
|
|
263
|
+
const header = headers.find(
|
|
264
|
+
(item) => item.text === name || item.rawText === name,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return header ? header.text : name;
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function CheetCellContent({
|
|
273
|
+
cell,
|
|
274
|
+
force,
|
|
275
|
+
copied,
|
|
276
|
+
copyable,
|
|
277
|
+
onCopy,
|
|
278
|
+
}: {
|
|
279
|
+
cell: CheetTableCell;
|
|
280
|
+
force: boolean;
|
|
281
|
+
copied?: boolean;
|
|
282
|
+
copyable?: boolean;
|
|
283
|
+
onCopy?: () => void;
|
|
284
|
+
}) {
|
|
285
|
+
return (
|
|
286
|
+
<span className="flex min-w-0 items-center gap-0.5">
|
|
287
|
+
<span
|
|
288
|
+
className={cn(
|
|
289
|
+
'block min-w-0 truncate',
|
|
290
|
+
force && cn('font-semibold', themeIconColor),
|
|
291
|
+
)}
|
|
292
|
+
>
|
|
293
|
+
{cell.text}
|
|
294
|
+
</span>
|
|
295
|
+
{cell.description ? (
|
|
296
|
+
<InfoTooltip
|
|
297
|
+
content={cell.description}
|
|
298
|
+
className="not-prose -mx-0.5"
|
|
299
|
+
desktopSide="bottom"
|
|
300
|
+
/>
|
|
301
|
+
) : null}
|
|
302
|
+
{copyable ? (
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
aria-label={copied ? 'Copied' : 'Copy cell content'}
|
|
306
|
+
className={cn(
|
|
307
|
+
'not-prose inline-flex size-6 shrink-0 touch-manipulation items-center justify-center rounded text-fd-muted-foreground transition',
|
|
308
|
+
'hover:bg-fd-accent hover:text-fd-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-950',
|
|
309
|
+
copied && themeIconColor,
|
|
310
|
+
themeRingColor,
|
|
311
|
+
)}
|
|
312
|
+
onClick={(event) => {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
event.stopPropagation();
|
|
315
|
+
onCopy?.();
|
|
316
|
+
}}
|
|
317
|
+
onPointerDown={(event) => {
|
|
318
|
+
event.stopPropagation();
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
{copied ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
|
|
322
|
+
</button>
|
|
323
|
+
) : null}
|
|
324
|
+
</span>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function CheetTable({
|
|
329
|
+
title,
|
|
330
|
+
description,
|
|
331
|
+
copyableColumns,
|
|
332
|
+
defaultOpen = true,
|
|
333
|
+
collapsible = true,
|
|
334
|
+
striped = true,
|
|
335
|
+
stickyHeader = false,
|
|
336
|
+
emptyText = 'No data',
|
|
337
|
+
className,
|
|
338
|
+
children,
|
|
339
|
+
style,
|
|
340
|
+
...props
|
|
341
|
+
}: CheetTableProps) {
|
|
342
|
+
const [mounted, setMounted] = useState(false);
|
|
343
|
+
const [model, setModel] = useState<CheetTableModel | null>(null);
|
|
344
|
+
const [parsed, setParsed] = useState(false);
|
|
345
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
346
|
+
const [copiedCell, setCopiedCell] = useState<string | null>(null);
|
|
347
|
+
const [columnWidths, setColumnWidths] = useState<number[]>([]);
|
|
348
|
+
const sourceRef = useRef<HTMLDivElement>(null);
|
|
349
|
+
const resizeStartRef = useRef<{
|
|
350
|
+
columnIndex: number;
|
|
351
|
+
startX: number;
|
|
352
|
+
startWidth: number;
|
|
353
|
+
} | null>(null);
|
|
354
|
+
const headerCount = model?.headers.length ?? 0;
|
|
355
|
+
const copyableColumnSet = useMemo(
|
|
356
|
+
() => createCopyableColumnSet(model?.headers ?? [], copyableColumns),
|
|
357
|
+
[copyableColumns, model?.headers],
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
setMounted(true);
|
|
362
|
+
setModel(parseCheetTableElement(sourceRef.current) ?? parseCheetTable(children));
|
|
363
|
+
setParsed(true);
|
|
364
|
+
}, [children]);
|
|
365
|
+
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
if (headerCount === 0) {
|
|
368
|
+
setColumnWidths([]);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
setColumnWidths((current) =>
|
|
373
|
+
Array.from({ length: headerCount }, (_, index) =>
|
|
374
|
+
Math.max(current[index] ?? DEFAULT_COLUMN_WIDTH, MIN_COLUMN_WIDTH),
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
}, [headerCount]);
|
|
378
|
+
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
function handlePointerMove(event: PointerEvent) {
|
|
381
|
+
const start = resizeStartRef.current;
|
|
382
|
+
|
|
383
|
+
if (!start) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const nextWidth = Math.max(
|
|
388
|
+
MIN_COLUMN_WIDTH,
|
|
389
|
+
start.startWidth + event.clientX - start.startX,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
setColumnWidths((current) => {
|
|
393
|
+
const next = [...current];
|
|
394
|
+
next[start.columnIndex] = nextWidth;
|
|
395
|
+
return next;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function handlePointerUp() {
|
|
400
|
+
resizeStartRef.current = null;
|
|
401
|
+
document.body.style.removeProperty('cursor');
|
|
402
|
+
document.body.style.removeProperty('user-select');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
window.addEventListener('pointermove', handlePointerMove);
|
|
406
|
+
window.addEventListener('pointerup', handlePointerUp);
|
|
407
|
+
|
|
408
|
+
return () => {
|
|
409
|
+
window.removeEventListener('pointermove', handlePointerMove);
|
|
410
|
+
window.removeEventListener('pointerup', handlePointerUp);
|
|
411
|
+
document.body.style.removeProperty('cursor');
|
|
412
|
+
document.body.style.removeProperty('user-select');
|
|
413
|
+
};
|
|
414
|
+
}, []);
|
|
415
|
+
|
|
416
|
+
const startResize = (event: React.PointerEvent, columnIndex: number) => {
|
|
417
|
+
event.preventDefault();
|
|
418
|
+
event.stopPropagation();
|
|
419
|
+
|
|
420
|
+
resizeStartRef.current = {
|
|
421
|
+
columnIndex,
|
|
422
|
+
startX: event.clientX,
|
|
423
|
+
startWidth: columnWidths[columnIndex] ?? DEFAULT_COLUMN_WIDTH,
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
document.body.style.cursor = 'col-resize';
|
|
427
|
+
document.body.style.userSelect = 'none';
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const handleCopy = async (text: string, cellId: string) => {
|
|
431
|
+
if (!text || !navigator.clipboard) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
await navigator.clipboard.writeText(text);
|
|
437
|
+
setCopiedCell(cellId);
|
|
438
|
+
window.setTimeout(() => setCopiedCell(null), 1200);
|
|
439
|
+
} catch {
|
|
440
|
+
// Keep MDX reading uninterrupted when clipboard permission is unavailable.
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const shellClassName = cn(
|
|
445
|
+
'not-prose my-6 overflow-hidden rounded-lg border bg-fd-card text-fd-card-foreground shadow-sm',
|
|
446
|
+
CHEET_TABLE_BORDER_CLASS,
|
|
447
|
+
className,
|
|
448
|
+
);
|
|
449
|
+
const shellStyle = {
|
|
450
|
+
'--cheet-table-border-color': CHEET_TABLE_BORDER_COLOR,
|
|
451
|
+
...style,
|
|
452
|
+
} as CSSProperties;
|
|
453
|
+
|
|
454
|
+
const titleBar = title || description || collapsible ? (
|
|
455
|
+
<div className="grid min-w-0 grid-cols-[minmax(0,90%)_minmax(2rem,10%)] items-center bg-fd-muted/35 px-4 py-2">
|
|
456
|
+
<div className="flex min-w-0 items-center justify-center gap-0.5">
|
|
457
|
+
{title ? (
|
|
458
|
+
<h3 className="truncate text-center text-sm font-semibold leading-6 text-fd-foreground">
|
|
459
|
+
{title}
|
|
460
|
+
</h3>
|
|
461
|
+
) : null}
|
|
462
|
+
{description ? (
|
|
463
|
+
<InfoTooltip
|
|
464
|
+
content={description}
|
|
465
|
+
className="not-prose -mx-0.5"
|
|
466
|
+
desktopSide="bottom"
|
|
467
|
+
/>
|
|
468
|
+
) : null}
|
|
469
|
+
</div>
|
|
470
|
+
{collapsible ? (
|
|
471
|
+
<button
|
|
472
|
+
type="button"
|
|
473
|
+
className={cn(
|
|
474
|
+
'justify-self-end inline-flex size-8 shrink-0 items-center justify-center rounded-md text-fd-muted-foreground transition',
|
|
475
|
+
'hover:text-fd-accent-foreground',
|
|
476
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-950',
|
|
477
|
+
open && themeIconColor,
|
|
478
|
+
themeRingColor,
|
|
479
|
+
)}
|
|
480
|
+
aria-expanded={open}
|
|
481
|
+
onClick={() => setOpen((value) => !value)}
|
|
482
|
+
>
|
|
483
|
+
{open ? <EyeIcon className="size-4" /> : <EyeOffIcon className="size-4" />}
|
|
484
|
+
</button>
|
|
485
|
+
) : null}
|
|
486
|
+
</div>
|
|
487
|
+
) : null;
|
|
488
|
+
|
|
489
|
+
if (!mounted) {
|
|
490
|
+
return (
|
|
491
|
+
<section
|
|
492
|
+
data-cheet-table
|
|
493
|
+
className={shellClassName}
|
|
494
|
+
style={shellStyle}
|
|
495
|
+
{...props}
|
|
496
|
+
>
|
|
497
|
+
{titleBar}
|
|
498
|
+
<div ref={sourceRef} hidden>
|
|
499
|
+
{children}
|
|
500
|
+
</div>
|
|
501
|
+
</section>
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (!model) {
|
|
506
|
+
return (
|
|
507
|
+
<section
|
|
508
|
+
data-cheet-table
|
|
509
|
+
className={shellClassName}
|
|
510
|
+
style={shellStyle}
|
|
511
|
+
{...props}
|
|
512
|
+
>
|
|
513
|
+
{titleBar}
|
|
514
|
+
<div ref={sourceRef} hidden>
|
|
515
|
+
{children}
|
|
516
|
+
</div>
|
|
517
|
+
{open && parsed ? (
|
|
518
|
+
<div className="px-4 py-6 text-center text-sm text-fd-muted-foreground">
|
|
519
|
+
{emptyText}
|
|
520
|
+
</div>
|
|
521
|
+
) : null}
|
|
522
|
+
</section>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const content = model.rows.length > 0 ? (
|
|
527
|
+
<div className="overflow-x-auto">
|
|
528
|
+
<table
|
|
529
|
+
className="table-fixed border-separate border-spacing-0 text-sm"
|
|
530
|
+
style={{
|
|
531
|
+
width: columnWidths.length
|
|
532
|
+
? columnWidths.reduce((total, width) => total + width, 0)
|
|
533
|
+
: '100%',
|
|
534
|
+
minWidth: '100%',
|
|
535
|
+
}}
|
|
536
|
+
>
|
|
537
|
+
<colgroup>
|
|
538
|
+
{model.headers.map((header, index) => (
|
|
539
|
+
<col
|
|
540
|
+
key={`${header.text}-${index}-col`}
|
|
541
|
+
style={{
|
|
542
|
+
width: columnWidths[index] ?? DEFAULT_COLUMN_WIDTH,
|
|
543
|
+
minWidth: MIN_COLUMN_WIDTH,
|
|
544
|
+
}}
|
|
545
|
+
/>
|
|
546
|
+
))}
|
|
547
|
+
</colgroup>
|
|
548
|
+
<thead className={cn(stickyHeader && 'sticky top-0 z-10')}>
|
|
549
|
+
<tr>
|
|
550
|
+
{model.headers.map((header, index) => (
|
|
551
|
+
<th
|
|
552
|
+
key={`${header.text}-${index}`}
|
|
553
|
+
className={cn(
|
|
554
|
+
'relative border-t border-b bg-fd-muted/80 px-3 py-2.5 align-middle text-xs font-semibold uppercase tracking-normal text-fd-muted-foreground first:pl-4 last:pr-4',
|
|
555
|
+
index > 0 && 'border-l',
|
|
556
|
+
CHEET_TABLE_BORDER_CLASS,
|
|
557
|
+
'text-left',
|
|
558
|
+
)}
|
|
559
|
+
scope="col"
|
|
560
|
+
>
|
|
561
|
+
<CheetCellContent cell={header} force={header.force} />
|
|
562
|
+
{index < model.headers.length - 1 ? (
|
|
563
|
+
<button
|
|
564
|
+
type="button"
|
|
565
|
+
aria-label="Resize column"
|
|
566
|
+
className={cn(
|
|
567
|
+
'absolute top-0 right-0 h-full w-2 cursor-col-resize touch-none select-none',
|
|
568
|
+
'after:absolute after:top-2 after:right-0 after:bottom-2 after:w-px after:bg-transparent after:transition-colors',
|
|
569
|
+
'hover:after:bg-fd-muted-foreground/40',
|
|
570
|
+
)}
|
|
571
|
+
onPointerDown={(event) => startResize(event, index)}
|
|
572
|
+
/>
|
|
573
|
+
) : null}
|
|
574
|
+
</th>
|
|
575
|
+
))}
|
|
576
|
+
</tr>
|
|
577
|
+
</thead>
|
|
578
|
+
<tbody>
|
|
579
|
+
{model.rows.map((row, rowIndex) => (
|
|
580
|
+
<tr
|
|
581
|
+
key={rowIndex}
|
|
582
|
+
className={cn(
|
|
583
|
+
'transition-colors hover:bg-fd-accent/45',
|
|
584
|
+
striped && rowIndex % 2 === 1 && 'bg-fd-muted/30',
|
|
585
|
+
)}
|
|
586
|
+
>
|
|
587
|
+
{model.headers.map((header, columnIndex) => {
|
|
588
|
+
const cell = row[columnIndex] ?? {
|
|
589
|
+
rawText: '',
|
|
590
|
+
text: '',
|
|
591
|
+
force: false,
|
|
592
|
+
};
|
|
593
|
+
const cellId = `${rowIndex}:${columnIndex}`;
|
|
594
|
+
const copyable = copyableColumnSet.has(header.text);
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
<td
|
|
598
|
+
key={cellId}
|
|
599
|
+
className={cn(
|
|
600
|
+
'group px-3 py-2.5 align-top text-fd-foreground first:pl-4 last:pr-4',
|
|
601
|
+
rowIndex < model.rows.length - 1 && 'border-b',
|
|
602
|
+
columnIndex > 0 && 'border-l',
|
|
603
|
+
'text-left',
|
|
604
|
+
CHEET_TABLE_BORDER_CLASS,
|
|
605
|
+
)}
|
|
606
|
+
onDoubleClick={(event) => {
|
|
607
|
+
event.preventDefault();
|
|
608
|
+
event.stopPropagation();
|
|
609
|
+
}}
|
|
610
|
+
>
|
|
611
|
+
<CheetCellContent
|
|
612
|
+
cell={cell}
|
|
613
|
+
force={header.force || cell.force}
|
|
614
|
+
copyable={copyable}
|
|
615
|
+
copied={copiedCell === cellId}
|
|
616
|
+
onCopy={
|
|
617
|
+
copyable
|
|
618
|
+
? () => handleCopy(cell.text, cellId)
|
|
619
|
+
: undefined
|
|
620
|
+
}
|
|
621
|
+
/>
|
|
622
|
+
</td>
|
|
623
|
+
);
|
|
624
|
+
})}
|
|
625
|
+
</tr>
|
|
626
|
+
))}
|
|
627
|
+
</tbody>
|
|
628
|
+
</table>
|
|
629
|
+
</div>
|
|
630
|
+
) : (
|
|
631
|
+
<div className="px-4 py-6 text-center text-sm text-fd-muted-foreground">
|
|
632
|
+
{emptyText}
|
|
633
|
+
</div>
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
return (
|
|
637
|
+
<section
|
|
638
|
+
data-cheet-table
|
|
639
|
+
className={shellClassName}
|
|
640
|
+
style={shellStyle}
|
|
641
|
+
{...props}
|
|
642
|
+
>
|
|
643
|
+
{titleBar}
|
|
644
|
+
<div ref={sourceRef} hidden>
|
|
645
|
+
{children}
|
|
646
|
+
</div>
|
|
647
|
+
{open ? content : null}
|
|
648
|
+
</section>
|
|
649
|
+
);
|
|
650
|
+
}
|
package/src/fuma/mdx/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { ZiaCard } from '../../mdx/zia-card';
|
|
|
5
5
|
import { GradientButton } from '../../../main/buttons';
|
|
6
6
|
import { ZiaFile, ZiaFolder } from '../../mdx/zia-file';
|
|
7
7
|
import { SunoEmbed } from '../../mdx/suno-embed';
|
|
8
|
+
import { CheetTable } from '../../mdx/cheet-table';
|
|
8
9
|
|
|
9
10
|
const ImageGrid = lazy(() =>
|
|
10
11
|
import('../../heavy/image-grid').then((mod) => ({ default: mod.ImageGrid })),
|
|
@@ -24,6 +25,7 @@ export function createWidgetMdxComponents(
|
|
|
24
25
|
ZiaFile,
|
|
25
26
|
ZiaFolder,
|
|
26
27
|
SunoEmbed,
|
|
28
|
+
CheetTable,
|
|
27
29
|
ImageGrid: (props) => (
|
|
28
30
|
<ImageGrid {...props} cdnBaseUrl={cdnBaseUrl} />
|
|
29
31
|
),
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
dialogHeaderClass,
|
|
22
22
|
dialogThemedOverlayClass,
|
|
23
23
|
dialogTitleClass,
|
|
24
|
+
dialogTitleTextClass,
|
|
24
25
|
primaryButtonClass,
|
|
25
26
|
secondaryButtonClass,
|
|
26
27
|
} from './dialog-styles';
|
|
@@ -39,6 +40,7 @@ interface ConfirmDialogProps {
|
|
|
39
40
|
confirmText?: string;
|
|
40
41
|
emphasis?: ConfirmDialogEmphasis;
|
|
41
42
|
loadingActions?: readonly DialogLoadingAction[];
|
|
43
|
+
loadingFullPage?: boolean;
|
|
42
44
|
onCancel?: DialogActionHandler;
|
|
43
45
|
onConfirm?: DialogActionHandler;
|
|
44
46
|
}
|
|
@@ -76,6 +78,7 @@ export function ConfirmDialog({
|
|
|
76
78
|
confirmText = 'Confirm',
|
|
77
79
|
emphasis = 'confirm',
|
|
78
80
|
loadingActions,
|
|
81
|
+
loadingFullPage,
|
|
79
82
|
onCancel,
|
|
80
83
|
onConfirm,
|
|
81
84
|
}: ConfirmDialogProps) {
|
|
@@ -83,7 +86,7 @@ export function ConfirmDialog({
|
|
|
83
86
|
const Icon = typeClass.Icon;
|
|
84
87
|
const cancelButtonClass = emphasis === 'cancel' ? typeClass.action : secondaryButtonClass;
|
|
85
88
|
const confirmButtonClass = emphasis === 'cancel' ? secondaryButtonClass : typeClass.action;
|
|
86
|
-
const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, onOpenChange });
|
|
89
|
+
const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, loadingFullPage, onOpenChange });
|
|
87
90
|
|
|
88
91
|
const handleCancel = () => {
|
|
89
92
|
void runDialogAction('cancel', onCancel);
|
|
@@ -106,7 +109,7 @@ export function ConfirmDialog({
|
|
|
106
109
|
<span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', typeClass.iconWrap)}>
|
|
107
110
|
<Icon className={cn('size-5', typeClass.icon)} />
|
|
108
111
|
</span>
|
|
109
|
-
<span className=
|
|
112
|
+
<span className={dialogTitleTextClass}>{title}</span>
|
|
110
113
|
</div>
|
|
111
114
|
</AlertDialogTitle>
|
|
112
115
|
<button
|