Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ test.describe.serial('Theme "eForm new design" (workspace variant)', () => {
});
const siteRowFirstCell = () => siteRow().locator('td.mat-mdc-cell').first();
const siteRowActionBtn = () => siteRow().locator('[id^="action-items-"] #actionMenu');
// The Device Users row of the user created in beforeAll.
const deviceUserRow = () => page.locator('tbody > tr', { hasText: firstName });

// Appends stand-in markup to the content card while `assertions` run, then
// removes it, so fixtures never leak into later tests.
const withContentCardFixture = async (html: string, assertions: () => Promise<void>) => {
await page.evaluate((markup) => {
const template = document.createElement('template');
template.innerHTML = markup;
Array.from(template.content.children).forEach((el) => el.setAttribute('data-e2e-fixture', ''));
document.querySelector('.content-card')?.append(template.content);
}, html);
try {
await assertions();
} finally {
await page.evaluate(() => document.querySelectorAll('[data-e2e-fixture]').forEach((el) => el.remove()));
}
};
// A hand-written one-row table (no .mdc-data-table__row), like the legacy grids.
const matTable = (cells: string) =>
`<table class="mat-mdc-table"><tbody><tr class="mat-mdc-row">${cells}</tr></tbody></table>`;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
Expand All @@ -66,7 +87,7 @@ test.describe.serial('Theme "eForm new design" (workspace variant)', () => {
await deviceUsersPage.newDeviceUserBtn().waitFor({ state: 'visible', timeout: 40000 });
await deviceUsersPage.createNewDeviceUser(firstName, lastName);
deviceUserCreated = true;
await expect(page.locator('tbody > tr', { hasText: firstName })).toBeVisible({ timeout: 40000 });
await expect(deviceUserRow()).toBeVisible({ timeout: 40000 });
await myEformsPage.Navbar.goToSites();
await siteRow().waitFor({ state: 'visible', timeout: 40000 });
});
Expand All @@ -87,8 +108,7 @@ test.describe.serial('Theme "eForm new design" (workspace variant)', () => {
await deviceUsersPage.newDeviceUserBtn().waitFor({ state: 'visible', timeout: 40000 });
// Let the list load before looking the user up; if its creation never
// completed there is no row, and the lookup below finds nothing to delete.
await page
.locator('tbody > tr', { hasText: firstName })
await deviceUserRow()
.waitFor({ state: 'visible', timeout: 40000 })
.catch(() => undefined);
const deviceUser = await deviceUsersPage.getDeviceUserByName(firstName);
Expand Down Expand Up @@ -132,7 +152,33 @@ test.describe.serial('Theme "eForm new design" (workspace variant)', () => {
await expect(td).toHaveCSS('padding-left', '16px');
await expect(td).toHaveCSS('vertical-align', 'middle');

// The frame's bottom edge is the only line under the last row.
const lastRowFirstCell = grid().locator('tbody > tr').last().locator('td').first();
await expect(lastRowFirstCell).toHaveCSS('border-bottom-style', 'none');

await expect(grid()).toHaveCSS('border-top-left-radius', '8px');
// width: 100% + the 1px frame must not overflow the container.
await expect(grid()).toHaveCSS('box-sizing', 'border-box');

// The right-pinned actions header keeps the --bg header tint (#f8fafd),
// even against a plugin-style `:host ::ng-deep … th` paint (0,3,1).
const pinnedActionsHeader = grid().locator('thead tr th').last();
await expect(pinnedActionsHeader).toHaveClass(/\bmat-table-sticky-right\b/);
await expect(pinnedActionsHeader).toHaveCSS('background-color', 'rgb(248, 250, 253)');
const pluginPaintSelector = '.mtx-grid .mat-mdc-header-row th.mat-table-sticky-right';
await page.evaluate((selector) => {
const style = document.createElement('style');
style.id = 'e2e-plugin-pinned-paint';
style.textContent = `${selector} { background: rgb(255, 0, 0) !important; }`;
document.head.appendChild(style);
}, pluginPaintSelector);
try {
// Positive control: the injected paint really targets this cell.
expect(await pinnedActionsHeader.evaluate((el, s) => el.matches(s), pluginPaintSelector)).toBe(true);
await expect(pinnedActionsHeader).toHaveCSS('background-color', 'rgb(248, 250, 253)');
} finally {
await page.evaluate(() => document.getElementById('e2e-plugin-pinned-paint')?.remove());
}
});

test('new design lays out the content card and sub-header', async () => {
Expand All @@ -142,6 +188,51 @@ test.describe.serial('Theme "eForm new design" (workspace variant)', () => {
await expect(page.locator('app-sites .eform-sub-header')).toHaveCSS('padding-left', '0px');
});

test('new design keeps the content card flush around full-bleed views', async () => {
const contentCard = page.locator('.content-card').first();
// Stand-in for the backend-configuration calendar's root.
await withContentCardFixture('<div class="calendar-shell"></div>', async () => {
await expect(contentCard).toHaveCSS('padding-left', '0px');
await expect(contentCard).toHaveCSS('padding-top', '0px');
});
await expect(contentCard).toHaveCSS('padding-left', '24px');
});

test('new design keeps time-planning grid dividers off the frame edges', async () => {
// Stand-in for the time-planning week grid: day cells carry the status
// classes that styles.scss gives !important right/bottom borders.
const dayCell = '<td class="mat-mdc-cell white-background">day</td>';
await withContentCardFixture(
`<div class="mtx-grid time-dashboard" id="e2e-time-dashboard">${matTable(dayCell + dayCell)}</div>`,
async () => {
const cells = page.locator('#e2e-time-dashboard td');
// Day divider between cells, in the row-separator colour.
await expect(cells.first()).toHaveCSS('border-right-style', 'solid');
await expect(cells.first()).toHaveCSS('border-right-color', 'rgb(225, 231, 239)');
// None against the frame's right and bottom edges.
await expect(cells.last()).toHaveCSS('border-right-style', 'none');
await expect(cells.last()).toHaveCSS('border-bottom-style', 'none');
}
);
});

test('new design drops mtx dividers after left-pinned columns except on the time-planning grid', async () => {
// Stand-ins for a grid with left-pinned columns (compliance report,
// working hours) and the time-planning week grid. mtx-grid's global
// styles are loaded by the Sites grid on this page.
const pinnedCells = matTable(
'<td class="mat-mdc-cell mat-table-sticky-left">a</td><td class="mat-mdc-cell">b</td>'
);
await withContentCardFixture(
`<div class="mtx-grid" id="e2e-pinned-left">${pinnedCells}</div>` +
`<div class="mtx-grid time-dashboard" id="e2e-pinned-left-tp">${pinnedCells}</div>`,
async () => {
await expect(page.locator('#e2e-pinned-left td').first()).toHaveCSS('border-right-style', 'none');
await expect(page.locator('#e2e-pinned-left-tp td').first()).toHaveCSS('border-right-style', 'solid');
}
);
});

test('new design uses a compact row action button and keeps the row lit while its menu is open', async () => {
const actionBtn = siteRowActionBtn();
// The row paints the background (cells are transparent); the pinned
Expand Down
80 changes: 70 additions & 10 deletions eform-client/src/scss/components/_workspace-mat-overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ body.theme-workspace {
// - Body: 12px/16px cells, content-height rows, surface-variant separators,
// no separator under the last row
// - Row hover --ws-table-row-hover; a row stays lit while its menu is open
// - Row-action icon buttons shrink to 32px inside cells
// - Icon buttons inside body cells shrink to 32px

// --table-row-hover equals --md-surface-container in dark (#1e1f20), so a
// hover on it would be invisible; dark mode gets a 4% on-surface lift.
Expand All @@ -961,11 +961,14 @@ body.theme-workspace {
// _table.scss and styles.scss draw the host frame from these hooks with
// !important; re-point the hooks instead of out-shouting those rules.
--mtx-grid-border: 1px solid var(--md-surface-variant);
--mtx-grid-radius: 8px;
--mtx-grid-radius: var(--radius-lg, 8px);
// mtx-grid's own [rowHover] rule out-specifies ours; feed it our colour.
--mtx-grid-table-row-hover-background-color: var(--ws-table-row-hover);
display: flex;
flex-direction: column;
// mtx sets the host `width: 100%`; content-box would add the frame's
// 2px on top, and an `overflow: hidden` parent clips the right edge.
box-sizing: border-box;
background: var(--md-surface-container);
overflow: hidden;

Expand All @@ -976,6 +979,15 @@ body.theme-workspace {
}
}

// The grid's scroller gets a thin themed scrollbar. Setting scrollbar-color
// makes Chrome 121+ skip the global black-track ::-webkit-scrollbar
// (styles.scss). scrollbar-color inherits (scrollbar-width doesn't), so
// scroll areas inside cells get the themed colours at default width.
.mtx-grid .mat-table-container {
scrollbar-width: thin;
scrollbar-color: var(--state-scrollbar-thumb) transparent;
}

// Content-height rows. Set on the rows rather than via Material's
// --mat-table-*-container-height tokens: mtx-grid also does arithmetic
// with the row token (expansion-row padding), which `auto` would break.
Expand All @@ -997,7 +1009,7 @@ body.theme-workspace {
// sticky header still sticks to the real scroller.
.mat-mdc-table:not(.mtx-grid .mat-mdc-table) {
border: 1px solid var(--md-surface-variant);
border-radius: 8px;
border-radius: var(--radius-lg, 8px);
overflow: clip;
}

Expand All @@ -1013,6 +1025,13 @@ body.theme-workspace {
border-bottom-color: var(--md-surface-variant) !important;
}

// Material drops the last row's separator only on `.mdc-data-table__row`;
// hand-written mat-mdc-table markup (property-areas-edit modal) lacks that
// class. The !important rules above set the colour only, so `none` wins.
.mat-mdc-table tbody > tr:last-child > .mat-mdc-cell {
border-bottom: none;
}

// The divider under the header is the header cells' own bottom border,
// so it moves with them while sticky.
.mat-mdc-header-row {
Expand Down Expand Up @@ -1078,6 +1097,34 @@ body.theme-workspace {
text-transform: none;
}

// Time-planning planning grid. Its ViewEncapsulation.None component styles
// box every header cell (`.time-dashboard table tr th`, 0,1,3) and draw
// --tp-border day dividers; styles.scss and mtx-grid's pinned-column rule
// recolour them. Header: tint + the bottom line only. Body: keep the day
// dividers in the row-separator colour, none against the frame's edges.
// !important where styles.scss's unscoped rules use it: the border colour,
// and the day cells' `.white-/.grey-/.green-/.red-background` classes, which
// force border-right and border-bottom.
.mtx-grid.time-dashboard {
.mat-mdc-header-row .mat-mdc-header-cell {
border-top: none;
border-left: none;
border-right: none;
}

.mat-mdc-cell {
border-right-color: var(--md-surface-variant) !important;
}

.mat-mdc-cell:last-child {
border-right: none !important;
}

tbody > tr:last-child > .mat-mdc-cell {
border-bottom: none !important;
}
}

// time-planning break-policy rules: the table now draws its own frame, so
// drop the wrapper's to avoid a double border. !important: the component's
// emulated-encapsulation rule (an _ngcontent attribute on both parts,
Expand All @@ -1091,10 +1138,15 @@ body.theme-workspace {
// cells. Header cells take the header tint; body cells inherit the row's
// background, so hover, menu-open, selected and status-coloured rows all
// carry across without per-state rules. !important beats styles.scss's
// unscoped `.mtx-grid .mat-table-sticky-left` background.
th.mat-table-sticky-left,
th.mat-table-sticky-right,
th.mat-mdc-table-sticky-border-elem-right {
// unscoped `.mtx-grid .mat-table-sticky-left` background. The header
// selector is row-qualified (0,4,2) so a plugin's `:host ::ng-deep … th`
// paint (0,3,1) can't un-tint it; the body rule stays low on purpose —
// plugin row states like `tr.highlighted > td` rely on beating it.
.mat-mdc-table .mat-mdc-header-row > th:is(
.mat-table-sticky-left,
.mat-table-sticky-right,
.mat-mdc-table-sticky-border-elem-right
) {
background-color: var(--bg) !important;
}

Expand All @@ -1104,6 +1156,13 @@ body.theme-workspace {
background-color: inherit !important;
}

// mtx draws a divider after every left-pinned column; the new design has
// no vertical dividers. The time-planning week grid keeps its pale one
// (recoloured in the .time-dashboard block above).
.mtx-grid:not(.time-dashboard) .mat-table-sticky-left {
border-right: none;
}

// Right-pinned actions column: drop the legacy left border + shadow.
th.mat-table-sticky-right,
td.mat-table-sticky-right,
Expand All @@ -1113,9 +1172,10 @@ body.theme-workspace {
box-shadow: none !important;
}

// Row-action icon buttons inside body cells (mat and native tables):
// compact 32px target with a 20px glyph. Icon buttons elsewhere keep the
// 42px default above, and the no-halo tokens carry over from it.
// Every icon button inside a body cell (mat and native tables), not only
// row actions: compact 32px target with a 20px glyph. Icon buttons
// elsewhere keep the 42px default above, and the no-halo tokens carry over
// from it.
// .small-icon-btn (shared-tags) sizes itself to 28px; leave it alone.
td .mat-mdc-icon-button:not(.small-icon-btn),
.mat-mdc-cell .mat-mdc-icon-button:not(.small-icon-btn) {
Expand Down
39 changes: 26 additions & 13 deletions eform-client/src/scss/components/_workspace-patterns.scss
Original file line number Diff line number Diff line change
Expand Up @@ -479,8 +479,9 @@ body.theme-workspace {
// from the card edges. border-box keeps the padding inside the card's
// `min-height: calc(100% - 2 * margin)` (full-layout.component.scss);
// content-box would make short pages 32px taller than the drawer content
// and scroll it. Simple-layout routes (auth, landing, connection-string)
// render no .content-card and are unaffected.
// and scroll it. Every rule here is scoped to the content card, so
// simple-layout routes (auth, landing, connection-setup), which render
// none, keep their own padding.
// ---------------------------------------------------------------------------

mat-drawer-container .content-card {
Expand Down Expand Up @@ -514,7 +515,9 @@ body.theme-workspace {
// card. The card now supplies the gutter, so drop the horizontal part —
// keeping the 16px top/bottom and the mb-3 — and the title lines up with the
// table's left edge. !important is needed to beat the utility's own.
.eform-sub-header {
// The simple-layout connection-setup page (div.p-3 > eform-new-subheader)
// has no card gutter, so it keeps the inset.
mat-drawer-container .content-card .eform-sub-header {
padding-left: 0 !important;
padding-right: 0 !important;
}
Expand All @@ -526,10 +529,10 @@ body.theme-workspace {
// Limited to div/mat-card wrappers so a stray px-3 button keeps its padding.
// Hits: navigation-menu-page, admin-settings, items-planning
// plannings-header, backend-configuration statistics + task-tracker.
eform-new-subheader ~ :is(div, mat-card).px-3,
eform-new-subheader ~ :not(.mat-mdc-card) > :is(div, mat-card).px-3,
.eform-sub-header ~ :is(div, mat-card).px-3,
.eform-sub-header ~ :not(.mat-mdc-card) > :is(div, mat-card).px-3 {
mat-drawer-container .content-card eform-new-subheader ~ :is(div, mat-card).px-3,
mat-drawer-container .content-card eform-new-subheader ~ :not(.mat-mdc-card) > :is(div, mat-card).px-3,
mat-drawer-container .content-card .eform-sub-header ~ :is(div, mat-card).px-3,
mat-drawer-container .content-card .eform-sub-header ~ :not(.mat-mdc-card) > :is(div, mat-card).px-3 {
padding-left: 0 !important;
padding-right: 0 !important;
}
Expand All @@ -540,16 +543,26 @@ body.theme-workspace {
// top/bottom spacing is part of those pages' own layout.
// kanban: board list, sprint panel, board settings, reports, statistics
// time-planning: pay rule sets
.content-card router-outlet + * > .board-list-container,
.content-card router-outlet + * > .sprint-panel,
.content-card router-outlet + * > .board-settings,
.content-card router-outlet + * > .reports-container,
.content-card router-outlet + * > .statistics-container,
.content-card router-outlet + * > .pay-rule-sets-container {
.content-card router-outlet + * > :is(
.board-list-container,
.sprint-panel,
.board-settings,
.reports-container,
.statistics-container,
.pay-rule-sets-container
) {
padding-left: 0;
padding-right: 0;
}

// backend-configuration case page: its root is `div.d-grid.p-4`, the only
// routed page root using the p-4 utility. !important beats the utility's
// own (padding: 1.5rem !important).
.content-card router-outlet + * > div.p-4 {
padding-left: 0 !important;
padding-right: 0 !important;
}

// ---------------------------------------------------------------------------
// 4. Filter panel — right-edge mirror of the drawer
// ---------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions eform-client/src/scss/components/_workspace-utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ body.theme-workspace {
// Native data tables mirror the mat-table look in
// _workspace-mat-overrides.scss (Tables): the DS hooks, Bootstrap
// `.table`, hand-written `table.mat-table` markup (legacy classes the
// .mat-mdc-* rules miss) and plugin `table.data`. Row-action icon
// .mat-mdc-* rules miss) and plugin `table.data`. Body-cell icon
// button sizing and the open-menu trigger colour live there too.
// Deliberately not matched: mat-datepicker's `.mat-calendar-table` and
// key/value layout tables such as `.summary-table`.
Expand All @@ -647,7 +647,7 @@ body.theme-workspace {
max-height: 220px;
overflow: auto;
border: 1px solid var(--md-surface-variant);
border-radius: 8px;
border-radius: var(--radius-lg, 8px);
background: var(--md-surface-container);
table {
margin: 0;
Expand All @@ -667,7 +667,7 @@ body.theme-workspace {
// already draws one. `clip` keeps the sticky header working.
&:not(.mtx-grid table):not(.table-scroll table) {
border: 1px solid var(--md-surface-variant);
border-radius: 8px;
border-radius: var(--radius-lg, 8px);
overflow: clip;
}

Expand Down
Loading