mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 03:22:32 -05:00
tests/playwright/specs/viewport-matrix.e2e.js opens WeKan as an iPhone 15, an unfolded Galaxy Z Fold 7, an iPad Pro 11, a 1366x768 laptop and a 55" 4K touch panel, each upright and on its side, and checks the board header, board menu -> Original positions, Problems -> Memory Usage and Disk usage, the Table, Calendar Mode, Time, Timeline and Map views, and dragscroll by mouse or by finger. It measures that icons and texts are on screen and not drawn in the colour behind them, that the page never scrolls sideways, that every column can be reached and stays readable; screenshots of each step go to .tools/log/viewports/. What it found, fixed: - iPhone: the header bar was width: 100% plus its side padding as a content box, 24px wider than the screen, and the sidebar hamburger sat past the right edge. It measures its padding inside now. - Admin Panel left menu on a phone: the list's disc bullets showed as a dot after every entry, and a rule between groups shrank to a dash beside an entry. No bullets, and each group starts a row of its own. - Admin tables: 13 equal columns were 25px each on a phone and 42px on an upright iPad, every header one letter per line. Up to 1024px a column keeps a readable floor and the table scrolls sideways in its wrapper. - Table view: the title-wrap and swimlane-group toggles were white icons on white - two empty squares - stretched to 72px by the global button padding. And on an upright iPad or foldable, Members, Labels and the date columns lay past the edge of an overflow-hidden board with nothing to scroll; the view scrolls sideways itself now. A swipe that starts beside a list's resize handle resizes the list, as it should, so the dragscroll check starts where a person pans. The file runs in Chromium only: device emulation and the touch swipe exist only there. Thanks to xet7 !
430 lines
15 KiB
CSS
430 lines
15 KiB
CSS
/* The ONE table page layout. Design: docs/Features/General/Table-Page.md
|
|
Used by every paginated admin table (Security, Speed, Tests, CPU usage, Files,
|
|
Rules, Boards, Cards, Impersonation, Recovery). Defined once here so a fix to
|
|
the layout reaches every page instead of one report at a time. */
|
|
|
|
/* Rows, top to bottom: title, status, controls, table. Plain block order - no
|
|
flex `order` anywhere, so the DOM order in tablePage.jade IS the visual order,
|
|
in both LTR and RTL. */
|
|
.table-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
/* min-width:0 lets this shrink inside the flex .main-body; without it a wide
|
|
table sets a min-content floor and pushes the panel's right edge past the
|
|
window, which is exactly the "table runs off-screen" report. */
|
|
min-width: 0;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.table-page-title {
|
|
margin: 0;
|
|
}
|
|
|
|
.table-page-desc {
|
|
margin: 0;
|
|
}
|
|
|
|
/* Search on the start side, pagination pushed to the end. margin-inline-start
|
|
mirrors automatically in RTL. Wraps to a second line rather than overflowing
|
|
when the panel is narrow. */
|
|
.table-page-controls {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.table-page-controls input.js-table-page-search {
|
|
flex: 0 1 320px;
|
|
min-width: 0;
|
|
max-width: 100%;
|
|
padding: 0 8px;
|
|
height: 34px;
|
|
}
|
|
|
|
/* ONE height and NO margins for everything in the controls row, so the search
|
|
field, the action buttons and the prev/next buttons share the same top and
|
|
bottom edge instead of each floating at its own y.
|
|
|
|
The global `button` rule in forms.css is `display: block; margin-bottom: 14px;
|
|
min-height: 41px` - written for stacked form buttons. In this row that margin is
|
|
part of the flex item's box, so a button carrying it is centred HIGHER than one
|
|
without: the pager buttons kept the 14px and the action buttons did not, which is
|
|
exactly the "Teams is higher, Unlock all is lower" misalignment. Height and
|
|
margin belong to the layout, so they are set here, once, for every control in the
|
|
row; the colours stay in paginationControls.css.
|
|
|
|
The pager selector is spelled out because paginationControls.css sizes
|
|
`.table-page-pagination button` at the same specificity as a plain
|
|
`.table-page-controls button` - whichever file happened to load last would win,
|
|
which is not something a layout rule may depend on. */
|
|
.table-page-controls button,
|
|
.table-page-controls .table-page-pagination button,
|
|
.table-page-controls button.js-table-page-action {
|
|
margin: 0;
|
|
height: 34px;
|
|
min-height: 34px;
|
|
}
|
|
|
|
/* Layout only. The COLOURS of the prev/next buttons and of the "3 / 42" counter
|
|
come from the one shared pager stylesheet, client/components/main/
|
|
paginationControls.css, which every pager in WeKan uses: they follow the
|
|
per-user theme accent (Member Settings -> Change color sets --theme-accent on
|
|
:root) and fall back to the WeKan default blue when no custom theme is chosen.
|
|
Do NOT restate the button colours here. That stylesheet spells out :hover,
|
|
:focus, :active and :active:hover on purpose, because the global button rules
|
|
in forms.css set those states from --theme-accent with a BLACK/grey fallback at
|
|
equal or higher specificity - a partial copy loses the moment the button is
|
|
clicked and leaves a black button sitting on the page. */
|
|
.table-page-pagination {
|
|
margin-inline-start: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
/* Filter dropdowns, action buttons and the total: the controls-row features that
|
|
came from Admin Panel / People. Same height as the search field so the row
|
|
reads as one strip, and all of them shrink before the row wraps. */
|
|
.table-page-controls select.js-table-page-filter {
|
|
height: 34px;
|
|
min-width: 0;
|
|
max-width: 100%;
|
|
padding: 0 6px;
|
|
}
|
|
|
|
/* The icon centred beside the label, on one line. Height and margin come from the
|
|
shared control rule above, so an action button lines up with the search field and
|
|
with the prev/next pair. (People's "Unlock all users" used to bring its own 20px
|
|
top margin and 28px height from the old page header, and sat lower and shorter
|
|
than "Teams" next to it.) Layout only; the colours are in paginationControls.css
|
|
with the pager. */
|
|
.table-page-controls button.js-table-page-action {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
padding: 0 12px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The total sits with the controls, not in the table: it counts the whole result
|
|
set, not the page. Inherits its colour so it stays readable on a dark theme. */
|
|
.table-page-total {
|
|
color: inherit;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Last-resort sideways scroll for the table only - never the whole page. */
|
|
.table-page-table-wrap {
|
|
max-width: 100%;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
/* Full width, equal columns, wrapping text - at EVERY window width.
|
|
`table-layout: fixed` is what makes the columns an equal share of the width AND
|
|
what makes width:100% binding: with the default `auto` layout the browser sizes
|
|
columns to their content, so one long id or file name widened the table past
|
|
the panel and the right-hand columns ended up outside the window - and a table
|
|
with two short columns shrank to a third of the panel instead of filling it.
|
|
|
|
`!important` on the three properties that ARE this design: they have twice been
|
|
overridden from outside by app-wide `table { width: max-content !important;
|
|
min-width: 1200px !important }` rules that a page stylesheet declared with no
|
|
page in the selector. Those are fixed, but an unscoped `table` rule anywhere in
|
|
the app must never be able to decide the width of a table page again - a rule
|
|
that a stylesheet in another folder can silently break is not a guarantee. */
|
|
.table-page-table {
|
|
width: 100% !important;
|
|
max-width: 100% !important;
|
|
table-layout: fixed !important;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
/* Cells wrap and carry no minimum of their own - `white-space: nowrap` or a
|
|
`min-width` on a cell is the other way a table grows past its panel, and admin
|
|
stylesheets used to set both on every `table td` in the app.
|
|
|
|
The wrapping is `!important` for the same reason the width above is: it IS the
|
|
design, and it was being switched off from outside by app-wide `table td`
|
|
rules. A cut-off id or a value that runs under the next column is the same
|
|
report as a table running off the right edge - and it is the wrapping, not the
|
|
width, that fixes it once a column is narrow. */
|
|
.table-page-table th,
|
|
.table-page-table td {
|
|
text-align: start;
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
vertical-align: top;
|
|
min-width: 0 !important;
|
|
/* Long unbroken text (ids, URLs, file names, stack details) must wrap inside
|
|
its cell instead of widening the column. `anywhere` breaks a long word that
|
|
has no space in it; `break-word` is the older spelling kept for Safari. */
|
|
white-space: normal !important;
|
|
overflow-wrap: anywhere !important;
|
|
word-break: break-word !important;
|
|
}
|
|
|
|
.table-page-table th {
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Opt-in per column from the column spec. */
|
|
.table-page-table .table-page-end {
|
|
text-align: end;
|
|
}
|
|
|
|
/* Legacy column specs mark dates and addresses with nowrap. They still wrap on
|
|
narrow tables: every displayed address and long line must remain readable. */
|
|
.table-page-table .table-page-nowrap {
|
|
white-space: normal !important;
|
|
overflow-wrap: anywhere !important;
|
|
word-break: break-word !important;
|
|
}
|
|
|
|
/* Severity, coloured from the cell's own data attribute so the rule lives with
|
|
the table rather than in each report's stylesheet. */
|
|
.table-page-table td[data-sev='high'],
|
|
.table-page-table td[data-sev='critical'] {
|
|
color: #c0392b;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.table-page-empty {
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.table-page-status-icons {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.table-page-status-done {
|
|
color: #2e7d32;
|
|
}
|
|
.table-page-status-failed {
|
|
color: #c62828;
|
|
}
|
|
.table-page-status-deleted {
|
|
color: #d6a100;
|
|
}
|
|
|
|
/* Narrow windows: the side menu goes full width on top and the table sits BELOW
|
|
it, instead of the two being squeezed side by side. Above this width the menu
|
|
stays on the start side with the table next to it.
|
|
|
|
This deliberately overrides the older "always side by side, even on narrow
|
|
windows" rule in settingBody.css: keeping both on one row left the table a few
|
|
dozen pixels wide on a phone, with its right-hand columns unreachable. 800px is
|
|
the same phone breakpoint the rest of WeKan uses. */
|
|
@media screen and (max-width: 800px) {
|
|
.setting-content .content-body {
|
|
flex-wrap: wrap;
|
|
}
|
|
.setting-content .content-body .side-menu {
|
|
width: 100%;
|
|
flex: 1 1 100%;
|
|
}
|
|
.setting-content .content-body .main-body {
|
|
flex: 1 1 100%;
|
|
/* The table wraps now, so the panel itself never needs to scroll sideways. */
|
|
overflow-x: auto !important;
|
|
}
|
|
}
|
|
|
|
/* Equal columns stop working when there are more of them than the window has
|
|
room for: the 13 columns of an event report (Problems / Memory Usage, Disk
|
|
usage, Security, ...) were about 25px each on a 393px iPhone, and every header
|
|
broke one letter per line - and 42px on an iPad held upright, where the menu
|
|
still sits beside the table. Up to 1024px a column keeps a readable floor, and
|
|
a table wider than the window scrolls sideways inside its own wrapper - the
|
|
last resort that wrapper exists for - while the page still does not. A table
|
|
with few columns is unaffected: the floor is below its share of the width.
|
|
tests/playwright/specs/viewport-matrix.e2e.js checks it at every size. */
|
|
@media screen and (max-width: 1024px) {
|
|
.table-page-table {
|
|
min-width: calc(var(--table-page-columns, 1) * 6.5em) !important;
|
|
}
|
|
}
|
|
|
|
/* ── Cells that are not text ───────────────────────────────────────────────
|
|
A table page's rows carry three things that are not words: the people who
|
|
share an office, the country a location is in, and the location itself as a
|
|
link to a map. All three exist to keep a row SHORT - these tables are wide,
|
|
and a column of full names or coordinates is what made them so. */
|
|
|
|
/* People, as initials or avatars, several to a cell. Wrapping rather than
|
|
overflowing: an office with thirty accounts is a taller row, not a row with
|
|
twenty-two of them off the side of the screen. */
|
|
.table-page-people {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
/* The avatar box is the BOARD SIDEBAR'S, `.member` from userAvatar.css - 24px,
|
|
round, the image cropped to fill it and the initials centred in it. Reused
|
|
rather than copied: every avatar rule in that file is scoped to `.member`, so
|
|
a cell without the class drew the image at its natural size (a 300px
|
|
photograph in a table row, with the login counts scattered around it), and a
|
|
fourth private copy of "how big is an avatar" is how the three that already
|
|
exist came to disagree.
|
|
|
|
Two of its board-specific declarations do not belong in a table: it floats,
|
|
and it carries a 3px margin for the sidebar's grid. */
|
|
.table-page .member,
|
|
.table-page-people .member {
|
|
float: none;
|
|
margin: 0;
|
|
flex: 0 0 auto;
|
|
}
|
|
/* One person: avatar, visible identity and login count, side by side. Offices
|
|
are grouped by person now, so a tooltip-only identity would hide the name of
|
|
the group from keyboard and touch users. */
|
|
.table-page-person {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
.table-page-person-name {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.table-page-people .edit-user {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
/* A one-user column must show the identity as text as well as an avatar. Keep
|
|
the avatar fixed and let a long username wrap in the rest of the cell. */
|
|
.table-page-single-person {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 4px;
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
.table-page-single-person .member {
|
|
flex: 0 0 24px;
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
.table-page-single-person-name {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
white-space: normal !important;
|
|
overflow-wrap: anywhere;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.table-page-attachment {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-width: 0;
|
|
}
|
|
/* The preview is a picture frame, not an action: a neutral ground in every
|
|
state, like the card's attachment tile (attachments.css). forms.css fills
|
|
every <button> with the accent, so each state is reset here. */
|
|
.table-page button.table-page-attachment-preview,
|
|
.table-page button.table-page-attachment-preview:hover,
|
|
.table-page button.table-page-attachment-preview:focus,
|
|
.table-page button.table-page-attachment-preview:active,
|
|
.table-page button.table-page-attachment-preview:active:hover,
|
|
.table-page button.table-page-attachment-preview:active:focus {
|
|
width: 56px;
|
|
height: 44px;
|
|
flex: 0 1 56px;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
max-width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
border: 1px solid #d0d7de;
|
|
border-radius: 3px;
|
|
background: #f4f5f7;
|
|
color: inherit;
|
|
font-weight: 400;
|
|
line-height: 0;
|
|
box-shadow: none;
|
|
filter: none;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
}
|
|
.table-page button.table-page-attachment-preview:focus-visible {
|
|
outline: 2px solid #01628c;
|
|
outline-offset: 2px;
|
|
}
|
|
.table-page-attachment-thumbnail {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
object-fit: contain;
|
|
}
|
|
.table-page-attachment-extension {
|
|
display: flex;
|
|
width: 100%;
|
|
height: 100%;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow-wrap: anywhere;
|
|
text-transform: uppercase;
|
|
}
|
|
.table-page-attachment-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
max-width: 100%;
|
|
gap: 4px;
|
|
}
|
|
.table-page-attachment-actions button,
|
|
.table-page-attachment-actions a {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 28px;
|
|
min-height: 28px;
|
|
margin: 0;
|
|
}
|
|
/* How many times that person logged in from here. Small and quiet: it is the
|
|
second question, after who. */
|
|
.table-page-people-count {
|
|
font-size: 0.75em;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* The country's flag, at the start of a location cell. Sized like text rather
|
|
than like an icon - it is read as part of "🇬🇧 London", one phrase. */
|
|
.table-page-flag {
|
|
margin-inline-end: 4px;
|
|
font-size: 1.1em;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* A location cell leads to the map popup, so it looks like what it is. */
|
|
a.js-table-page-map {
|
|
text-decoration: none;
|
|
}
|
|
a.js-table-page-map:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* The popup itself: a place, its coordinates, the link, the chooser. */
|
|
.table-page-map-popup .table-page-map-label {
|
|
font-weight: bold;
|
|
margin-bottom: 2px;
|
|
}
|
|
.table-page-map-popup .table-page-map-coordinates {
|
|
font-size: 0.85em;
|
|
margin-bottom: 8px;
|
|
}
|
|
.table-page-map-popup .js-open-map {
|
|
display: inline-block;
|
|
margin-bottom: 10px;
|
|
}
|