Introduces a reusable core detail-drawer primitive that slides in from the right and loads any view via a `*-fragment(none).phtml` endpoint. Bundles the address book list overhaul that is its first consumer. Core additions: - `app-detail-drawer.js` — generic drawer with stepper, focus trap, body scroll-lock, URL-hash deep-linking, session-expiry detection - `app-fragment-init.js` — auto-wires tabs/lookups/confirm/file-upload/ fslightbox inside injected HTML; consumers do not re-initialize components - `app-focus-trap.js` — shared focus-trap + refcounted scroll-lock, used by both filter-drawer and detail-drawer - `getHtml()` in `app-http.js` + `SessionExpiredError`; drawer reloads the page on auth redirect instead of rendering the login form in the panel - `DetailDrawerFragmentContractTest` enforces that every `initDetailDrawer` consumer ships matching `*-fragment($id).php` + `*-fragment(none).phtml` Address book list: - Grid collapses from 9 columns to 4 (identity / context / phone / actions) with a two-line identity cell (avatar + name + email) - Tenant register tabs above the grid using the `app-list-tabs` partial; tenant filter wired via hidden toolbar field so grid.js forwards it on every data fetch - Profile body extracted to a shared partial so the full-page view and the new drawer fragment share the same markup - New i18n keys for the drawer/list labels Also refactors `app-filter-drawer` to reuse the shared focus-trap and scroll-lock instead of maintaining its own copy, and documents the detail-drawer convention in CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
2.6 KiB
CSS
144 lines
2.6 KiB
CSS
/* Address book list — dense identity layout */
|
|
|
|
#address-book-grid .gridjs-tr {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.addr-identity-cell {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.addr-identity-avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.addr-identity-text {
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.addr-identity-name {
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
color: var(--app-color-text, inherit);
|
|
text-decoration: none;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 28ch;
|
|
}
|
|
|
|
.addr-identity-name:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.addr-identity-email {
|
|
color: var(--app-color-muted, #6b7280);
|
|
font-size: 0.82rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 28ch;
|
|
}
|
|
|
|
.addr-context-cell {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
min-width: 0;
|
|
color: var(--app-color-text, inherit);
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.addr-context-primary {
|
|
font-weight: 500;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 20ch;
|
|
}
|
|
|
|
.addr-context-secondary {
|
|
color: var(--app-color-muted, #6b7280);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 20ch;
|
|
}
|
|
|
|
.addr-context-separator {
|
|
color: var(--app-color-muted, #9ca3af);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.addr-context-extra {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 1.75rem;
|
|
height: 1.25rem;
|
|
padding: 0 0.375rem;
|
|
border-radius: 9999px;
|
|
background: var(--app-color-subtle, #f3f4f6);
|
|
color: var(--app-color-muted, #6b7280);
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
margin-left: 0.25rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.addr-context-empty,
|
|
.addr-phone-empty {
|
|
color: var(--app-color-muted, #9ca3af);
|
|
}
|
|
|
|
.addr-phone-cell {
|
|
color: var(--app-color-text, inherit);
|
|
text-decoration: none;
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.addr-phone-cell:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.addr-action-cell {
|
|
display: inline-flex;
|
|
gap: 0.25rem;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.addr-action-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 2rem;
|
|
height: 2rem;
|
|
border-radius: 0.375rem;
|
|
color: var(--app-color-muted, #6b7280);
|
|
text-decoration: none;
|
|
transition: background 0.12s, color 0.12s;
|
|
}
|
|
|
|
.addr-action-button:hover {
|
|
background: var(--app-color-subtle, #f3f4f6);
|
|
color: var(--app-color-text, #111827);
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.addr-identity-name,
|
|
.addr-identity-email,
|
|
.addr-context-primary,
|
|
.addr-context-secondary {
|
|
max-width: 100%;
|
|
}
|
|
}
|