Files
Lauri Ojansivu e34a488617 Phone, foldable, tablet, laptop and 55" touchscreen: the new UI checked upright and on its side.
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 !
2026-10-17 04:05:49 +03:00

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;
}