Arabic mirrored the layout but not these sixteen rules
An RTL audit of research-map, research-dashboard and its four dashboard components. Translation was already sound -- 101 keys, all present in all eleven locales, no hardcoded English anywhere user-visible -- so what was left was direction: rules that pin a physical side while the reader's text flows the other way. Three an Arabic reader would notice immediately. Both search icons sit at left:8px and left:10px while the input around them mirrors, so the icon ends up on the far side of the field it belongs to. The detail drawer's border-left stays on the geometric left, which in Arabic is the drawer's trailing edge, so the panel appears unattached on the side it hinges from. The rest are quieter but the same mistake: a badge, a provider chip, the two edge affordances, a table's numeric columns and its indicator, a result row's spacer, the projection toggle. Every change is LTR-NEUTRAL, which is what makes this safe to land on its own. In left-to-right, inset-inline-start resolves to left, text-align:end resolves to right, margin-inline-start resolves to margin-left. The English view cannot move; only the Arabic one can. Fourteen insertions against fourteen deletions, one property each, nothing else touched. The pattern is not new here -- .rd-legend-map already uses inset-inline-end, with a comment recording that a physical right pinned it to the wrong edge in Arabic. These sixteen are the rules that were missed when that lesson was applied. What this does NOT establish: that Arabic renders correctly. Every RTL gate in the repo needs a browser, so this was read rather than run. The three visible ones want eyes on ar before anyone calls RTL done.
This commit is contained in:
parent
f7e9ed1ea7
commit
55767fabbc
6 changed files with 14 additions and 14 deletions
|
|
@ -268,7 +268,7 @@ useShadowStyles(`
|
|||
.dg-main { font-weight:500;font-size:13px;color:#222;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:6px; }
|
||||
.dg-main strong { font-weight:700;overflow:hidden;text-overflow:ellipsis; }
|
||||
.dg-meta { margin-top:2px;display:flex;flex-wrap:wrap;gap:4px 10px;font-size:11px;color:#555;line-height:1.35; }
|
||||
.dg-field strong { font-weight:600;color:#333;margin-right:2px; }
|
||||
.dg-field strong { font-weight:600;color:#333;margin-inline-end:2px; }
|
||||
/* Round 16 — labeled category tag replacing the old emoji prefixes. */
|
||||
.dg-tag { flex:none;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;color:#3b463d;background:rgba(59,70,61,0.10);border-radius:4px;padding:1px 5px;line-height:1.5;white-space:nowrap; }
|
||||
.dg-agg-label { overflow:hidden;text-overflow:ellipsis; }
|
||||
|
|
@ -424,10 +424,10 @@ useShadowStyles(`
|
|||
map did manage to render stays visible around it. A spinner that outlives the
|
||||
failure reads as "slow" and the user just waits; this says the map is gone. */
|
||||
.rm-error { position:absolute;top:50%;left:12px;right:12px;z-index:7;transform:translateY(-50%);text-align:center;font:13px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#fecaca;background:rgba(15,18,22,.95);border:1px solid rgba(254,202,202,.28);border-radius:10px;padding:12px 14px;box-shadow:0 2px 10px rgba(0,0,0,.4); }
|
||||
.rm-badge { position:absolute;top:10px;right:10px;background:rgba(124,140,248,0.12);color:#7c8cf8;border:1px solid rgba(124,140,248,0.3);padding:3px 9px;border-radius:4px;font-size:11px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;pointer-events:none;z-index:50; }
|
||||
.rm-badge { position:absolute;top:10px;inset-inline-end:10px;background:rgba(124,140,248,0.12);color:#7c8cf8;border:1px solid rgba(124,140,248,0.3);padding:3px 9px;border-radius:4px;font-size:11px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;pointer-events:none;z-index:50; }
|
||||
/* Engine badge — bottom-left, unobtrusive. Colour differs per engine so a
|
||||
side-by-side comparison is readable at a glance. */
|
||||
.rm-provider-badge { position:absolute;bottom:8px;left:8px;z-index:50;pointer-events:none;padding:2px 8px;border-radius:999px;font:600 10px/1.6 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;letter-spacing:.04em;color:#fff;background:rgba(30,41,59,0.72);border:1px solid rgba(255,255,255,0.18);backdrop-filter:blur(2px); }
|
||||
.rm-provider-badge { position:absolute;bottom:8px;inset-inline-start:8px;z-index:50;pointer-events:none;padding:2px 8px;border-radius:999px;font:600 10px/1.6 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;letter-spacing:.04em;color:#fff;background:rgba(30,41,59,0.72);border:1px solid rgba(255,255,255,0.18);backdrop-filter:blur(2px); }
|
||||
.rm-provider-badge[data-provider="mapbox"] { background:rgba(37,99,235,0.78);border-color:rgba(147,197,253,0.5); }
|
||||
.rm-provider-badge[data-provider="maplibre"] { background:rgba(5,150,105,0.80);border-color:rgba(110,231,183,0.5); }
|
||||
/* Mobile-only legend slot — desktop hides via @media */
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ useShadowStyles(`
|
|||
border-radius:5px; font-size:11px;
|
||||
line-height:1; box-shadow:0 1px 4px rgba(0,0,0,.14); }
|
||||
.rd-edge-caret:hover{ background:var(--dx-hover,rgba(59,70,61,.06)); }
|
||||
.rd-edge-facet{ left:5px; } .rd-edge-rail{ right:5px; }
|
||||
.rd-edge-facet{ inset-inline-start:5px; } .rd-edge-rail{ inset-inline-end:5px; }
|
||||
/* (the floating .rd-table-restore pill is retired — the table's own always-docked header bar
|
||||
carries the chevron, the live count and Export CSV while collapsed) */
|
||||
/* Medium-width niceties. These keep the SAME rail balance as the defaults above (tree wider
|
||||
|
|
|
|||
|
|
@ -713,10 +713,10 @@ useShadowStyles(`
|
|||
so nothing is clipped in practice, and hidden keeps a pathological config (a 40-character
|
||||
label with an explicit narrow minW) from painting over the next column. */
|
||||
.rd-th{ flex:1 1 0; min-width:0; padding:4px 4px; font-weight:700; color:var(--dx-text, #3b463d); cursor:pointer; user-select:none; white-space:nowrap; overflow:hidden; }
|
||||
.rd-th.num{ text-align:right; } .rd-th.chk{ flex:0 0 30px; cursor:default; text-align:center; padding:6px 0; }
|
||||
.rd-th.num{ text-align:end; } .rd-th.chk{ flex:0 0 30px; cursor:default; text-align:center; padding:6px 0; }
|
||||
/* sort indicator — the glyph carries the meaning; colour only needs to read as ACTIVE
|
||||
against the muted header row, so it takes primary ink rather than a hue of its own. */
|
||||
.rd-th .ind{ font-size:9px; color:var(--dx-text, #3b463d); margin-left:3px; }
|
||||
.rd-th .ind{ font-size:9px; color:var(--dx-text, #3b463d); margin-inline-start:3px; }
|
||||
/* ★ THE ROW WIDTH IS SHARED, NEVER INTRINSIC — and getting this wrong is invisible in the
|
||||
numbers. Two facts had to be reconciled:
|
||||
(1) min-width ALONE DOES NOT CREATE OVERFLOW. A block-level flex container inside the
|
||||
|
|
@ -736,7 +736,7 @@ useShadowStyles(`
|
|||
.rd-row{ display:flex; width:100%; height:26px; line-height:16px; border-bottom:1px solid var(--dx-surface, #f3f3f1); align-items:center; }
|
||||
.rd-row:hover{ background:var(--dx-surface, #f3f3f1); } .rd-row.sel{ background:#fff7ec; box-shadow:inset 3px 0 0 #e67e22; }
|
||||
.rd-td{ flex:1 1 0; min-width:0; padding:0 4px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:var(--dx-text, #3b463d); }
|
||||
.rd-td.num{ text-align:right; font-variant-numeric:tabular-nums; } .rd-td.chk{ flex:0 0 30px; text-align:center; padding:0; }
|
||||
.rd-td.num{ text-align:end; font-variant-numeric:tabular-nums; } .rd-td.chk{ flex:0 0 30px; text-align:center; padding:0; }
|
||||
/* ★ THE FOOTER IS A SEPARATE SCROLLPORT and has to be told to follow. .rd-foot is a SIBLING of
|
||||
.rd-grid, not a child, so scrolling the grid right does not move it — the Σ total would slide
|
||||
out of alignment with the Population column the moment the table scrolled. overflow:hidden +
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ function clearDetail() {
|
|||
}
|
||||
|
||||
useShadowStyles(`
|
||||
.rd-detail{ box-sizing:border-box; background:var(--rd-panel,#fff); border-left:1px solid var(--rd-line, #d1d4cd);
|
||||
.rd-detail{ box-sizing:border-box; background:var(--rd-panel,#fff); border-inline-start:1px solid var(--rd-line, #d1d4cd);
|
||||
border-bottom:1px solid var(--rd-line, #d1d4cd); padding:var(--s3,12px);
|
||||
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; color:var(--rd-ink, #3b463d); flex:0 0 auto; }
|
||||
.rd-detail .rd-dh{ display:flex; align-items:center; justify-content:space-between; gap:8px;
|
||||
|
|
@ -137,7 +137,7 @@ useShadowStyles(`
|
|||
padding:2px 0; border-bottom:1px solid var(--rd-line, #d1d4cd); }
|
||||
.rd-detail .rd-row:last-child{ border-bottom:0; }
|
||||
.rd-detail .rd-row .k{ color:var(--rd-muted, #6d766e); flex:0 0 auto; min-width:118px; }
|
||||
.rd-detail .rd-row .v{ color:var(--rd-ink, #3b463d); margin-left:auto; text-align:right; font-variant-numeric:tabular-nums; }
|
||||
.rd-detail .rd-row .v{ color:var(--rd-ink, #3b463d); margin-inline-start:auto; text-align:end; font-variant-numeric:tabular-nums; }
|
||||
.rd-detail .rd-row .v .fam{ color:var(--rd-muted, #6d766e); }
|
||||
.rd-detail .rd-exp{ margin-top:var(--s3,12px); width:100%; border:0; border-radius:var(--r-md,6px); cursor:pointer;
|
||||
background:var(--rd-accent,#e67e22); color:#fff; font-weight:700; font-size:var(--fs-md,12px); padding:var(--s2,8px) var(--s3,12px); }
|
||||
|
|
|
|||
|
|
@ -1599,7 +1599,7 @@ useShadowStyles(`
|
|||
shows on engines without fog support (MapLibre), where black is the honest fallback. */
|
||||
.rd-mapwrap[data-projection="globe"]{ background:#000; }
|
||||
.rd-mapwrap .rd-mapc{ position:absolute; inset:0; }
|
||||
.rd-proj{ position:absolute; right:8px; top:8px; z-index:3; font-size:11px; font-weight:700; cursor:pointer; user-select:none;
|
||||
.rd-proj{ position:absolute; inset-inline-end:8px; top:8px; z-index:3; font-size:11px; font-weight:700; cursor:pointer; user-select:none;
|
||||
background:#1a1a2ecc; color:#fff; border:1px solid #ffffff33; border-radius:6px; padding:4px 9px; backdrop-filter:blur(4px); }
|
||||
.rd-proj:hover{ background:#1a1a2e; }
|
||||
/* ★ TOUCH TARGET: 24px, STATED, AT EVERY WIDTH. Measured on a phone (390x844) this rendered
|
||||
|
|
|
|||
|
|
@ -260,11 +260,11 @@ useShadowStyles(`
|
|||
.rd-topbar .rd-filter-search .search-box input{ height:32px; padding:0 10px 0 34px; font-size:var(--fs-md,12px);
|
||||
border-radius:16px; border-color:#ffffff3d; }
|
||||
.rd-topbar .rd-filter-search .search-box input:focus{ border-color:var(--rd-accent,#e67e22); }
|
||||
.rd-topbar .rd-filter-search .search-icon{ left:10px; width:15px; height:15px; }
|
||||
.rd-topbar .rd-filter-search .search-icon{ inset-inline-start:10px; width:15px; height:15px; }
|
||||
/* narrow: return the search to normal flow (drop the absolute centring) and let it take the row
|
||||
width; the align button is already hidden here, so a centred search has no right-hand anchor. */
|
||||
.rd-shell.rd-narrow .rd-topbar .rd-filter-search{ position:static; transform:none;
|
||||
left:auto; top:auto; flex:1 1 auto; width:auto; max-width:none; }
|
||||
inset-inline-start:auto; top:auto; flex:1 1 auto; width:auto; max-width:none; }
|
||||
/* search — translucent light field so typed text stays legible on the green bar */
|
||||
.rd-search{ position:relative; flex:0 1 280px; min-width:140px; }
|
||||
.rd-search input{ width:100%; box-sizing:border-box; height:30px; padding:0 10px 0 28px; font-size:var(--fs-md,12px);
|
||||
|
|
@ -272,14 +272,14 @@ useShadowStyles(`
|
|||
border-radius:var(--r-md,6px); outline:none; }
|
||||
.rd-search input:focus{ border-color:var(--rd-accent,#e67e22); background:#ffffff33; }
|
||||
.rd-search input::placeholder{ color:var(--rd-header-muted,#cbc5b9); }
|
||||
.rd-search .ic{ position:absolute; left:8px; top:50%; transform:translateY(-50%); font-size:12px; color:var(--rd-header-muted,#cbc5b9); pointer-events:none; }
|
||||
.rd-search .ic{ position:absolute; inset-inline-start:8px; top:50%; transform:translateY(-50%); font-size:12px; color:var(--rd-header-muted,#cbc5b9); pointer-events:none; }
|
||||
.rd-results{ position:absolute; left:0; right:0; top:34px; z-index:60; background:#fff; border:1px solid var(--rd-line, #d1d4cd);
|
||||
border-radius:var(--r-md,6px); box-shadow:0 8px 24px #1f273322; overflow:hidden; max-height:320px; overflow-y:auto;
|
||||
scrollbar-width:thin; scrollbar-color:var(--dx-scroll-thumb, #c4c8c0) transparent; }
|
||||
.rd-results .rd-res{ display:flex; align-items:baseline; gap:8px; padding:6px 10px; cursor:pointer; font-size:var(--fs-md,12px); }
|
||||
.rd-results .rd-res:hover, .rd-results .rd-res.on{ background:var(--rd-bg, #f3f3f1); }
|
||||
.rd-results .rd-res .nm{ font-weight:700; color:var(--rd-ink, #3b463d); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.rd-results .rd-res .sub{ margin-left:auto; color:var(--rd-muted, #6d766e); font-size:var(--fs-sm,11px); white-space:nowrap; flex:0 0 auto; }
|
||||
.rd-results .rd-res .sub{ margin-inline-start:auto; color:var(--rd-muted, #6d766e); font-size:var(--fs-sm,11px); white-space:nowrap; flex:0 0 auto; }
|
||||
.rd-results .rd-empty{ padding:8px 10px; color:var(--rd-muted, #6d766e); font-size:var(--fs-sm,11px); }
|
||||
/* (csv button + toast styles moved to DashboardDataTable's header bar) */
|
||||
/* Align Dashboard — subtle ghost button on the green bar; snaps the dragged layout to defaults.
|
||||
|
|
|
|||
Loading…
Reference in a new issue