@proteinjs/db-ui 1.10.2 → 1.11.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/CHANGELOG.md +11 -0
- package/dist/src/pages/RecordTablePage.d.ts.map +1 -1
- package/dist/src/pages/RecordTablePage.js +6 -2
- package/dist/src/pages/RecordTablePage.js.map +1 -1
- package/dist/test/recordTablePageMobile.test.d.ts +5 -0
- package/dist/test/recordTablePageMobile.test.d.ts.map +1 -0
- package/dist/test/recordTablePageMobile.test.js +234 -0
- package/dist/test/recordTablePageMobile.test.js.map +1 -0
- package/package.json +4 -4
- package/src/pages/RecordTablePage.tsx +7 -3
- package/test/recordTablePageMobile.test.tsx +144 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.11.0](https://github.com/proteinjs/db/compare/@proteinjs/db-ui@1.10.2...@proteinjs/db-ui@1.11.0) (2026-08-19)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* record table page phone layout — full-width card with page gutters (task [#53](https://github.com/proteinjs/db/issues/53)) ([43061ff](https://github.com/proteinjs/db/commit/43061ff77d1677ff9294006a00da71c46659c281))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## [1.10.2](https://github.com/proteinjs/db/compare/@proteinjs/db-ui@1.10.1...@proteinjs/db-ui@1.10.2) (2026-08-19)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @proteinjs/db-ui
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordTablePage.d.ts","sourceRoot":"","sources":["../../../src/pages/RecordTablePage.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"RecordTablePage.d.ts","sourceRoot":"","sources":["../../../src/pages/RecordTablePage.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAqC,MAAM,eAAe,CAAC;AACxE,OAAO,EAAe,KAAK,EAAE,MAAM,eAAe,CAAC;AAInD,eAAO,MAAM,eAAe,EAAE,IAc7B,CAAC;AAEF,eAAO,MAAM,eAAe,UAAW,MAAM,GAAG,CAAC,WAEhD,CAAC;AAEF,eAAO,MAAM,qBAAqB,cAAe,MAAM,WAEtD,CAAC"}
|
|
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
28
|
exports.recordTableLinkByName = exports.recordTableLink = exports.recordTablePage = void 0;
|
|
29
29
|
var react_1 = __importDefault(require("react"));
|
|
30
|
+
var ui_1 = require("@proteinjs/ui");
|
|
30
31
|
var db_1 = require("@proteinjs/db");
|
|
31
32
|
var RecordTable_1 = require("../table/RecordTable");
|
|
32
33
|
var material_1 = require("@mui/material");
|
|
@@ -58,6 +59,9 @@ var recordTableLinkByName = function (tableName) {
|
|
|
58
59
|
exports.recordTableLinkByName = recordTableLinkByName;
|
|
59
60
|
var DynamicRecordTable = function (_a) {
|
|
60
61
|
var urlParams = _a.urlParams;
|
|
62
|
+
// Phone (MOBILE_SUPPORT §4.5): the floating fit-content card becomes a full-width card with
|
|
63
|
+
// page gutters — the table inside presents its phone card face (rows as stacked cards).
|
|
64
|
+
var isPhone = (0, ui_1.useFormFactor)().isPhone;
|
|
61
65
|
function Table() {
|
|
62
66
|
var tableName = urlParams['name'];
|
|
63
67
|
var table;
|
|
@@ -78,8 +82,8 @@ var DynamicRecordTable = function (_a) {
|
|
|
78
82
|
if (!table) {
|
|
79
83
|
return react_1.default.createElement(material_1.Typography, { sx: { p: 3, color: 'text.secondary' } }, errorMessage);
|
|
80
84
|
}
|
|
81
|
-
return (react_1.default.createElement(material_1.Box, { sx: { display: 'flex', flexGrow: 1, justifyContent: 'center', padding: 4 } },
|
|
82
|
-
react_1.default.createElement(material_1.Paper, { sx: { maxHeight: '80vh' } },
|
|
85
|
+
return (react_1.default.createElement(material_1.Box, { sx: { display: 'flex', flexGrow: 1, justifyContent: 'center', padding: isPhone ? 2 : 4, minWidth: 0 } },
|
|
86
|
+
react_1.default.createElement(material_1.Paper, { sx: __assign({ maxHeight: '80vh' }, (isPhone ? { width: '100%', minWidth: 0 } : {})) },
|
|
83
87
|
react_1.default.createElement(RecordTable_1.RecordTable, { table: table }))));
|
|
84
88
|
}
|
|
85
89
|
return react_1.default.createElement(Table, null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordTablePage.js","sourceRoot":"","sources":["../../../src/pages/RecordTablePage.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAA0B;
|
|
1
|
+
{"version":3,"file":"RecordTablePage.js","sourceRoot":"","sources":["../../../src/pages/RecordTablePage.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAA0B;AAC1B,oCAAwE;AACxE,oCAAmD;AACnD,oDAAmD;AACnD,0CAAuE;AAE1D,QAAA,eAAe,GAAS;IACnC,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE;QACJ,QAAQ,EAAE,IAAI;KACf;IACD,qFAAqF;IACrF,qEAAqE;IACrE,oBAAoB,EAAE,UAAC,KAAY;QACjC,OAAO;YACL,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO;SAClD,CAAC;IACJ,CAAC;IACD,SAAS,EAAE,UAAC,EAAY;YAAP,KAAK,cAAV,EAAY,CAAF;QAAO,OAAA,8BAAC,kBAAkB,eAAK,KAAK,EAAI,CAAA;KAAA;CAC/D,CAAC;AAEK,IAAM,eAAe,GAAG,UAAC,KAAiB;IAC/C,OAAO,WAAI,uBAAe,CAAC,IAAI,mBAAS,KAAK,CAAC,IAAI,CAAE,CAAC;AACvD,CAAC,CAAC;AAFW,QAAA,eAAe,mBAE1B;AAEK,IAAM,qBAAqB,GAAG,UAAC,SAAiB;IACrD,OAAO,WAAI,uBAAe,CAAC,IAAI,mBAAS,SAAS,CAAE,CAAC;AACtD,CAAC,CAAC;AAFW,QAAA,qBAAqB,yBAEhC;AAEF,IAAM,kBAAkB,GAAG,UAAC,EAAiC;QAA/B,SAAS,eAAA;IACrC,4FAA4F;IAC5F,wFAAwF;IAChF,IAAA,OAAO,GAAK,IAAA,kBAAa,GAAE,QAApB,CAAqB;IAEpC,SAAS,KAAK;QACZ,IAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC;QACV,IAAI,YAAY,CAAC;QACjB,IAAI,SAAS,EAAE;YACb,IAAI;gBACF,KAAK,GAAG,IAAA,gBAAW,EAAC,SAAS,CAAC,CAAC;aAChC;YAAC,OAAO,KAAK,EAAE;gBACd,YAAY,GAAG,sCAA+B,SAAS,CAAE,CAAC;aAC3D;SACF;aAAM;YACL,YAAY,GAAG,6CAA6C,CAAC;SAC9D;QAED,oFAAoF;QACpF,6EAA6E;QAC7E,IAAI,CAAC,KAAK,EAAE;YACV,OAAO,8BAAC,qBAAU,IAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAG,YAAY,CAAc,CAAC;SACvF;QAED,OAAO,CACL,8BAAC,cAAG,IAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;YACxG,8BAAC,gBAAK,IAAC,EAAE,aAAI,SAAS,EAAE,MAAM,IAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChF,8BAAC,yBAAW,IAAC,KAAK,EAAE,KAAK,GAAI,CACvB,CACJ,CACP,CAAC;IACJ,CAAC;IAED,OAAO,8BAAC,KAAK,OAAG,CAAC;AACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordTablePageMobile.test.d.ts","sourceRoot":"","sources":["../../test/recordTablePageMobile.test.tsx"],"names":[],"mappings":"AA+CA,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,wBAAwB,EAAE,OAAO,CAAC;CACvC"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __assign = (this && this.__assign) || function () {
|
|
18
|
+
__assign = Object.assign || function(t) {
|
|
19
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
20
|
+
s = arguments[i];
|
|
21
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
22
|
+
t[p] = s[p];
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
};
|
|
26
|
+
return __assign.apply(this, arguments);
|
|
27
|
+
};
|
|
28
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
29
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
30
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
31
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
32
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
33
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
34
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
38
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
39
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
40
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
41
|
+
function step(op) {
|
|
42
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
43
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
44
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
45
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
46
|
+
switch (op[0]) {
|
|
47
|
+
case 0: case 1: t = op; break;
|
|
48
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
49
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
50
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
51
|
+
default:
|
|
52
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
53
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
54
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
55
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
56
|
+
if (t[2]) _.ops.pop();
|
|
57
|
+
_.trys.pop(); continue;
|
|
58
|
+
}
|
|
59
|
+
op = body.call(thisArg, _);
|
|
60
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
61
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
65
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
66
|
+
};
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
/**
|
|
69
|
+
* @jest-environment jsdom
|
|
70
|
+
*
|
|
71
|
+
* RecordTablePage's phone layout (task #53: admin surfaces work on mobile). Below the phone
|
|
72
|
+
* line the page presents the table card full-width with page gutters (the desktop face floats
|
|
73
|
+
* a fit-content Paper in 32px padding — a fixed column grid wider than the screen). Also pins
|
|
74
|
+
* the end-to-end substrate integration: on a phone the page renders Table's card face.
|
|
75
|
+
*/
|
|
76
|
+
var react_1 = __importDefault(require("react"));
|
|
77
|
+
var client_1 = require("react-dom/client");
|
|
78
|
+
var test_utils_1 = require("react-dom/test-utils");
|
|
79
|
+
var react_router_dom_1 = require("react-router-dom");
|
|
80
|
+
var react_query_1 = require("react-query");
|
|
81
|
+
var db_1 = require("@proteinjs/db");
|
|
82
|
+
var UserTable = /** @class */ (function (_super) {
|
|
83
|
+
__extends(UserTable, _super);
|
|
84
|
+
function UserTable() {
|
|
85
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
86
|
+
_this.name = 'user';
|
|
87
|
+
_this.columns = (0, db_1.withRecordColumns)({
|
|
88
|
+
email: new db_1.StringColumn('email'),
|
|
89
|
+
});
|
|
90
|
+
return _this;
|
|
91
|
+
}
|
|
92
|
+
return UserTable;
|
|
93
|
+
}(db_1.Table));
|
|
94
|
+
var rows = [{ id: 'u-1', email: 'a@n3xa.io' }];
|
|
95
|
+
var mockDb = {
|
|
96
|
+
query: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
97
|
+
return [2 /*return*/, rows];
|
|
98
|
+
}); }); }),
|
|
99
|
+
getRowCount: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
100
|
+
return [2 /*return*/, rows.length];
|
|
101
|
+
}); }); }),
|
|
102
|
+
};
|
|
103
|
+
jest.mock('@proteinjs/db', function () { return (__assign(__assign({}, jest.requireActual('@proteinjs/db')), { getDb: function () { return mockDb; }, tableByName: function (name) {
|
|
104
|
+
if (name !== 'user') {
|
|
105
|
+
throw new Error("no such table: ".concat(name));
|
|
106
|
+
}
|
|
107
|
+
return new UserTable();
|
|
108
|
+
} })); });
|
|
109
|
+
// import AFTER the mock so the page module binds the mocked db seams
|
|
110
|
+
var RecordTablePage_1 = require("../src/pages/RecordTablePage");
|
|
111
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
112
|
+
var StubIntersectionObserver = /** @class */ (function () {
|
|
113
|
+
function StubIntersectionObserver() {
|
|
114
|
+
}
|
|
115
|
+
StubIntersectionObserver.prototype.observe = function () { };
|
|
116
|
+
StubIntersectionObserver.prototype.unobserve = function () { };
|
|
117
|
+
StubIntersectionObserver.prototype.disconnect = function () { };
|
|
118
|
+
return StubIntersectionObserver;
|
|
119
|
+
}());
|
|
120
|
+
globalThis.IntersectionObserver = StubIntersectionObserver;
|
|
121
|
+
var phoneMode = true;
|
|
122
|
+
beforeAll(function () {
|
|
123
|
+
window.matchMedia = function (query) { return ({
|
|
124
|
+
matches: phoneMode,
|
|
125
|
+
media: query,
|
|
126
|
+
onchange: null,
|
|
127
|
+
addListener: function () { return undefined; },
|
|
128
|
+
removeListener: function () { return undefined; },
|
|
129
|
+
addEventListener: function () { return undefined; },
|
|
130
|
+
removeEventListener: function () { return undefined; },
|
|
131
|
+
dispatchEvent: function () { return false; },
|
|
132
|
+
}); };
|
|
133
|
+
});
|
|
134
|
+
/** Emotion under jest injects rules via CSSOM; read the actual styles the classes apply. */
|
|
135
|
+
var cssFor = function (el) {
|
|
136
|
+
var classes = Array.from(el.classList).filter(function (cls) { return cls.startsWith('css-'); });
|
|
137
|
+
var out = [];
|
|
138
|
+
Array.from(document.querySelectorAll('style')).forEach(function (styleEl) {
|
|
139
|
+
var _a, _b;
|
|
140
|
+
var rules = (_b = (_a = styleEl.sheet) === null || _a === void 0 ? void 0 : _a.cssRules) !== null && _b !== void 0 ? _b : [];
|
|
141
|
+
Array.from(rules).forEach(function (rule) {
|
|
142
|
+
if (classes.some(function (cls) { return rule.cssText.includes(".".concat(cls)); })) {
|
|
143
|
+
out.push(rule.cssText);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
return out.join('\n');
|
|
148
|
+
};
|
|
149
|
+
describe('RecordTablePage phone layout', function () {
|
|
150
|
+
var container;
|
|
151
|
+
var root;
|
|
152
|
+
beforeEach(function () {
|
|
153
|
+
container = document.createElement('div');
|
|
154
|
+
document.body.appendChild(container);
|
|
155
|
+
root = (0, client_1.createRoot)(container);
|
|
156
|
+
});
|
|
157
|
+
afterEach(function () {
|
|
158
|
+
(0, test_utils_1.act)(function () {
|
|
159
|
+
root.unmount();
|
|
160
|
+
});
|
|
161
|
+
container.remove();
|
|
162
|
+
});
|
|
163
|
+
var mount = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
164
|
+
var client, Page;
|
|
165
|
+
return __generator(this, function (_a) {
|
|
166
|
+
switch (_a.label) {
|
|
167
|
+
case 0:
|
|
168
|
+
client = new react_query_1.QueryClient({ defaultOptions: { queries: { retry: false, cacheTime: 0 } } });
|
|
169
|
+
Page = RecordTablePage_1.recordTablePage.component;
|
|
170
|
+
return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
171
|
+
return __generator(this, function (_a) {
|
|
172
|
+
root.render(react_1.default.createElement(react_query_1.QueryClientProvider, { client: client },
|
|
173
|
+
react_1.default.createElement(react_router_dom_1.MemoryRouter, null,
|
|
174
|
+
react_1.default.createElement(Page, { urlParams: { name: 'user' } }))));
|
|
175
|
+
return [2 /*return*/];
|
|
176
|
+
});
|
|
177
|
+
}); })];
|
|
178
|
+
case 1:
|
|
179
|
+
_a.sent();
|
|
180
|
+
return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
181
|
+
return __generator(this, function (_a) {
|
|
182
|
+
switch (_a.label) {
|
|
183
|
+
case 0: return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
|
|
184
|
+
case 1:
|
|
185
|
+
_a.sent();
|
|
186
|
+
return [2 /*return*/];
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}); })];
|
|
190
|
+
case 2:
|
|
191
|
+
_a.sent();
|
|
192
|
+
return [2 /*return*/];
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}); };
|
|
196
|
+
it('phone: the table card spans the page (full-width Paper) and rows render as the card face', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
197
|
+
var paper;
|
|
198
|
+
return __generator(this, function (_a) {
|
|
199
|
+
switch (_a.label) {
|
|
200
|
+
case 0:
|
|
201
|
+
phoneMode = true;
|
|
202
|
+
return [4 /*yield*/, mount()];
|
|
203
|
+
case 1:
|
|
204
|
+
_a.sent();
|
|
205
|
+
paper = document.querySelector('.MuiPaper-root');
|
|
206
|
+
expect(paper).toBeTruthy();
|
|
207
|
+
expect(cssFor(paper)).toContain('width: 100%');
|
|
208
|
+
expect(document.querySelector('[data-table-phone-face]')).toBeTruthy();
|
|
209
|
+
expect(document.querySelector('table')).toBeNull();
|
|
210
|
+
expect(document.body.textContent).toContain('a@n3xa.io');
|
|
211
|
+
return [2 /*return*/];
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}); });
|
|
215
|
+
it('desktop: the floating fit-content card and the table face stay unchanged', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
216
|
+
var paper;
|
|
217
|
+
return __generator(this, function (_a) {
|
|
218
|
+
switch (_a.label) {
|
|
219
|
+
case 0:
|
|
220
|
+
phoneMode = false;
|
|
221
|
+
return [4 /*yield*/, mount()];
|
|
222
|
+
case 1:
|
|
223
|
+
_a.sent();
|
|
224
|
+
paper = document.querySelector('.MuiPaper-root');
|
|
225
|
+
expect(paper).toBeTruthy();
|
|
226
|
+
expect(cssFor(paper)).not.toContain('width: 100%');
|
|
227
|
+
expect(document.querySelector('table')).toBeTruthy();
|
|
228
|
+
expect(document.querySelector('[data-table-phone-face]')).toBeNull();
|
|
229
|
+
return [2 /*return*/];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}); });
|
|
233
|
+
});
|
|
234
|
+
//# sourceMappingURL=recordTablePageMobile.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordTablePageMobile.test.js","sourceRoot":"","sources":["../../test/recordTablePageMobile.test.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Db ui components",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"@emotion/styled": "11.11.0",
|
|
27
27
|
"@mui/icons-material": "5.14.11",
|
|
28
28
|
"@mui/material": "5.14.11",
|
|
29
|
-
"@proteinjs/db": "^1.34.
|
|
29
|
+
"@proteinjs/db": "^1.34.2",
|
|
30
30
|
"@proteinjs/reflection": "^1.1.12",
|
|
31
|
-
"@proteinjs/ui": "^4.
|
|
31
|
+
"@proteinjs/ui": "^4.10.0",
|
|
32
32
|
"@proteinjs/util": "^1.6.1",
|
|
33
33
|
"moment": "2.29.4",
|
|
34
34
|
"react": "18.2.0",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
},
|
|
56
56
|
"main": "./dist/generated/index.js",
|
|
57
57
|
"types": "./dist/generated/index.d.ts",
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "ead198c6ceceff2a37853c6211895b444575295c"
|
|
59
59
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Page, PageComponentProps } from '@proteinjs/ui';
|
|
2
|
+
import { Page, PageComponentProps, useFormFactor } from '@proteinjs/ui';
|
|
3
3
|
import { tableByName, Table } from '@proteinjs/db';
|
|
4
4
|
import { RecordTable } from '../table/RecordTable';
|
|
5
5
|
import { Box, Paper, SxProps, Theme, Typography } from '@mui/material';
|
|
@@ -29,6 +29,10 @@ export const recordTableLinkByName = (tableName: string) => {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const DynamicRecordTable = ({ urlParams }: PageComponentProps) => {
|
|
32
|
+
// Phone (MOBILE_SUPPORT §4.5): the floating fit-content card becomes a full-width card with
|
|
33
|
+
// page gutters — the table inside presents its phone card face (rows as stacked cards).
|
|
34
|
+
const { isPhone } = useFormFactor();
|
|
35
|
+
|
|
32
36
|
function Table() {
|
|
33
37
|
const tableName = urlParams['name'];
|
|
34
38
|
let table;
|
|
@@ -50,8 +54,8 @@ const DynamicRecordTable = ({ urlParams }: PageComponentProps) => {
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
return (
|
|
53
|
-
<Box sx={{ display: 'flex', flexGrow: 1, justifyContent: 'center', padding: 4 }}>
|
|
54
|
-
<Paper sx={{ maxHeight: '80vh' }}>
|
|
57
|
+
<Box sx={{ display: 'flex', flexGrow: 1, justifyContent: 'center', padding: isPhone ? 2 : 4, minWidth: 0 }}>
|
|
58
|
+
<Paper sx={{ maxHeight: '80vh', ...(isPhone ? { width: '100%', minWidth: 0 } : {}) }}>
|
|
55
59
|
<RecordTable table={table} />
|
|
56
60
|
</Paper>
|
|
57
61
|
</Box>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*
|
|
4
|
+
* RecordTablePage's phone layout (task #53: admin surfaces work on mobile). Below the phone
|
|
5
|
+
* line the page presents the table card full-width with page gutters (the desktop face floats
|
|
6
|
+
* a fit-content Paper in 32px padding — a fixed column grid wider than the screen). Also pins
|
|
7
|
+
* the end-to-end substrate integration: on a phone the page renders Table's card face.
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
11
|
+
import { act } from 'react-dom/test-utils';
|
|
12
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
13
|
+
import { QueryClient, QueryClientProvider } from 'react-query';
|
|
14
|
+
import { Record, StringColumn, Table, withRecordColumns } from '@proteinjs/db';
|
|
15
|
+
|
|
16
|
+
interface User extends Record {
|
|
17
|
+
email: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class UserTable extends Table<User> {
|
|
21
|
+
public name = 'user';
|
|
22
|
+
public columns = withRecordColumns<User>({
|
|
23
|
+
email: new StringColumn('email'),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const rows: User[] = [{ id: 'u-1', email: 'a@n3xa.io' } as User];
|
|
28
|
+
|
|
29
|
+
const mockDb = {
|
|
30
|
+
query: jest.fn(async () => rows),
|
|
31
|
+
getRowCount: jest.fn(async () => rows.length),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
jest.mock('@proteinjs/db', () => ({
|
|
35
|
+
...jest.requireActual('@proteinjs/db'),
|
|
36
|
+
getDb: () => mockDb,
|
|
37
|
+
tableByName: (name: string) => {
|
|
38
|
+
if (name !== 'user') {
|
|
39
|
+
throw new Error(`no such table: ${name}`);
|
|
40
|
+
}
|
|
41
|
+
return new UserTable();
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// import AFTER the mock so the page module binds the mocked db seams
|
|
46
|
+
import { recordTablePage } from '../src/pages/RecordTablePage';
|
|
47
|
+
|
|
48
|
+
declare global {
|
|
49
|
+
// eslint-disable-next-line no-var
|
|
50
|
+
var IS_REACT_ACT_ENVIRONMENT: boolean;
|
|
51
|
+
}
|
|
52
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
53
|
+
|
|
54
|
+
class StubIntersectionObserver {
|
|
55
|
+
observe() {}
|
|
56
|
+
unobserve() {}
|
|
57
|
+
disconnect() {}
|
|
58
|
+
}
|
|
59
|
+
(globalThis as any).IntersectionObserver = StubIntersectionObserver;
|
|
60
|
+
|
|
61
|
+
let phoneMode = true;
|
|
62
|
+
beforeAll(() => {
|
|
63
|
+
(window as any).matchMedia = (query: string) => ({
|
|
64
|
+
matches: phoneMode,
|
|
65
|
+
media: query,
|
|
66
|
+
onchange: null,
|
|
67
|
+
addListener: () => undefined,
|
|
68
|
+
removeListener: () => undefined,
|
|
69
|
+
addEventListener: () => undefined,
|
|
70
|
+
removeEventListener: () => undefined,
|
|
71
|
+
dispatchEvent: () => false,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** Emotion under jest injects rules via CSSOM; read the actual styles the classes apply. */
|
|
76
|
+
const cssFor = (el: Element): string => {
|
|
77
|
+
const classes = Array.from(el.classList).filter((cls) => cls.startsWith('css-'));
|
|
78
|
+
const out: string[] = [];
|
|
79
|
+
Array.from(document.querySelectorAll('style')).forEach((styleEl) => {
|
|
80
|
+
const rules = styleEl.sheet?.cssRules ?? ([] as unknown as CSSRuleList);
|
|
81
|
+
Array.from(rules).forEach((rule) => {
|
|
82
|
+
if (classes.some((cls) => rule.cssText.includes(`.${cls}`))) {
|
|
83
|
+
out.push(rule.cssText);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return out.join('\n');
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
describe('RecordTablePage phone layout', () => {
|
|
91
|
+
let container: HTMLDivElement;
|
|
92
|
+
let root: Root;
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
container = document.createElement('div');
|
|
96
|
+
document.body.appendChild(container);
|
|
97
|
+
root = createRoot(container);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
act(() => {
|
|
102
|
+
root.unmount();
|
|
103
|
+
});
|
|
104
|
+
container.remove();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const mount = async () => {
|
|
108
|
+
const client = new QueryClient({ defaultOptions: { queries: { retry: false, cacheTime: 0 } } });
|
|
109
|
+
const Page = recordTablePage.component as React.ComponentType<any>;
|
|
110
|
+
await act(async () => {
|
|
111
|
+
root.render(
|
|
112
|
+
<QueryClientProvider client={client}>
|
|
113
|
+
<MemoryRouter>
|
|
114
|
+
<Page urlParams={{ name: 'user' }} />
|
|
115
|
+
</MemoryRouter>
|
|
116
|
+
</QueryClientProvider>
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
await act(async () => {
|
|
120
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
it('phone: the table card spans the page (full-width Paper) and rows render as the card face', async () => {
|
|
125
|
+
phoneMode = true;
|
|
126
|
+
await mount();
|
|
127
|
+
const paper = document.querySelector('.MuiPaper-root') as HTMLElement;
|
|
128
|
+
expect(paper).toBeTruthy();
|
|
129
|
+
expect(cssFor(paper)).toContain('width: 100%');
|
|
130
|
+
expect(document.querySelector('[data-table-phone-face]')).toBeTruthy();
|
|
131
|
+
expect(document.querySelector('table')).toBeNull();
|
|
132
|
+
expect(document.body.textContent).toContain('a@n3xa.io');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('desktop: the floating fit-content card and the table face stay unchanged', async () => {
|
|
136
|
+
phoneMode = false;
|
|
137
|
+
await mount();
|
|
138
|
+
const paper = document.querySelector('.MuiPaper-root') as HTMLElement;
|
|
139
|
+
expect(paper).toBeTruthy();
|
|
140
|
+
expect(cssFor(paper)).not.toContain('width: 100%');
|
|
141
|
+
expect(document.querySelector('table')).toBeTruthy();
|
|
142
|
+
expect(document.querySelector('[data-table-phone-face]')).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
});
|