mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 17:27:06 +08:00
fix(base): match inline-embed placeholder skeleton to seeded 3 col / 1 row shape
The placeholder rendered a default 10×6 BaseTableSkeleton while waiting on the create-base API, then swapped to the real table once the response landed. Because the inline-embed flow now seeds Title + Text 1 + Text 2 with one default row, the real table is 3×1 — the swap visibly collapsed a large fake table down to a small empty one. The scroll didn't jump (initialOffset takes care of that) but the flicker was jarring. Re-introduce rows + columns props on BaseTableSkeleton (default still 10 / 6 so other call sites are unaffected) and pass rows=1 columns=3 from the inline-embed placeholder so the swap is visually stable.
This commit is contained in:
@@ -4,18 +4,31 @@ import classes from "@/features/base/styles/base-table-skeleton.module.css";
|
|||||||
|
|
||||||
const ROW_NUMBER_WIDTH = 64;
|
const ROW_NUMBER_WIDTH = 64;
|
||||||
const COLUMN_WIDTH = 180;
|
const COLUMN_WIDTH = 180;
|
||||||
const COLUMN_COUNT = 6;
|
const DEFAULT_COLUMN_COUNT = 6;
|
||||||
const ROW_COUNT = 10;
|
const DEFAULT_ROW_COUNT = 10;
|
||||||
|
|
||||||
// Deterministic per-cell widths so the skeleton doesn't flicker between
|
// Deterministic per-cell widths so the skeleton doesn't flicker between
|
||||||
// renders. Values are rough normal distribution around 55-85 % of cell.
|
// renders. Values are rough normal distribution around 55-85 % of cell.
|
||||||
const CELL_WIDTH_RATIOS = [0.78, 0.62, 0.84, 0.55, 0.71, 0.66];
|
const CELL_WIDTH_RATIOS = [0.78, 0.62, 0.84, 0.55, 0.71, 0.66];
|
||||||
const HEADER_WIDTH_RATIOS = [0.42, 0.58, 0.5, 0.64, 0.46, 0.54];
|
const HEADER_WIDTH_RATIOS = [0.42, 0.58, 0.5, 0.64, 0.46, 0.54];
|
||||||
|
|
||||||
export function BaseTableSkeleton() {
|
type BaseTableSkeletonProps = {
|
||||||
|
// Override the rendered shape to match what the eventual content
|
||||||
|
// will be — the inline-embed placeholder passes rows=1, columns=3
|
||||||
|
// (matching the seeded Title + Text 1 + Text 2 with one default
|
||||||
|
// row) so the swap from skeleton to real table doesn't visibly
|
||||||
|
// collapse a large fake table down to a small empty one.
|
||||||
|
rows?: number;
|
||||||
|
columns?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BaseTableSkeleton({
|
||||||
|
rows = DEFAULT_ROW_COUNT,
|
||||||
|
columns = DEFAULT_COLUMN_COUNT,
|
||||||
|
}: BaseTableSkeletonProps = {}) {
|
||||||
const gridTemplateColumns = [
|
const gridTemplateColumns = [
|
||||||
`${ROW_NUMBER_WIDTH}px`,
|
`${ROW_NUMBER_WIDTH}px`,
|
||||||
...Array.from({ length: COLUMN_COUNT }, () => `${COLUMN_WIDTH}px`),
|
...Array.from({ length: columns }, () => `${COLUMN_WIDTH}px`),
|
||||||
].join(" ");
|
].join(" ");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -41,27 +54,27 @@ export function BaseTableSkeleton() {
|
|||||||
<Skeleton height={14} width={14} circle />
|
<Skeleton height={14} width={14} circle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{Array.from({ length: COLUMN_COUNT }).map((_, colIndex) => (
|
{Array.from({ length: columns }).map((_, colIndex) => (
|
||||||
<div key={`h-${colIndex}`} className={gridClasses.headerCell}>
|
<div key={`h-${colIndex}`} className={gridClasses.headerCell}>
|
||||||
<div className={classes.headerCellInner}>
|
<div className={classes.headerCellInner}>
|
||||||
<Skeleton height={14} width={14} circle />
|
<Skeleton height={14} width={14} circle />
|
||||||
<Skeleton
|
<Skeleton
|
||||||
height={10}
|
height={10}
|
||||||
width={`${HEADER_WIDTH_RATIOS[colIndex] * 100}%`}
|
width={`${HEADER_WIDTH_RATIOS[colIndex % HEADER_WIDTH_RATIOS.length] * 100}%`}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => (
|
{Array.from({ length: rows }).map((_, rowIndex) => (
|
||||||
<div key={`row-${rowIndex}`} style={{ display: "contents" }}>
|
<div key={`row-${rowIndex}`} style={{ display: "contents" }}>
|
||||||
<div className={gridClasses.cell}>
|
<div className={gridClasses.cell}>
|
||||||
<div className={classes.cellInner}>
|
<div className={classes.cellInner}>
|
||||||
<Skeleton height={10} width={18} radius="sm" />
|
<Skeleton height={10} width={18} radius="sm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{Array.from({ length: COLUMN_COUNT }).map((_, colIndex) => (
|
{Array.from({ length: columns }).map((_, colIndex) => (
|
||||||
<div
|
<div
|
||||||
key={`cell-${rowIndex}-${colIndex}`}
|
key={`cell-${rowIndex}-${colIndex}`}
|
||||||
className={gridClasses.cell}
|
className={gridClasses.cell}
|
||||||
|
|||||||
@@ -74,9 +74,12 @@ export function BaseEmbedView({ node }: NodeViewProps) {
|
|||||||
let content: React.ReactNode;
|
let content: React.ReactNode;
|
||||||
if (pendingKey) {
|
if (pendingKey) {
|
||||||
// Slash command inserted the embed and is awaiting the server's
|
// Slash command inserted the embed and is awaiting the server's
|
||||||
// assigned pageId — render the same skeleton BaseTable shows
|
// assigned pageId. Match the shape the create endpoint will
|
||||||
// during its own initial load so the swap is visually a no-op.
|
// return for an inline-embed (Title + Text 1 + Text 2, one
|
||||||
content = <BaseTableSkeleton />;
|
// empty row — see BaseService.create's `defaults`) so the swap
|
||||||
|
// to the real table doesn't visibly collapse a large fake table
|
||||||
|
// down to a small empty one.
|
||||||
|
content = <BaseTableSkeleton rows={1} columns={3} />;
|
||||||
} else if (!pageId) {
|
} else if (!pageId) {
|
||||||
content = (
|
content = (
|
||||||
<Box p="md">
|
<Box p="md">
|
||||||
|
|||||||
Reference in New Issue
Block a user