187 files
This commit is contained in:
1 parent
16c9b4d7f4
commit
d6413e19b5
187 files changed
+15542
-2765
No files matched your search
@@ -9,8 +9,22 @@ export const panelStyles = css`
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
|
||||
/* The panel lays itself out: a bounded host, the header on top and
|
||||
.content as THE scroll container (virtualized table, sticky bulk bar
|
||||
and the docked split-view detail all hang off its scroll position).
|
||||
HA 2026.8 started wrapping custom panels in a height-less, safe-area
|
||||
padded block — which turned our 100% into "auto" and moved scrolling to
|
||||
the document. panel.py opts out of that wrapper (handle_safe_area), so
|
||||
the insets are ours to apply: same padding HA would have added, inside
|
||||
the bounded box. The vars are 0 on desktop / older cores. */
|
||||
.panel {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding:
|
||||
var(--safe-area-inset-top, 0px)
|
||||
var(--safe-area-content-inset-right, var(--safe-area-inset-right, 0px))
|
||||
var(--safe-area-inset-bottom, 0px)
|
||||
var(--safe-area-content-inset-left, var(--safe-area-inset-left, 0px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -141,6 +155,19 @@ export const panelStyles = css`
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
/* Saved-views "save" icon-button sits in the select row: size it to the
|
||||
select (36px) so its icon centres on the select, not 6px above it.
|
||||
HA ≥2025.11 sizes ha-icon-button via --ha-icon-button-size (the old
|
||||
--mdc-icon-button-size is a no-op there); older cores the other way
|
||||
round, so declare both. */
|
||||
.views-save-btn {
|
||||
--ha-icon-button-size: 36px;
|
||||
--mdc-icon-button-size: 36px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
margin: 0 -2px 0 -4px;
|
||||
}
|
||||
|
||||
.filter-bar select {
|
||||
padding: 8px;
|
||||
border: 1px solid var(--divider-color);
|
||||
@@ -230,6 +257,42 @@ export const panelStyles = css`
|
||||
}
|
||||
.bulk-check { display: flex; align-items: center; justify-content: center; cursor: pointer; }
|
||||
|
||||
/* Group-by sections: the OUTER .task-table owns the column tracks (every
|
||||
breakpoint's template above applies to it unchanged); each section card
|
||||
and its row block are column subgrids, so the badge/object/name columns
|
||||
line up across sections — a section whose rows are all "OK" no longer
|
||||
starts its names 60px left of one that also carries "Overdue". */
|
||||
.task-table.grouped > .group-section {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
}
|
||||
.task-table.grouped .group-section-header { grid-column: 1 / -1; }
|
||||
.task-table.grouped .group-rows {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
/* Object page task list: it has no object-name cell (the page IS the
|
||||
object), so under the shared 7-track template every cell sat one track
|
||||
to the left — the ~190px action pair landed in the 150px due track and
|
||||
poked past the row's right edge (a horizontal scrollbar as soon as a
|
||||
classic vertical one took its 17px; wider still with German labels).
|
||||
Six tracks for six cells. Below 769px the narrow/tight layouts place
|
||||
the cells explicitly and keep their own four tracks. */
|
||||
@media (min-width: 769px) {
|
||||
.task-table.object-tasks {
|
||||
grid-template-columns: auto minmax(120px, 1fr) minmax(0, 220px) 100px 150px auto;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.task-table.object-tasks {
|
||||
grid-template-columns: auto fit-content(400px) minmax(0, 1fr) 100px 150px auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Master-detail split (>=1500px panel, 2026-09-01): list left, docked task
|
||||
detail right - the width finally carries content instead of slack. Only
|
||||
the dashboard tab uses it; below the threshold everything is unchanged. */
|
||||
@@ -248,12 +311,18 @@ export const panelStyles = css`
|
||||
.split-list .task-table.bulk { grid-template-columns: auto auto minmax(100px, 180px) minmax(120px, 1fr) 150px auto; }
|
||||
.split-list .cell.type { display: none; }
|
||||
.split-list .task-sub { display: none; }
|
||||
/* Grouped list: the section cards carry a 12px vertical margin that does
|
||||
not collapse inside the grid, so the first card sat 12px below the
|
||||
pane's top edge and the two cards read as misaligned. Level them. */
|
||||
.split-list .task-table.grouped > .group-section:first-child { margin-top: 0; }
|
||||
/* The pane never scrolls on its own: it is as tall as the detail and
|
||||
docks scroll-aware (helpers/sticky-pane sets top/margin-top inline) —
|
||||
top edge while scrolling up, bottom edge while scrolling down, so a
|
||||
long detail is read by scrolling the list, not a nested scrollbar. */
|
||||
.split-pane {
|
||||
min-width: 0;
|
||||
position: sticky;
|
||||
top: 8px;
|
||||
max-height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 12px;
|
||||
padding: 4px 16px 16px;
|
||||
@@ -412,6 +481,11 @@ export const panelStyles = css`
|
||||
}
|
||||
|
||||
.cell { font-size: 14px; }
|
||||
/* Task name + object name travel as one group: in the wide grids the
|
||||
wrapper dissolves (display: contents) so both stay direct grid items in
|
||||
their own tracks; the narrow/tight layouts turn it into the first row
|
||||
(name left, object right — #150). */
|
||||
.row-head { display: contents; }
|
||||
.cell.object-name { color: var(--primary-color); cursor: pointer; }
|
||||
.cell.task-name { font-weight: 500; }
|
||||
.cell.type { color: var(--secondary-text-color); }
|
||||
@@ -474,7 +548,7 @@ export const panelStyles = css`
|
||||
|
||||
/* DESIGN PROTOTYPE (#145 wish 1): labelled, colour-coded action buttons
|
||||
instead of bare icons. Complete = filled success pill, Skip = outlined
|
||||
neutral pill; both keep the icon as a leading glyph. */
|
||||
warning (orange) pill; both keep the icon as a leading glyph. */
|
||||
.row-actions.as-buttons { gap: 4px; }
|
||||
.row-actions.as-buttons ha-button {
|
||||
--ha-button-font-size: 13px;
|
||||
@@ -893,15 +967,24 @@ export const panelStyles = css`
|
||||
/* Stale-bundle handshake banner (roadmap guard 2) */
|
||||
.update-banner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 6px 10px;
|
||||
padding: 8px 16px;
|
||||
background: color-mix(in srgb, var(--primary-color) 14%, var(--card-background-color, #fff));
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
font-size: 14px;
|
||||
}
|
||||
/* The text claims the row; on a phone the buttons wrap under it instead of
|
||||
squeezing it into a one-word-per-line column (#150 round, 2026-09-02). */
|
||||
.update-banner span {
|
||||
flex: 1;
|
||||
flex: 1 1 220px;
|
||||
}
|
||||
.update-banner ha-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
.update-banner ha-button + ha-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.popup-menu {
|
||||
@@ -1518,24 +1601,54 @@ export const panelStyles = css`
|
||||
X-positions across rows regardless of content (sparkline, bar, %).
|
||||
Earlier flex-wrap-based layouts let the row wrap unpredictably so
|
||||
"X days" sometimes sat near the middle, sometimes at the right edge.
|
||||
Grid template:
|
||||
[badges auto | task-name 1fr | due-cell 100px | actions auto]
|
||||
Task-name spans the full top row (own row above), chips span the
|
||||
full bottom row. */
|
||||
Two rows since #150 (was three: name / badge+object+due+actions /
|
||||
chips — a phone list scrolled forever):
|
||||
row 1 task name ................................ object name
|
||||
row 2 [status icon | chips | due 100px | actions]
|
||||
The status pill drops its label (icon + colour stay, title/aria keep
|
||||
the word), the object name moves up beside the task name, and the
|
||||
chips take the freed middle track. */
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: auto auto auto;
|
||||
row-gap: 4px;
|
||||
grid-template-rows: auto auto;
|
||||
row-gap: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:host([narrow]) .cell.type { display: none; }
|
||||
:host([narrow]) .row-head {
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
/* The object page's rows have no object name — there the task name is a
|
||||
direct grid item and the grid placement applies; inside .row-head the
|
||||
flex rule wins and the grid lines are inert. */
|
||||
:host([narrow]) .cell.task-name {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
:host([narrow]) .cell.object-name {
|
||||
order: 1;
|
||||
flex: 0 1 auto;
|
||||
max-width: 50%;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: right;
|
||||
}
|
||||
/* Bulk selection on a phone: the checkbox takes the badge column of the
|
||||
first row, the name group starts one track further right. */
|
||||
:host([narrow]) .task-table.bulk .bulk-check { grid-column: 1; grid-row: 1; align-self: start; }
|
||||
:host([narrow]) .task-table.bulk .row-head { grid-column: 2 / -1; }
|
||||
:host([narrow]) .cell-badges {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
@@ -1547,19 +1660,11 @@ export const panelStyles = css`
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
:host([narrow]) .cell.object-name {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
min-width: 0;
|
||||
/* Cap long object names at 2 lines with ellipsis instead of growing
|
||||
unbounded vertically. The full name is still readable via the panel
|
||||
object-detail view (one tap on the object). */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.2;
|
||||
}
|
||||
/* Icon-only status pill (#150): a round 22 px disc; the colour + shape
|
||||
still carry the state, the label lives in title/aria-label. */
|
||||
:host([narrow]) .task-row .status-label { display: none; }
|
||||
:host([narrow]) .task-row .status-badge { min-width: 0; padding: 3px; border-radius: 50%; }
|
||||
:host([narrow]) .task-row .status-badge ha-icon { --mdc-icon-size: 16px; margin-left: 0; }
|
||||
:host([narrow]) .due-cell {
|
||||
grid-column: 3;
|
||||
grid-row: 2;
|
||||
@@ -1583,15 +1688,15 @@ export const panelStyles = css`
|
||||
}
|
||||
:host([narrow]) .row-actions.as-buttons ha-button,
|
||||
:host([tight]) .row-actions.as-buttons ha-button { --ha-button-font-size: 12px; }
|
||||
/* buttons_compact: the labelled buttons collapse to an icon-only ha-button
|
||||
plus ha-icon-button side by side — the colour carries the meaning, the
|
||||
row keeps the icon-era height. */
|
||||
/* buttons_compact: the labelled buttons collapse to two icon-only
|
||||
ha-buttons side by side (solid green Complete, outlined orange Skip) —
|
||||
the colour carries the meaning, the row keeps the icon-era height.
|
||||
--wa-form-control-padding-inline is the wa-button inset HA's ha-button
|
||||
forwards (16 px default -> a 58 px pill around a 20 px glyph). */
|
||||
:host([narrow]) .row-actions.as-buttons.compact,
|
||||
:host([tight]) .row-actions.as-buttons.compact { flex-direction: row; align-items: center; gap: 2px; }
|
||||
:host([tight]) .row-actions.as-buttons.compact { flex-direction: row; align-items: center; gap: 4px; }
|
||||
:host([narrow]) .row-actions.compact ha-button,
|
||||
:host([tight]) .row-actions.compact ha-button { --ha-button-height: 36px; min-width: 0; }
|
||||
:host([narrow]) .row-actions.compact ha-icon-button,
|
||||
:host([tight]) .row-actions.compact ha-icon-button { --mdc-icon-button-size: 40px; --mdc-icon-size: 22px; color: var(--secondary-text-color); }
|
||||
:host([tight]) .row-actions.compact ha-button { --ha-button-height: 36px; --wa-form-control-padding-inline: 10px; min-width: 0; }
|
||||
:host([narrow]) .row-actions.compact ha-icon,
|
||||
:host([tight]) .row-actions.compact ha-icon { --mdc-icon-size: 20px; }
|
||||
|
||||
@@ -1609,26 +1714,35 @@ export const panelStyles = css`
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: auto auto auto;
|
||||
row-gap: 4px;
|
||||
grid-template-rows: auto auto;
|
||||
row-gap: 6px;
|
||||
padding: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
:host([tight]:not([narrow])) .cell.type { display: none; }
|
||||
:host([tight]:not([narrow])) .cell.task-name { grid-column: 1 / -1; grid-row: 1; min-width: 0; }
|
||||
:host([tight]:not([narrow])) .row-head { display: flex; grid-column: 1 / -1; grid-row: 1; align-items: baseline; justify-content: space-between; gap: 10px; min-width: 0; }
|
||||
:host([tight]:not([narrow])) .cell.task-name { grid-column: 1 / -1; grid-row: 1; flex: 1 1 auto; min-width: 0; }
|
||||
:host([tight]:not([narrow])) .cell.object-name { order: 1; flex: 0 1 auto; max-width: 50%; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
|
||||
:host([tight]:not([narrow])) .task-table.bulk .bulk-check { grid-column: 1; grid-row: 1; align-self: start; }
|
||||
:host([tight]:not([narrow])) .task-table.bulk .row-head { grid-column: 2 / -1; }
|
||||
:host([tight]:not([narrow])) .cell-badges { grid-column: 1; grid-row: 2; flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
:host([tight]:not([narrow])) .cell.object-name { grid-column: 2; grid-row: 2; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.2; }
|
||||
:host([tight]:not([narrow])) .task-row .status-label { display: none; }
|
||||
:host([tight]:not([narrow])) .task-row .status-badge { min-width: 0; padding: 3px; border-radius: 50%; }
|
||||
:host([tight]:not([narrow])) .task-row .status-badge ha-icon { --mdc-icon-size: 16px; margin-left: 0; }
|
||||
:host([tight]:not([narrow])) .due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; }
|
||||
:host([tight]:not([narrow])) .row-actions { grid-column: 4; grid-row: 2; }
|
||||
:host([tight]:not([narrow])) .task-sub { grid-column: 1 / -1; grid-row: 3; font-size: 11px; gap: 6px; justify-content: flex-start; flex-wrap: wrap; }
|
||||
:host([tight]:not([narrow])) .task-sub { grid-column: 2; grid-row: 2; align-self: center; font-size: 11px; gap: 4px; justify-content: flex-start; flex-wrap: wrap; }
|
||||
:host([tight]:not([narrow])) .task-sub-empty { display: none; }
|
||||
:host([tight]:not([narrow])) .mini-sparkline { display: none; }
|
||||
:host([tight]:not([narrow])) .trend-arrow { display: inline; }
|
||||
/* Chips sit in the middle track of row 2 — the slot the object name
|
||||
vacated — vertically centred on the 36 px action buttons. */
|
||||
:host([narrow]) .task-sub {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
align-self: center;
|
||||
font-size: 11px;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -1647,7 +1761,6 @@ export const panelStyles = css`
|
||||
column-gap: 6px;
|
||||
}
|
||||
:host([narrow]) .task-row { padding: 12px 8px; }
|
||||
:host([narrow]) .row-actions.compact ha-icon-button { --mdc-icon-button-size: 32px; --mdc-icon-size: 20px; }
|
||||
}
|
||||
|
||||
:host([narrow]) .detail-header {
|
||||
@@ -1781,17 +1894,23 @@ export const panelStyles = css`
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: auto auto auto;
|
||||
row-gap: 4px;
|
||||
grid-template-rows: auto auto;
|
||||
row-gap: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
.cell.type { display: none; }
|
||||
.cell.task-name { grid-column: 1 / -1; grid-row: 1; min-width: 0; }
|
||||
.row-head { display: flex; grid-column: 1 / -1; grid-row: 1; align-items: baseline; justify-content: space-between; gap: 10px; min-width: 0; }
|
||||
.cell.task-name { grid-column: 1 / -1; grid-row: 1; flex: 1 1 auto; min-width: 0; }
|
||||
.cell.object-name { order: 1; flex: 0 1 auto; max-width: 50%; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
|
||||
.task-table.bulk .bulk-check { grid-column: 1; grid-row: 1; align-self: start; }
|
||||
.task-table.bulk .row-head { grid-column: 2 / -1; }
|
||||
.cell-badges { grid-column: 1; grid-row: 2; flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
.cell.object-name { grid-column: 2; grid-row: 2; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.2; }
|
||||
.task-row .status-label { display: none; }
|
||||
.task-row .status-badge { min-width: 0; padding: 3px; border-radius: 50%; }
|
||||
.task-row .status-badge ha-icon { --mdc-icon-size: 16px; margin-left: 0; }
|
||||
.due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; }
|
||||
.row-actions { grid-column: 4; grid-row: 2; }
|
||||
.task-sub { grid-column: 1 / -1; grid-row: 3; font-size: 11px; gap: 6px; justify-content: flex-start; flex-wrap: wrap; }
|
||||
.task-sub { grid-column: 2; grid-row: 2; align-self: center; font-size: 11px; gap: 4px; justify-content: flex-start; flex-wrap: wrap; }
|
||||
.task-sub-empty { display: none; }
|
||||
.mini-sparkline { display: none; }
|
||||
.trend-arrow { display: inline; }
|
||||
|
||||
Reference in new issue
Block a user