@multiplatform.one/frappe-ui 7.53.0 → 7.54.1
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/cjs/FrappeListView.cjs +13 -28
- package/dist/cjs/FrappeListView.native.js +15 -30
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/record/RecordHero.cjs +1 -1
- package/dist/cjs/record/RecordHero.native.js +1 -1
- package/dist/cjs/record/RecordHero.native.js.map +1 -1
- package/dist/cjs/record/RecordPage.cjs +1 -1
- package/dist/cjs/record/RecordPage.native.js +1 -1
- package/dist/esm/FrappeListView.mjs +14 -29
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +16 -31
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/record/RecordHero.mjs +1 -1
- package/dist/esm/record/RecordHero.mjs.map +1 -1
- package/dist/esm/record/RecordHero.native.js +1 -1
- package/dist/esm/record/RecordHero.native.js.map +1 -1
- package/dist/esm/record/RecordPage.mjs +1 -1
- package/dist/esm/record/RecordPage.native.js +1 -1
- package/dist/jsx/FrappeListView.mjs +14 -29
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +15 -30
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/record/RecordHero.mjs +1 -1
- package/dist/jsx/record/RecordHero.mjs.map +1 -1
- package/dist/jsx/record/RecordHero.native.js +1 -1
- package/dist/jsx/record/RecordHero.native.js.map +1 -1
- package/dist/jsx/record/RecordPage.mjs +1 -1
- package/dist/jsx/record/RecordPage.native.js +1 -1
- package/package.json +16 -16
- package/src/FrappeListView.controls.spec.tsx +7 -0
- package/src/FrappeListView.spec.tsx +8 -3
- package/src/FrappeListView.tsx +16 -19
- package/src/frappeRoutes.spec.tsx +3 -0
- package/src/record/RecordHero.tsx +1 -1
- package/src/record/RecordPage.tsx +1 -1
- package/src/schemaForm/SchemaForm.spec.tsx +16 -4
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// FrappeListView's list controls (MPO-411): sorts, a facet on a connection
|
|
2
2
|
// doctype, a range, favorites, a search mapping and the URL state that a
|
|
3
3
|
// list page (pokemon's roster) used to hand-wire around the view.
|
|
4
|
+
import { dismissAllToasts } from "@multiplatform.one/components";
|
|
4
5
|
import { Order } from "@multiplatform.one/frappe";
|
|
5
6
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
6
7
|
import { act, cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
|
|
@@ -47,8 +48,14 @@ afterAll(() => {
|
|
|
47
48
|
globalThis.fetch = realFetch;
|
|
48
49
|
});
|
|
49
50
|
|
|
51
|
+
// A favorite toggle shows a toast that outlives its test in the module
|
|
52
|
+
// store. Remounted by a later test, its close timer calls tamagui's hide,
|
|
53
|
+
// whose 100 ms follow-up fired after happy-dom was gone ("window is not
|
|
54
|
+
// defined", two unhandled errors that fail the file). Unmount first, then
|
|
55
|
+
// clear the store, so no toast reaches the next test.
|
|
50
56
|
afterEach(() => {
|
|
51
57
|
cleanup();
|
|
58
|
+
dismissAllToasts();
|
|
52
59
|
vi.restoreAllMocks();
|
|
53
60
|
});
|
|
54
61
|
|
|
@@ -164,15 +164,20 @@ describe("FrappeListView refresh press floor (LC-54 amendment)", () => {
|
|
|
164
164
|
const midWeightAtom = /(?:^|\s)(?:_fw-|_fow-|_fontWeight-)(?:500|600)(?:\s|$)/;
|
|
165
165
|
|
|
166
166
|
describe("FrappeListView design-law (avatar + type)", () => {
|
|
167
|
-
|
|
167
|
+
// LC-155 ROW-ANATOMY: a list row's leading avatar fills ListRow's `$true`
|
|
168
|
+
// square media slot. It replaced the nestedControl (29px) avatar.
|
|
169
|
+
it("list-row avatars fill the $true media slot (LC-155), not a 40px literal", async () => {
|
|
168
170
|
const { container } = renderListView();
|
|
169
171
|
const avatar = await waitFor(() => {
|
|
170
172
|
const node = container.querySelector("[data-avatar-px]");
|
|
171
173
|
expect(node).not.toBeNull();
|
|
172
174
|
return node as HTMLElement;
|
|
173
175
|
});
|
|
174
|
-
|
|
175
|
-
expect(
|
|
176
|
+
const slot = avatar.closest('[data-list-slot="media"]');
|
|
177
|
+
expect(slot).not.toBeNull();
|
|
178
|
+
expect(avatar.getAttribute("data-avatar-nested")).toBeNull();
|
|
179
|
+
const px = Number(getVariableValue(getSize("$true" as SizeTokens)));
|
|
180
|
+
expect(Number(avatar.getAttribute("data-avatar-px"))).toBe(px);
|
|
176
181
|
expect(Number(avatar.getAttribute("data-avatar-px"))).not.toBe(40);
|
|
177
182
|
});
|
|
178
183
|
|
package/src/FrappeListView.tsx
CHANGED
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
Avatar,
|
|
22
22
|
EmptyState,
|
|
23
23
|
ListPageLayout,
|
|
24
|
+
ListRow,
|
|
24
25
|
PageHeader,
|
|
25
26
|
SkipLink,
|
|
26
27
|
Tooltip,
|
|
27
28
|
ViewSwitcher,
|
|
28
|
-
componentColors,
|
|
29
29
|
showToast,
|
|
30
30
|
type DropdownMenuEntry,
|
|
31
31
|
type ImageGridProps,
|
|
@@ -489,22 +489,19 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
|
|
|
489
489
|
const imageUrl = imageFieldName ? resolveImageUrl(row[imageFieldName], finalBaseURL) : "";
|
|
490
490
|
|
|
491
491
|
const titleName = typeof title === "string" ? title : undefined;
|
|
492
|
+
// LC-155: the MPO ListRow owns the anatomy (headline, one meta line,
|
|
493
|
+
// a $true media slot).
|
|
492
494
|
return (
|
|
493
|
-
<
|
|
494
|
-
{
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
{...
|
|
503
|
-
>
|
|
504
|
-
{title}
|
|
505
|
-
</Text>
|
|
506
|
-
{subtitleParts.length > 0 && (
|
|
507
|
-
<Text {...knobProps.body} color={knobProps.textAccentColor} numberOfLines={1}>
|
|
495
|
+
<ListRow
|
|
496
|
+
media={
|
|
497
|
+
imageUrl ? (
|
|
498
|
+
<Avatar circular size="$true" src={imageUrl} name={titleName} alt={titleName ?? ""} />
|
|
499
|
+
) : undefined
|
|
500
|
+
}
|
|
501
|
+
title={title}
|
|
502
|
+
subtitle={
|
|
503
|
+
subtitleParts.length > 0 ? (
|
|
504
|
+
<Text {...knobProps.body} {...knobProps.textRole.meta} numberOfLines={1}>
|
|
508
505
|
{subtitleParts.map((part, index) => (
|
|
509
506
|
<React.Fragment key={index}>
|
|
510
507
|
{index > 0 ? " · " : null}
|
|
@@ -512,9 +509,9 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
|
|
|
512
509
|
</React.Fragment>
|
|
513
510
|
))}
|
|
514
511
|
</Text>
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
|
|
512
|
+
) : undefined
|
|
513
|
+
}
|
|
514
|
+
/>
|
|
518
515
|
);
|
|
519
516
|
},
|
|
520
517
|
[effectiveDisplayFields, imageFieldName, finalBaseURL, knobProps],
|
|
@@ -284,6 +284,9 @@ describe("frappeRouteTarget", () => {
|
|
|
284
284
|
describe("FrappeListView with a route", () => {
|
|
285
285
|
it("is the doctype's list page: its rows, a row opens the record, Add opens a new one", async () => {
|
|
286
286
|
const { container, pushed } = renderAt("/moves", "list");
|
|
287
|
+
// The file's first list render pays the cold imports before its read
|
|
288
|
+
// goes out; time the row wait from the read, as the guest case does.
|
|
289
|
+
await movesListed;
|
|
287
290
|
await waitFor(() => expect(screen.getByText("tackle")).toBeTruthy());
|
|
288
291
|
expect(screen.getByText("vine-whip")).toBeTruthy();
|
|
289
292
|
expect(screen.getByRole("heading", { name: doctype })).toBeTruthy();
|
|
@@ -80,7 +80,7 @@ export function RecordVitals({ tiles }: { tiles: readonly RecordVitalTile[] }) {
|
|
|
80
80
|
testID={`record-vital-${tile.id}`}
|
|
81
81
|
data-record-metric={tile.id}
|
|
82
82
|
>
|
|
83
|
-
<Text {...knobProps.label}
|
|
83
|
+
<Text {...knobProps.label} {...knobProps.textRole.label} numberOfLines={1}>
|
|
84
84
|
{tile.label}
|
|
85
85
|
</Text>
|
|
86
86
|
<TamaguiSizableText size="$6" {...knobProps.heading} color="$color" numberOfLines={1}>
|
|
@@ -360,7 +360,7 @@ export function RecordPage<TDoc extends { name: string; doctype: string }>({
|
|
|
360
360
|
</XStack>
|
|
361
361
|
<RecordLargeTitle title={title} />
|
|
362
362
|
{look?.subtitle?.(doc) ? (
|
|
363
|
-
<Text {...nested.body} color="$
|
|
363
|
+
<Text {...nested.body} color="$textMuted">
|
|
364
364
|
{look.subtitle(doc)}
|
|
365
365
|
</Text>
|
|
366
366
|
) : null}
|
|
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { fireEvent, screen } from "@testing-library/react";
|
|
5
5
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
6
|
+
import { hairlineBorderWidth, Preset } from "@multiplatform.one/theme";
|
|
6
7
|
import { describe, expect, it, vi } from "vitest";
|
|
7
8
|
import type { FieldSchema } from "../fieldRegistry";
|
|
8
9
|
import { SchemaForm, type FormSchema } from "./index";
|
|
@@ -79,12 +80,15 @@ describe("SchemaForm design-law (MPO-60 / R10)", () => {
|
|
|
79
80
|
expect(input.disabled || input.getAttribute("aria-disabled") === "true").toBe(true);
|
|
80
81
|
});
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
// LC-151 replaced the borderless content card: a top-level card's edge is
|
|
84
|
+
// cardSurface's theme hairline, which follows the borderWidth knob, and the
|
|
85
|
+
// section hardcodes no border of its own.
|
|
86
|
+
it("default section is SF-CARD: the cardSurface hairline edge (LC-151), no hardcoded border", () => {
|
|
83
87
|
const schema: FormSchema = {
|
|
84
88
|
fields: identityFields,
|
|
85
89
|
sections: [{ label: "Identity", columns: [{ fields: identityFields }] }],
|
|
86
90
|
};
|
|
87
|
-
const { container } = renderWithProviders(
|
|
91
|
+
const { container, unmount } = renderWithProviders(
|
|
88
92
|
<SchemaForm schema={schema} doc={{}} setValue={vi.fn()} />,
|
|
89
93
|
);
|
|
90
94
|
const card = container.querySelector("[data-mpo-schema-form-card]") as HTMLElement;
|
|
@@ -92,8 +96,16 @@ describe("SchemaForm design-law (MPO-60 / R10)", () => {
|
|
|
92
96
|
expect(card.getAttribute("data-mpo-schema-form-borderless")).toBeNull();
|
|
93
97
|
const style = card.getAttribute("style") ?? "";
|
|
94
98
|
expect(style).not.toMatch(/border-width:\s*1px/i);
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
expect(getComputedStyle(card).borderWidth).toBe(`${hairlineBorderWidth}px`);
|
|
100
|
+
unmount();
|
|
101
|
+
|
|
102
|
+
const flat = renderWithProviders(
|
|
103
|
+
<Preset overrides={{ borderWidth: "none" }}>
|
|
104
|
+
<SchemaForm schema={schema} doc={{}} setValue={vi.fn()} />
|
|
105
|
+
</Preset>,
|
|
106
|
+
);
|
|
107
|
+
const edgeless = flat.container.querySelector("[data-mpo-schema-form-card]") as HTMLElement;
|
|
108
|
+
expect(getComputedStyle(edgeless).borderWidth).toBe("0px");
|
|
97
109
|
});
|
|
98
110
|
|
|
99
111
|
it("hideBorder is SF-TRANSPARENT (borderless marker, no card fill)", () => {
|