المكوّنات · جدول بتصفية مركّبة
جدول بتصفية مركّبة
بحث نصّي وقائمتان، والنتيجة تقاطعها والمجموع يتبعها
12 تصاميم من هذه العائلة
<div class="tb tbf">
<div class="head">
<input class="q" type="search" placeholder="ابحث بالاسم أو الرقم…" aria-label="بحث">
<select class="dp" aria-label="القسم"><option value="">كل الأقسام</option></select>
<select class="st" aria-label="الحالة"><option value="">كل الحالات</option>
<option>على رأس العمل</option><option>إجازة</option><option>منتهية</option></select>
<span class="cnt"></span>
</div>
<div class="wrap"><table>
<thead><tr>
<th scope="col" data-ar="الموظّف" data-en="Employee">الموظّف</th>
<th scope="col" data-ar="الرقم" data-en="ID">الرقم</th>
<th scope="col" data-ar="القسم" data-en="Department">القسم</th>
<th scope="col" data-ar="الحالة" data-en="Status">الحالة</th>
<th scope="col" class="e" data-ar="الراتب" data-en="Salary">الراتب</th>
</tr></thead>
<tbody></tbody>
<tfoot><tr><td colspan="4" data-ar="مجموع الظاهر" data-en="Visible total">مجموع الظاهر</td>
<td class="e sum"></td></tr></tfoot>
</table></div>
<p class="empty" hidden data-ar="لا موظّف يطابق التصفية" data-en="No employee matches">لا موظّف يطابق التصفية</p>
</div>
/* pt-scoped */
/* table-filter — موارد بشرية */
.tbf{width:100%;height:100%;display:flex;flex-direction:column;gap:6px;
font-size:10px;color:var(--pt-text-secondary);overflow:hidden;padding:9px 10px}
.tbf .wrap{flex:1;min-height:0;overflow:auto;border-radius:var(--pt-radius-sm);
border:1px solid var(--pt-border-subtle)}
.tbf table{width:100%;border-collapse:collapse;font-size:8.5px}
.tbf th, .tbf td{padding:5px 7px;text-align:start;white-space:nowrap}
.tbf thead th{position:sticky;inset-block-start:0;z-index:2;background:var(--pt-bg-inset);
color:var(--pt-text-muted);font-weight:500;font-size:8px;
border-bottom:1px solid var(--pt-border-default)}
.tbf tbody tr{border-bottom:1px solid var(--pt-border-subtle)}
.tbf tbody tr:last-child{border-bottom:0}
.tbf tbody tr:hover{background:var(--pt-surface-hover)}
.tbf tbody td{color:var(--pt-text-secondary)}
.tbf .e{text-align:end;font-variant-numeric:tabular-nums}
.tbf .head{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.tbf .head h4{margin:0;font-size:10px;color:var(--pt-text-primary);font-weight:600}
.tbf .cnt{font-size:8px;color:var(--pt-text-muted);font-variant-numeric:tabular-nums;
margin-inline-start:auto}
.tbf button{font:inherit;cursor:pointer}
.tbf button:focus-visible, .tbf input:focus-visible, .tbf select:focus-visible, .tbf th:focus-visible{outline:2px solid var(--pt-focus-ring);outline-offset:-2px}
.tbf .empty{padding:12px;text-align:center;font-size:9px;color:var(--pt-text-muted)}
/* شخصية: شريط أدوات أعلى، تذييل بمجموع، انحناء صغير */
.tbf .head input, .tbf .head select{padding:4px 8px;border-radius:var(--pt-radius-xs);
border:1px solid var(--pt-border-default);background:var(--pt-bg-inset);
font:inherit;font-size:8.5px;color:var(--pt-text-primary);outline:none}
.tbf .head input{flex:1;min-width:90px}
.tbf .head select{max-width:96px}
.tbf .st{padding:1px 6px;border-radius:var(--pt-radius-full);font-size:7.5px;font-weight:600}
.tbf .on{background:var(--pt-accent-muted);color:var(--pt-accent)}
.tbf .lv{background:color-mix(in srgb,var(--pt-color-warn-400) 20%,transparent);
color:var(--pt-color-warn-400)}
.tbf .off{background:var(--pt-bg-inset);color:var(--pt-text-muted)}
.tbf tfoot td{padding-top:6px;border-top:1px solid var(--pt-border-default);
color:var(--pt-text-muted);font-size:8px;background:var(--pt-bg-inset);
position:sticky;inset-block-end:0}
.tbf tfoot .sum{color:var(--pt-accent);font-weight:700;font-size:9.5px}
/* يعمل داخل إطار معزول — لا يعتمد على شيء خارجه */
/* pt-remount — يعاد التركيب عند تبديل اللغة */
(function () {
var host = document.querySelector('.tbf');
if (!host) return;
var snapshot = host.innerHTML;
var timers = [];
var ac = null;
/* الجسم كما كتبه المولِّد. يستقبل بدائل مظلَّلة لـ root وdocument
والمؤقّتات، فينظّف نفسه دون أن يعرف أنه قابل لإعادة التركيب. */
function run(root, document, setTimeout, setInterval) {
var ar = document.documentElement.lang !== 'en';
/* العملة: ر.ق (QAR) — سوق قطر.
الأرقام مخزَّنة بـالريال وتُحوَّل، فتبقى
المبالغ معقولة في كل سوق. والتحويل لا التبديل: «18,500» راتبًا بالريال
معقول، وبالدينار الأردني ثروة.
وخانتان عشريتان — الوحدة تُقسَّم إلى مئة. */
var money = (function () {
var RATE = 3.64, DEC = 2;
return function (v, d) {
var out = (Number(v) / 3.75) * RATE;
var places = d === undefined ? DEC : d;
var s = out.toLocaleString('en-US', {
minimumFractionDigits: places, maximumFractionDigits: places });
return ar ? s + ' ر.ق' : 'QAR ' + s;
};
})();
/* التطبيع العربي: «الموظفين» و«موظفين» و«مُوَظَّفين» كلمة واحدة */
function norm(s) {
return String(s).replace(/[\u064B-\u0652\u0670\u0640]/g, '')
.replace(/[\u0622\u0623\u0625\u0671]/g, '\u0627')
.replace(/\u0629/g, '\u0647').replace(/\u0649/g, '\u064A')
.replace(/[\u0660-\u0669]/g, function (d) { return d.charCodeAt(0) - 0x0660; })
.toLowerCase().replace(/\s+/g, ' ').trim();
}
var ROWS = [
{ nm: 'كرم', id: 'EMP-1042', dp: 'الهندسة', st: 'على رأس العمل', sal: 18500 },
{ nm: 'نور', id: 'EMP-1088', dp: 'التسويق', st: 'على رأس العمل', sal: 15200 },
{ nm: 'سامي', id: 'EMP-1103', dp: 'المالية', st: 'إجازة', sal: 21000 },
{ nm: 'دينا', id: 'EMP-1150', dp: 'الهندسة', st: 'على رأس العمل', sal: 17800 },
{ nm: 'مالك', id: 'EMP-1177', dp: 'العمليات', st: 'منتهية', sal: 12400 },
{ nm: 'رند', id: 'EMP-1204', dp: 'التسويق', st: 'إجازة', sal: 14600 },
{ nm: 'سيف', id: 'EMP-1231', dp: 'المالية', st: 'على رأس العمل', sal: 19900 },
{ nm: 'لينا', id: 'EMP-1266', dp: 'العمليات', st: 'على رأس العمل', sal: 13100 }
];
var CLS = { 'على رأس العمل': 'on', 'إجازة': 'lv', 'منتهية': 'off' };
var q = root.querySelector('.q'), dp = root.querySelector('.dp'), st = root.querySelector('.st');
var tb = root.querySelector('tbody'), empty = root.querySelector('.empty');
/* الأقسام تُبنى من البيانات لا تُكتب: قائمة يدوية تتقادم مع أوّل قسم جديد */
var deps = ROWS.map(function (r) { return r.dp; }).filter(function (v, i, a) {
return a.indexOf(v) === i; }).sort();
deps.forEach(function (d) {
var o = document.createElement('option'); o.textContent = d; dp.appendChild(o);
});
function draw() {
var t = norm(q.value), d = dp.value, s = st.value;
var shown = ROWS.filter(function (r) {
if (d && r.dp !== d) return false;
if (s && r.st !== s) return false;
if (!t) return true;
return norm(r.nm + ' ' + r.id + ' ' + r.dp).indexOf(t) !== -1;
});
tb.innerHTML = shown.map(function (r) {
return '<tr><td>' + r.nm + '</td><td>' + r.id + '</td><td>' + r.dp + '</td>' +
'<td><span class="st ' + CLS[r.st] + '">' + r.st + '</span></td>' +
'<td class="e">' + money(r.sal, 0) + '</td></tr>';
}).join('');
/* المجموع على الظاهر فقط — وإلّا ناقض الصفوف فوقه */
var tot = shown.reduce(function (a, r) { return a + r.sal; }, 0);
root.querySelector('.sum').textContent = money(tot, 0);
root.querySelector('.cnt').textContent = shown.length + ' / ' + ROWS.length;
empty.hidden = shown.length > 0;
}
[q, dp, st].forEach(function (el) {
el.addEventListener('input', draw); el.addEventListener('change', draw);
});
draw();
}
function mount() {
ac = new AbortController();
var real = window.document;
/* وسيط يمرّر كل شيء إلى المستند الحقيقي، ويعلّق كل مستمع بإشارة
تُقطع عند إعادة التركيب — فلا يبقى مستمع من نسخة ماتت. */
var doc = new Proxy(real, {
get: function (t, k) {
if (k === 'addEventListener') {
return function (type, fn, opts) {
var o = (opts && typeof opts === 'object') ? Object.assign({}, opts)
: { capture: !!opts };
o.signal = ac.signal;
return t.addEventListener(type, fn, o);
};
}
var v = t[k];
return typeof v === 'function' ? v.bind(t) : v;
}
});
function track(fn) {
return function (f, ms) { var id = fn(f, ms); timers.push(id); return id; };
}
run(host, doc, track(window.setTimeout.bind(window)),
track(window.setInterval.bind(window)));
}
function unmount() {
if (ac) ac.abort();
// المعرّفان يتشاركان فضاءً واحدًا في المتصفّح، فالإلغاء المزدوج آمن
timers.forEach(function (id) { window.clearTimeout(id); window.clearInterval(id); });
timers = [];
host.innerHTML = snapshot;
}
mount();
addEventListener('pt-lang', function () { unmount(); mount(); });
})();
# Table with Combined Filters
**Role:** You are an Arabic-first UI designer and front-end engineer. Read the whole
specification before writing a single line, then follow it literally. If two requirements
conflict, favour usability over visual effect.
**Goal:** Table with Combined Filters — Text search plus two selects — intersected, with a total that follows
**Why this component matters:** Recompute the footer total over visible rows only. A total that ignores the active filter contradicts the rows above it, and finance users will trust neither.
**Direction:** Genuine RTL — not a mirrored Latin layout. Use logical properties
(`margin-inline-start`, `padding-inline`, `inset-inline-start`, `text-align: start`),
never physical ones (`margin-left`, `left`). The layout must flip with `dir` on its own,
with no duplicated rules.
**Typography:** Cairo for Arabic, IBM Plex Sans for Latin. Arabic line-height must be
`0.18` higher than the Latin equivalent — dots, hamzas and maddas need more vertical room.
Arabic font-size runs `1.06×` the Latin size at the same optical weight.
**Numerals:** Western digits in both languages (`1,234.50`). Only the currency changes:
`ر.س` **after** the number in Arabic, `$` **before** it in English. Jordanian dinar
uses **three** decimal places, not two.
**Colors:** Dark mode — background `#0D142B` · surfaces `#211F54` · borders `#545C91` · text `#918FC2`.
Accent `#ADA9E8` must never exceed 8% of the screen area.
**Language:** Bilingual — every string carries both Arabic and English, and the layout
flips with `dir` automatically.
**States:** `default` · `hover` · `focus` · `active` · `disabled` · `loading` · `error`.
Do not design the resting state alone.
**Accessibility:** Contrast ratio at least `4.5:1`, complete keyboard navigation,
visible focus rings, and honour `prefers-reduced-motion`.
**Output:** Clean HTML and CSS in two separate files. No frameworks, no libraries,
no external dependencies.