المكوّنات · تسجيل على خطوات
تسجيل على خطوات
لا تنتقل خطوة قبل صحّة سابقتها، والتقدّم محسوب
12 تصاميم من هذه العائلة
<div class="sg sgs">
<div class="bar"><i></i></div>
<span class="pos"></span>
<div class="pane" data-step="1">
<h5 data-ar="بياناتك" data-en="Your details">بياناتك</h5>
<label><span data-ar="الاسم" data-en="Name">الاسم</span>
<input class="f1" type="text" placeholder="محمد الحربي"><span class="err"></span></label>
</div>
<div class="pane" data-step="2" hidden>
<h5 data-ar="الاشتراك" data-en="Membership">الاشتراك</h5>
<label><span data-ar="النوع" data-en="Type">النوع</span>
<select class="f2"><option value="">اختر…</option><option>شهري</option>
<option>ربع سنوي</option><option>سنوي</option></select><span class="err"></span></label>
</div>
<div class="pane" data-step="3" hidden>
<h5 data-ar="تأكيد" data-en="Confirm">تأكيد</h5>
<p class="sum"></p>
</div>
<div class="nav"><button class="bk" data-ar="السابق" data-en="Back">السابق</button>
<button class="nx" data-ar="التالي" data-en="Next">التالي</button></div>
</div>
/* pt-scoped */
/* signup-steps — نادٍ رياضي */
.sgs{width:100%;height:100%;display:flex;flex-direction:column;
font-size:10px;color:var(--pt-text-secondary);overflow:hidden}
.sgs h5{margin:0;font-size:11px;color:var(--pt-text-primary);font-weight:600}
.sgs .sub{margin:0;font-size:8.5px;color:var(--pt-text-muted)}
.sgs label{display:flex;flex-direction:column;gap:3px}
.sgs label > span{font-size:8.5px;color:var(--pt-text-muted)}
.sgs input, .sgs select{width:100%;font:inherit;font-size:9.5px;
color:var(--pt-text-primary);outline:none}
.sgs input::placeholder{color:var(--pt-text-muted)}
.sgs .err{font-size:8px;color:var(--pt-color-danger-400);min-height:0}
.sgs .ok{color:var(--pt-accent)}
.sgs button{font:inherit;cursor:pointer}
.sgs button:focus-visible, .sgs input:focus-visible{outline:2px solid var(--pt-focus-ring);
outline-offset:1px}
/* شخصية: شريط تقدّم أعلى، لوحة واحدة ظاهرة، أزرار متقابلة */
.sgs{padding:10px;gap:7px;border:1px solid var(--pt-border-subtle);
border-radius:var(--pt-radius-2xl);background:var(--pt-bg-inset)}
.sgs .bar{height:3px;border-radius:99px;background:var(--pt-border-subtle);overflow:hidden}
.sgs .bar i{display:block;height:100%;width:0;background:var(--pt-accent);
border-radius:inherit;
transition:width var(--pt-motion-duration-normal) var(--pt-motion-easing-standard)}
.sgs .pos{font-size:8px;color:var(--pt-text-muted);font-variant-numeric:tabular-nums}
.sgs .pane{flex:1;display:flex;flex-direction:column;gap:6px;justify-content:center}
.sgs input, .sgs select{padding:6px 9px;border-radius:var(--pt-radius-lg);
border:1px solid var(--pt-border-default);background:var(--pt-surface)}
.sgs input[aria-invalid=true], .sgs select[aria-invalid=true]{border-color:var(--pt-color-danger-400)}
.sgs .sum{margin:0;font-size:9px;color:var(--pt-text-secondary);line-height:1.9}
.sgs .nav{display:flex;gap:5px}
.sgs .nav button{flex:1;padding:7px;border-radius:var(--pt-radius-full);font-size:9.5px;
font-weight:600}
.sgs .bk{border:1px solid var(--pt-border-default);background:transparent;
color:var(--pt-text-secondary)}
.sgs .nx{border:0;background:var(--pt-accent);color:var(--pt-color-ink-900)}
.sgs .nx:disabled, .sgs .bk:disabled{opacity:.45;cursor:not-allowed}
/* يعمل داخل إطار معزول — لا يعتمد على شيء خارجه */
/* pt-remount — يعاد التركيب عند تبديل اللغة */
(function () {
var host = document.querySelector('.sgs');
if (!host) return;
var snapshot = host.innerHTML;
var timers = [];
var ac = null;
/* الجسم كما كتبه المولِّد. يستقبل بدائل مظلَّلة لـ root وdocument
والمؤقّتات، فينظّف نفسه دون أن يعرف أنه قابل لإعادة التركيب. */
function run(root, document, setTimeout, setInterval) {
/* الخطأ يظهر بعد مغادرة الحقل لا مع كل حرف: القراءة أثناء الكتابة
تُشعر المستخدم بالفشل قبل أن ينتهي من الإدخال. */
function bind(input, check, msg) {
var box = input.closest('label') || input.parentElement;
var err = box.querySelector('.err') || box.parentElement.querySelector('.err');
var touched = false;
function paint() {
var good = check(input.value);
input.setAttribute('aria-invalid', String(touched && !good));
if (err) err.textContent = touched && !good ? msg : '';
return good;
}
input.addEventListener('blur', function () { touched = true; paint(); });
input.addEventListener('input', function () { if (touched) paint(); });
return paint;
}
var panes = [].slice.call(root.querySelectorAll('.pane'));
var bar = root.querySelector('.bar i'), pos = root.querySelector('.pos');
var bk = root.querySelector('.bk'), nx = root.querySelector('.nx');
var f1 = root.querySelector('.f1'), f2 = root.querySelector('.f2');
var sum = root.querySelector('.sum');
var step = 0;
var okName = function (v) { return v.trim().length >= 3; };
var okType = function (v) { return !!v; };
bind(f1, okName, 'الاسم قصير جدًّا');
bind(f2, okType, 'اختر نوع الاشتراك');
function valid(i) { return i === 0 ? okName(f1.value) : i === 1 ? okType(f2.value) : true; }
function paint() {
panes.forEach(function (p, i) { p.hidden = i !== step; });
bar.style.width = ((step + 1) / panes.length * 100) + '%';
pos.textContent = 'الخطوة ' + (step + 1) + ' من ' + panes.length;
bk.disabled = step === 0;
// لا تقدّم قبل صحّة الخطوة الحالية — وإلّا أُجّل الفشل لا مُنِع
nx.disabled = !valid(step);
nx.textContent = step === panes.length - 1 ? 'إنهاء' : 'التالي';
if (step === 2) sum.textContent = f1.value + ' · اشتراك ' + f2.value;
}
nx.addEventListener('click', function () {
if (!valid(step)) return;
if (step < panes.length - 1) { step++; paint(); }
});
bk.addEventListener('click', function () { if (step > 0) { step--; paint(); } });
[f1, f2].forEach(function (el) { el.addEventListener('input', paint); el.addEventListener('change', paint); });
paint();
}
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(); });
})();
# Multi-step Sign-up
**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:** Multi-step Sign-up — No step advances before the previous one is valid — progress is computed
**Why this component matters:** Splitting a long form reduces abandonment only if each step blocks on its own validity. Letting the user reach step three with an invalid step one just moves the failure later.
**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.