7
Komponen UI Pelajaran
Dua jalur tampilan pelajaran memakai komponen yang berbeda. Latihan pemula dirender oleh PracticeSheet, pertemuan lanjutan dirender oleh MeetingLesson yang menyusun setiap langkah sebagai tab, dan satu halaman mushaf di keduanya selalu dirender oleh MushafPageView. Semua jalur ini berada di dalam FullscreenFrame yang dipasang sekali di layout, sehingga berpindah halaman tidak mematikan mode layar penuh (src/app/pemula/layout.tsx:1-51, src/app/lanjutan/[slug]/layout.tsx:1-52).
Komposisi komponen
FullscreenFrame menyimpan keadaan layar penuh dan membagikannya lewat FullscreenContext, dengan useFullscreen sebagai pembacanya (src/components/fullscreen-frame.tsx:7-113). Karena itu PracticeSheet, MeetingTabs, dan FullscreenToggle dapat menyesuaikan tampilannya tanpa saling menerima props.
graph TD
Frame["FullscreenFrame"] --> Sheet["PracticeSheet"]
Frame --> Lesson["MeetingLesson"]
Frame --> Toggle["FullscreenToggle"]
Sheet --> Index["drillIndex"]
Sheet --> Overall["OverallProgress"]
Lesson --> Tabs["MeetingTabs"]
Tabs --> Body["StepBody"]
Body --> Materi["Materi"]
Body --> Drills["Drills"]
Body --> Practice["MushafPractice"]
Practice --> Picker["MushafPicker"]
Picker --> Page["MushafPageView"]
Page --> Opening["SurahOpening"]
Page --> Marker["AyahMarker"]
style Frame fill:#f59e0b,color:#000
style Toggle fill:#f59e0b,color:#000
style Sheet fill:#2d7d46,color:#fff
style Lesson fill:#2d7d46,color:#fff
style Index fill:#8b5cf6,color:#fff
style Overall fill:#8b5cf6,color:#fff
style Page fill:#8b5cf6,color:#fff
style Marker fill:#8b5cf6,color:#fff
style Opening fill:#8b5cf6,color:#fff
%% fig: Komposisi komponen tampilan pelajaran
%% desc: Satu FullscreenFrame membungkus PracticeSheet dan MeetingLesson; MeetingLesson menurunkan tab, langkah, dan halaman mushaf beserta penanda ayat.MeetingLesson hanya penyusun tab. Setiap MeetingStep diubah menjadi satu MeetingTab oleh infoOf, lalu isinya dipilih StepBody menurut step.kind (src/components/lesson/meeting-lesson.tsx:239-2614). Langkah mushaf tidak merender MushafPageView secara langsung, melainkan lewat MushafPicker yang menampilkan satu halaman pada satu waktu dan menyediakan baris tombol nomor halaman bila halamannya lebih dari satu (src/components/lesson/mushaf-picker.tsx:6-115).
step.kind | id tab | Label |
|---|---|---|
materi | materi | Materi |
drills | latihan-ayat | Latihan ayat |
mushaf | praktik-mushaf | Praktik mushaf |
task | tugas-mandiri | Tugas mandiri |
other | langkah-<nomor> | step.title atau Catatan |
Pemetaan di atas berasal dari stepInfo dan infoOf (src/components/lesson/meeting-lesson.tsx:14-246).
PracticeSheet: satu latihan sebagai lembar drill
| Prop | Tipe | Dipakai untuk |
|---|---|---|
slug | string | kunci progres dan tautan pelajaran terakhir |
number | number | nomor latihan, juga dipakai practicePart |
title | string | judul pada header |
guide | string[] | isi panel Petunjuk |
sections | SheetSection[] | label, sound key, dan baris drill |
previous, next | Neighbour | navigasi di layar penuh dan ajakan lanjut |
totals | Record<string, number> | OverallProgress di layar penuh |
part | CurrentPart | bagian kurikulum yang sedang dibuka |
Seluruh prop tersebut dideklarasikan pada tipe Props (src/components/practice/practice-sheet.tsx:69-807). Tiap section membawa label, key berisi pasangan bacaan dan bentuk huruf, serta rows berupa kisi teks Arab (src/components/practice/practice-sheet.tsx:26-318).
PracticeSheet hanya menghitung section yang punya rows sebagai drill, lalu menomornya berurutan supaya tanda lancar tidak bergeser ketika label diubah (src/components/practice/practice-sheet.tsx:99-1039). Saat latihan dibuka, visitPractice menyimpannya sebagai yang terakhir dipelajari (src/components/practice/practice-sheet.tsx:109-11210); cara progres disimpan di perangkat dibahas di Progres Belajar di Perangkat.
Label yang berbentuk sound key dirender berbeda dari label biasa. Barisnya memakai dir="rtl" dan forms dibalik, sehingga entri fathah berada di tepi kanan dan dhammah di tepi kiri (src/components/practice/practice-sheet.tsx:213-24111). Baris drill dirender sebagai paragraf dir="rtl" berukuran text-[1.75rem] yang membesar di layar penuh, sedangkan pairs membungkus grup latihan dua per dua agar baris empat grup pecah 2 + 2 (src/components/practice/practice-sheet.tsx:258-27112, src/components/practice/practice-sheet.tsx:43-4813).
Tombol penanda hanya muncul pada section yang masuk drillIndex. aria-pressed mengikuti isDone dan teksnya berganti dari Tandai lancar menjadi Sudah lancar (src/components/practice/practice-sheet.tsx:289-30714). Setelah semua bagian ditandai, footer menampilkan ajakan lanjut ke latihan berikutnya dan tombol resetPractice (src/components/practice/practice-sheet.tsx:312-34515). Tombol layar penuh di header ditulis sendiri oleh PracticeSheet, bukan memakai FullscreenToggle, dan memilih ikon Maximize2 atau Minimize2 menurut fullscreen.active (src/components/practice/practice-sheet.tsx:167-17916).
MeetingLesson dan MeetingTabs
MeetingTabs menyimpan tab aktif sebagai state lokal active dan merender semua panel sekaligus, dengan hidden pada panel yang tidak aktif (src/components/lesson/meeting-tabs.tsx:20-5317). Saat tab diganti, open mengembalikan posisi ke atas kontainer bila pembaca sudah melewati tablist (src/components/lesson/meeting-tabs.tsx:14-1818). Di layar penuh, FullscreenToggle muncul di ujung baris tab karena header halaman disembunyikan (src/components/lesson/meeting-tabs.tsx:44-4519).
Langkah berjenis other yang datang berurutan digabung ke tab terakhir, jadi catatan seperti form ujian berbagi satu tab di bawah judulnya sendiri (src/components/lesson/meeting-lesson.tsx:271-29420).
Teks materi tidak dirender sebagai satu paragraf. flow memecahnya menjadi potongan Latin dan potongan Arab (src/components/lesson/meeting-lesson.tsx:36-4721), lalu ArabicLine mengubah huruf tunggal menjadi kotak size-14 dan kata utuh menjadi teks font-arabic text-3xl (src/components/lesson/meeting-lesson.tsx:57-6422). Praktik mushaf di dalam pertemuan menyetel --mushaf-size ke 1.75rem, lebih kecil dari ukuran pembaca mushaf, supaya halaman pas di samping materi (src/components/lesson/meeting-lesson.tsx:220-23723).
MushafPageView: satu halaman mushaf
MushafPageView merender satu halaman cetak: bingkai luar, baris juz dan nama surah, ayat per baris, lalu nomor halaman dalam angka Arab di bawah (src/components/mushaf/mushaf-page-view.tsx:44-5624). Nama surah pada baris atas diambil dari surahNames, yang mengelompokkan ayat halaman lewat groupBySurah (src/components/mushaf/mushaf-page-view.tsx:725).
Garis bawah tiap baris dibuat dari background yang berulang setiap 2.25em, sehingga garis mengikuti ukuran baca --mushaf-size dan bukan jumlah baris (src/components/mushaf/mushaf-page-view.tsx:58-7426). Setiap ayat diberi AyahMarker berupa bintang dengan nomor ayat, dan surah baru dibuka oleh SurahOpening yang menyertakan basmalah bila showsBismillah benar (src/components/mushaf/mushaf-page-view.tsx:9-4127). Asal data halaman dan rentang juz dijelaskan di Data Qur'an dan Mushaf.
Keadaan layar penuh
FullscreenFrame memisahkan dua jalur: native yang mengikuti document.fullscreenElement dan overlay untuk peramban tanpa element fullscreen, sedangkan active adalah gabungan keduanya (src/components/fullscreen-frame.tsx:18-2228). Yang diminta masuk fullscreen adalah div pembungkus milik frame itu sendiri, dan saat aktif div tersebut menerima atribut data-fullview (src/components/fullscreen-frame.tsx:79-8929).
stateDiagram-v2
[*] --> normal
normal --> native: requestFullscreen berhasil
normal --> overlay: requestFullscreen ditolak
native --> normal: exitFullscreen
overlay --> normal: tombol Keluar
overlay --> normal: tombol Escape
style normal fill:#2d7d46,color:#fff
style native fill:#8b5cf6,color:#fff
style overlay fill:#f59e0b,color:#000
%% fig: Perpindahan keadaan layar penuh
%% desc: Frame masuk mode native bila peramban mengizinkan, atau jatuh ke overlay yang ditutup tombol Keluar maupun Escape.Varian fullview: didefinisikan sebagai @custom-variant fullview (&:where([data-fullview] *)), jadi semua kelas fullview: hanya berlaku untuk turunan frame yang sedang aktif (src/app/globals.css:6-730). FullscreenToggle adalah tombol siap pakai untuk frame ini: labelnya berganti antara Layar penuh dan Keluar mengikuti active (src/components/fullscreen-frame.tsx:93-10431).
| Elemen | Kelas atau perilaku | Efek di layar penuh | Rujukan |
|---|---|---|---|
artikel PracticeSheet | fullview:border-0 fullview:bg-transparent fullview:shadow-none fullview:max-w-[110rem] | kartu dilepas dan lembar melebar | src/components/practice/practice-sheet.tsx:115-11632 |
header PracticeSheet | fullview:rounded-none fullview:bg-paper | sudut header tidak lagi membulat | src/components/practice/practice-sheet.tsx:115-11632 |
OverallProgress di header | dirender hanya bila fullscreen.active | progres keseluruhan bagian ikut tampil | src/components/practice/practice-sheet.tsx:138-14233 |
tautan previous dan next | dirender hanya bila fullscreen.active | navigasi tetap terjangkau | src/components/practice/practice-sheet.tsx:147-16634 |
kontainer MeetingTabs | fullview:mt-0 | jarak atas dihapus | src/components/lesson/meeting-lesson.tsx:296-30835 |
| header halaman lanjutan | fullview:hidden | judul dan tombol di atas materi disembunyikan | src/app/lanjutan/[slug]/page.tsx:14636 |
| footer dan tautan tetangga | fullview:hidden | chrome situs disembunyikan | src/app/lanjutan/[slug]/page.tsx:17337 |
⚠️ Bila
requestFullscreentidak tersedia, misalnya di iPhone Safari,togglememasang overlayfixed inset-0 z-[70] overflow-y-auto. Overlay tidak punya chrome peramban untuk keluar, jadi Escape menutupnya dandocument.body.style.overflowdipulihkan saat overlay dilepas (src/components/fullscreen-frame.tsx:31-6438).