feat: implement interactive calendar view grids and confirmation action patterns

This commit is contained in:
2026-05-30 13:40:18 +05:00
parent 1e705053f5
commit f35908095c
5 changed files with 449 additions and 67 deletions
+134 -4
View File
@@ -113,10 +113,13 @@ document.addEventListener('DOMContentLoaded', () => {
const valueInput = picker.querySelector('.datepicker-value'); const valueInput = picker.querySelector('.datepicker-value');
const monthYearLabel = picker.querySelector('.datepicker-month-year'); const monthYearLabel = picker.querySelector('.datepicker-month-year');
const daysContainer = picker.querySelector('.datepicker-days'); const daysContainer = picker.querySelector('.datepicker-days');
if (!valueInput || !monthYearLabel || !daysContainer) return; const weekdaysRow = picker.querySelector('.datepicker-weekdays');
if (!valueInput || !daysContainer) return;
let currentYear = parseInt(picker.dataset.year); let currentYear = parseInt(picker.dataset.year);
let currentMonth = parseInt(picker.dataset.month); // 0-indexed let currentMonth = parseInt(picker.dataset.month); // 0-indexed
let view = picker.dataset.view || 'days';
picker.dataset.view = view;
if (isNaN(currentYear) || isNaN(currentMonth)) { if (isNaN(currentYear) || isNaN(currentMonth)) {
const val = valueInput.value; const val = valueInput.value;
@@ -131,10 +134,40 @@ document.addEventListener('DOMContentLoaded', () => {
const today = new Date(); const today = new Date();
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
// Update header label
if (monthYearLabel) {
if (view === 'days') {
monthYearLabel.textContent = `${monthNames[currentMonth]} ${currentYear}`; monthYearLabel.textContent = `${monthNames[currentMonth]} ${currentYear}`;
} else if (view === 'months') {
monthYearLabel.textContent = `${currentYear}`;
} else if (view === 'years') {
const startYear = currentYear - (currentYear % 12);
monthYearLabel.textContent = `${startYear} - ${startYear + 11}`;
}
}
// Toggle weekdays row visibility
if (weekdaysRow) {
if (view === 'days') {
weekdaysRow.classList.remove('hidden');
} else {
weekdaysRow.classList.add('hidden');
}
}
// Reset grid columns class based on active view mode
if (view === 'days') {
daysContainer.classList.remove('grid-cols-3');
daysContainer.classList.add('grid-cols-7');
} else {
daysContainer.classList.remove('grid-cols-7');
daysContainer.classList.add('grid-cols-3');
}
daysContainer.innerHTML = ''; daysContainer.innerHTML = '';
if (view === 'days') {
const firstDayIndex = new Date(currentYear, currentMonth, 1).getDay(); const firstDayIndex = new Date(currentYear, currentMonth, 1).getDay();
const totalDays = new Date(currentYear, currentMonth + 1, 0).getDate(); const totalDays = new Date(currentYear, currentMonth + 1, 0).getDate();
const prevTotalDays = new Date(currentYear, currentMonth, 0).getDate(); const prevTotalDays = new Date(currentYear, currentMonth, 0).getDate();
@@ -182,7 +215,34 @@ document.addEventListener('DOMContentLoaded', () => {
btn.textContent = i; btn.textContent = i;
daysContainer.appendChild(btn); daysContainer.appendChild(btn);
} }
} else if (view === 'months') {
const shortMonthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
for (let m = 0; m < 12; m++) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = `py-3.5 text-xs rounded-xl font-semibold transition text-center hover:bg-accent hover:text-accent-foreground ${
m === currentMonth ? 'bg-indigo-600 text-white font-bold' : 'text-slate-350'
}`;
btn.dataset.monthVal = m;
btn.textContent = shortMonthNames[m];
daysContainer.appendChild(btn);
} }
} else if (view === 'years') {
const startYear = currentYear - (currentYear % 12);
for (let y = 0; y < 12; y++) {
const yearNum = startYear + y;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = `py-3.5 text-xs rounded-xl font-semibold transition text-center hover:bg-accent hover:text-accent-foreground ${
yearNum === currentYear ? 'bg-indigo-600 text-white font-bold' : 'text-slate-350'
}`;
btn.dataset.yearVal = yearNum;
btn.textContent = yearNum;
daysContainer.appendChild(btn);
}
}
}
// Initialize custom date pickers // Initialize custom date pickers
document.querySelectorAll('.custom-datepicker').forEach(picker => { document.querySelectorAll('.custom-datepicker').forEach(picker => {
@@ -205,16 +265,24 @@ document.addEventListener('DOMContentLoaded', () => {
if (pop !== popover) pop.classList.add('hidden'); if (pop !== popover) pop.classList.add('hidden');
}); });
popover.classList.toggle('hidden'); const isHidden = popover.classList.toggle('hidden');
if (!isHidden) {
picker.dataset.view = 'days';
renderCalendar(picker);
}
return; return;
} }
// Prev Month // Prev Month / Year / Decade
const prevBtn = event.target.closest('.datepicker-prev'); const prevBtn = event.target.closest('.datepicker-prev');
if (prevBtn) { if (prevBtn) {
const picker = prevBtn.closest('.custom-datepicker'); const picker = prevBtn.closest('.custom-datepicker');
if (picker) {
let currentMonth = parseInt(picker.dataset.month); let currentMonth = parseInt(picker.dataset.month);
let currentYear = parseInt(picker.dataset.year); let currentYear = parseInt(picker.dataset.year);
const view = picker.dataset.view || 'days';
if (view === 'days') {
currentMonth--; currentMonth--;
if (currentMonth < 0) { if (currentMonth < 0) {
currentMonth = 11; currentMonth = 11;
@@ -222,16 +290,28 @@ document.addEventListener('DOMContentLoaded', () => {
} }
picker.dataset.month = currentMonth; picker.dataset.month = currentMonth;
picker.dataset.year = currentYear; picker.dataset.year = currentYear;
} else if (view === 'months') {
currentYear--;
picker.dataset.year = currentYear;
} else if (view === 'years') {
currentYear -= 12;
picker.dataset.year = currentYear;
}
renderCalendar(picker); renderCalendar(picker);
}
return; return;
} }
// Next Month // Next Month / Year / Decade
const nextBtn = event.target.closest('.datepicker-next'); const nextBtn = event.target.closest('.datepicker-next');
if (nextBtn) { if (nextBtn) {
const picker = nextBtn.closest('.custom-datepicker'); const picker = nextBtn.closest('.custom-datepicker');
if (picker) {
let currentMonth = parseInt(picker.dataset.month); let currentMonth = parseInt(picker.dataset.month);
let currentYear = parseInt(picker.dataset.year); let currentYear = parseInt(picker.dataset.year);
const view = picker.dataset.view || 'days';
if (view === 'days') {
currentMonth++; currentMonth++;
if (currentMonth > 11) { if (currentMonth > 11) {
currentMonth = 0; currentMonth = 0;
@@ -239,7 +319,31 @@ document.addEventListener('DOMContentLoaded', () => {
} }
picker.dataset.month = currentMonth; picker.dataset.month = currentMonth;
picker.dataset.year = currentYear; picker.dataset.year = currentYear;
} else if (view === 'months') {
currentYear++;
picker.dataset.year = currentYear;
} else if (view === 'years') {
currentYear += 12;
picker.dataset.year = currentYear;
}
renderCalendar(picker); renderCalendar(picker);
}
return;
}
// Header click (switch view mode)
const headerBtn = event.target.closest('.datepicker-month-year');
if (headerBtn) {
const picker = headerBtn.closest('.custom-datepicker');
if (picker) {
const view = picker.dataset.view || 'days';
if (view === 'days') {
picker.dataset.view = 'months';
} else if (view === 'months') {
picker.dataset.view = 'years';
}
renderCalendar(picker);
}
return; return;
} }
@@ -260,6 +364,32 @@ document.addEventListener('DOMContentLoaded', () => {
return; return;
} }
// Month click
const monthBtn = event.target.closest('.datepicker-days button[data-month-val]');
if (monthBtn) {
const picker = monthBtn.closest('.custom-datepicker');
if (picker) {
const monthVal = parseInt(monthBtn.dataset.monthVal);
picker.dataset.month = monthVal;
picker.dataset.view = 'days';
renderCalendar(picker);
}
return;
}
// Year click
const yearBtn = event.target.closest('.datepicker-days button[data-year-val]');
if (yearBtn) {
const picker = yearBtn.closest('.custom-datepicker');
if (picker) {
const yearVal = parseInt(yearBtn.dataset.yearVal);
picker.dataset.year = yearVal;
picker.dataset.view = 'months';
renderCalendar(picker);
}
return;
}
// Close outside // Close outside
const openPickerPopover = document.querySelector('.datepicker-popover:not(.hidden)'); const openPickerPopover = document.querySelector('.datepicker-popover:not(.hidden)');
if (openPickerPopover && !event.target.closest('.custom-datepicker')) { if (openPickerPopover && !event.target.closest('.custom-datepicker')) {
+1 -1
View File
@@ -140,7 +140,7 @@
<td class="py-3 font-mono text-indigo-400 font-semibold">.datepicker-month-year</td> <td class="py-3 font-mono text-indigo-400 font-semibold">.datepicker-month-year</td>
<td class="py-3 font-semibold text-slate-300 text-[10px] uppercase">Class</td> <td class="py-3 font-semibold text-slate-300 text-[10px] uppercase">Class</td>
<td class="py-3 text-slate-400"> <td class="py-3 text-slate-400">
Display title label updated dynamically with current month and year (e.g. "May 2026"). Clickable header button. Toggles view grid modes between days selection, months selection, and years selection.
</td> </td>
</tr> </tr>
<tr> <tr>
+9 -2
View File
@@ -97,6 +97,12 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro modal_close_only() %}
</div>
</div>
{% endmacro %}
{% macro sheet_open(id, title, max_width_class="max-w-sm") %} {% macro sheet_open(id, title, max_width_class="max-w-sm") %}
<div id="{{ id }}" class="sheet-dialog fixed inset-0 z-50 overflow-hidden hidden" role="dialog" aria-modal="true"> <div id="{{ id }}" class="sheet-dialog fixed inset-0 z-50 overflow-hidden hidden" role="dialog" aria-modal="true">
<div class="sheet-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm opacity-0 transition-opacity duration-300 animate-fade-in"></div> <div class="sheet-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm opacity-0 transition-opacity duration-300 animate-fade-in"></div>
@@ -278,12 +284,13 @@
<button type="button" class="datepicker-prev p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition"> <button type="button" class="datepicker-prev p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg> <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
</button> </button>
<span class="datepicker-month-year text-xs font-bold text-slate-200"></span> <button type="button" class="datepicker-month-year text-xs font-bold text-slate-200 hover:bg-secondary/40 px-2 py-1 rounded-lg transition outline-none">
</button>
<button type="button" class="datepicker-next p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition"> <button type="button" class="datepicker-next p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg> <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>
</button> </button>
</div> </div>
<div class="grid grid-cols-7 gap-1 text-center text-[10px] font-bold text-slate-500 mb-2"> <div class="datepicker-weekdays grid grid-cols-7 gap-1 text-center text-[10px] font-bold text-slate-500 mb-2">
<span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span> <span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span>
</div> </div>
<div class="datepicker-days grid grid-cols-7 gap-1 text-center"></div> <div class="datepicker-days grid grid-cols-7 gap-1 text-center"></div>
+119
View File
@@ -123,6 +123,107 @@
</div> </div>
</section> </section>
<!-- Confirmation Action Pattern -->
<section class="space-y-4 border-t border-border/60 pt-6">
<h2 class="text-lg font-bold text-slate-200">Confirmation Actions (e.g., Deletes)</h2>
<p class="text-xs text-muted-foreground leading-relaxed">
To prevent accidental actions, wrap destructive or state-changing actions (such as deletions) in a confirmation dialog modal. Since modals are populated inside the DOM structure, you can either wrap the action inside a standard HTML form or use HTMX for AJAX deletions.
</p>
<div class="border border-border rounded-3xl p-5 bg-secondary/10 space-y-4">
<!-- Tab Headers -->
<div class="flex border-b border-border/60 pb-1.5">
<button class="px-3 py-1.5 text-xs font-semibold border-b-2 border-sky-500 text-sky-400" onclick="toggleWikiTabs(this, 'confirm-demo-pane')">Interactive Demo</button>
<button class="px-3 py-1.5 text-xs font-semibold border-b-2 border-transparent text-muted-foreground hover:text-muted-foreground" onclick="toggleWikiTabs(this, 'confirm-markup-form')">Form POST (Standard)</button>
<button class="px-3 py-1.5 text-xs font-semibold border-b-2 border-transparent text-muted-foreground hover:text-muted-foreground" onclick="toggleWikiTabs(this, 'confirm-markup-htmx')">HTMX Delete (AJAX)</button>
</div>
<!-- Interactive Demo Pane -->
<div id="confirm-demo-pane" class="wiki-pane py-2">
<div class="flex items-center gap-4">
<span class="text-xs text-slate-400 font-medium">Demo Project Entity:</span>
<div class="flex items-center gap-3 px-3 py-2 bg-secondary/20 border border-border rounded-xl">
<span class="text-xs font-semibold text-slate-200">Important Draft.docx</span>
{{ ui::modal_trigger(target_id="wiki-confirm-delete-modal", label="Delete Draft", variant="destructive", extra_class="!px-2.5 !py-1 text-[10px]") }}
</div>
</div>
</div>
<!-- Form POST Code Markup -->
<div id="confirm-markup-form" class="wiki-pane hidden space-y-4">
<div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-slate-455 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code
</button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;!-- 1. The Trigger Button (usually part of a table or list) --&gt;
{{ "{{" }} ui::modal_trigger(target_id="delete-confirm-modal-123", label="Delete Entity", variant="destructive") {{ "}}" }}
&lt;!-- 2. The Confirmation Dialog Modal with embedded Form --&gt;
{{ "{%" }} call ui::modal_open(id="delete-confirm-modal-123", title="Confirm Deletion") {{ "%}" }}
&lt;form action="/entities/123/delete" method="POST" class="space-y-4 mt-2"&gt;
&lt;p class="text-xs text-muted-foreground leading-relaxed text-center"&gt;
Are you sure you want to permanently delete this entity? This action cannot be undone.
&lt;/p&gt;
&lt;div class="flex gap-3 mt-4"&gt;
&lt;!-- Cancel Button (closes the modal via .modal-close) --&gt;
&lt;button type="button" class="modal-close flex-1 py-2 rounded-xl bg-secondary border border-border hover:bg-secondary/80 transition text-xs font-semibold text-slate-200"&gt;
Cancel
&lt;/button&gt;
&lt;!-- Submit Button (performs POST redirect) --&gt;
&lt;button type="submit" class="flex-1 py-2 rounded-xl bg-destructive text-destructive-foreground hover:opacity-90 shadow-md transition text-xs font-bold"&gt;
Confirm Delete
&lt;/button&gt;
&lt;/div&gt;
&lt;/form&gt;
{{ "{%" }} call ui::modal_close_only() {{ "%}" }}</code></pre>
</div>
</div>
<!-- HTMX AJAX Code Markup -->
<div id="confirm-markup-htmx" class="wiki-pane hidden space-y-4">
<div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-slate-455 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code
</button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;!-- 1. Trigger Button --&gt;
{{ "{{" }} ui::modal_trigger(target_id="delete-htmx-modal-123", label="Delete Entity", variant="destructive") {{ "}}" }}
&lt;!-- 2. The Confirmation Dialog Modal with HTMX triggers --&gt;
{{ "{%" }} call ui::modal_open(id="delete-htmx-modal-123", title="Confirm Deletion") {{ "%}" }}
&lt;div class="space-y-4 mt-2"&gt;
&lt;p class="text-xs text-muted-foreground leading-relaxed text-center"&gt;
Are you sure you want to delete this entity? This triggers an async HTMX request and removes the element.
&lt;/p&gt;
&lt;div class="flex gap-3 mt-4"&gt;
&lt;!-- Cancel Button --&gt;
&lt;button type="button" class="modal-close flex-1 py-2 rounded-xl bg-secondary border border-border hover:bg-secondary/80 transition text-xs font-semibold text-slate-200"&gt;
Cancel
&lt;/button&gt;
&lt;!-- HTMX Action button. Adding .modal-close closes the modal animation instantly as the request goes out --&gt;
&lt;button type="button"
hx-delete="/entities/123"
hx-target="#entity-row-123"
hx-swap="outerHTML"
class="modal-close flex-1 py-2 rounded-xl bg-destructive text-destructive-foreground hover:opacity-90 shadow-md transition text-xs font-bold"&gt;
Confirm Delete
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
{{ "{%" }} call ui::modal_close_only() {{ "%}" }}</code></pre>
</div>
</div>
</div>
</section>
</div> </div>
</div> </div>
@@ -131,6 +232,24 @@
<p class="text-xs text-slate-405 mt-2 leading-relaxed">This modal is animated and handled globally by <code>components.js</code>. Pressing escape or clicking outside closes it instantly.</p> <p class="text-xs text-slate-405 mt-2 leading-relaxed">This modal is animated and handled globally by <code>components.js</code>. Pressing escape or clicking outside closes it instantly.</p>
{{ ui::modal_close(close_label="Dismiss Modal") }} {{ ui::modal_close(close_label="Dismiss Modal") }}
<!-- Interactive Confirm Delete Modal for the Demo -->
{{ ui::modal_open(id="wiki-confirm-delete-modal", title="Confirm Deletion") }}
<div class="space-y-4 mt-2">
<p class="text-xs text-slate-400 leading-relaxed text-center">
Are you sure you want to permanently delete <strong class="text-slate-200">"Important Draft.docx"</strong>? This action cannot be undone.
</p>
<div class="flex gap-3 mt-4">
<button class="modal-close flex-1 py-2 rounded-xl bg-secondary border border-border hover:bg-secondary/80 transition text-xs font-semibold text-slate-200">
Cancel
</button>
<button class="modal-close flex-1 py-2 rounded-xl bg-rose-600 hover:bg-rose-500 text-white transition text-xs font-bold shadow-md" onclick="showToast('Entity &ldquo;Important Draft.docx&rdquo; deleted successfully!')">
Confirm Delete
</button>
</div>
</div>
{{ ui::modal_close_only() }}
<script> <script>
function toggleWikiTabs(btn, paneId) { function toggleWikiTabs(btn, paneId) {
const card = btn.closest('.border-border'); const card = btn.closest('.border-border');
+126
View File
@@ -120,6 +120,111 @@
</div> </div>
</section> </section>
<!-- Confirmation Action Pattern -->
<section class="space-y-4 border-t border-border/60 pt-6">
<h2 class="text-lg font-bold text-slate-200">Confirmation Actions (e.g., Deletes)</h2>
<p class="text-xs text-muted-foreground leading-relaxed">
Slide-over sheets are highly effective for confirmation dialogs when you want to provide more context, metadata details, or descriptive warnings about the entity being impacted before executing the action.
</p>
<div class="border border-border rounded-3xl p-5 bg-secondary/10 space-y-4">
<!-- Tab Headers -->
<div class="flex border-b border-border/60 pb-1.5">
<button class="px-3 py-1.5 text-xs font-semibold border-b-2 border-sky-500 text-sky-400" onclick="toggleWikiTabs(this, 'confirm-sheet-demo-pane')">Interactive Demo</button>
<button class="px-3 py-1.5 text-xs font-semibold border-b-2 border-transparent text-muted-foreground hover:text-muted-foreground" onclick="toggleWikiTabs(this, 'confirm-sheet-markup-form')">Form POST (Standard)</button>
<button class="px-3 py-1.5 text-xs font-semibold border-b-2 border-transparent text-muted-foreground hover:text-muted-foreground" onclick="toggleWikiTabs(this, 'confirm-sheet-markup-htmx')">HTMX Delete (AJAX)</button>
</div>
<!-- Interactive Demo Pane -->
<div id="confirm-sheet-demo-pane" class="wiki-pane py-2">
<div class="flex items-center gap-4">
<span class="text-xs text-slate-400 font-medium">Demo Database Entity:</span>
<div class="flex items-center gap-3 px-3 py-2 bg-secondary/20 border border-border rounded-xl">
<span class="text-xs font-semibold text-slate-200">User Account: ahmed_shaamil</span>
{{ ui::sheet_trigger(target_id="wiki-confirm-delete-sheet", label="Delete User", variant="destructive", extra_class="!px-2.5 !py-1 text-[10px]") }}
</div>
</div>
</div>
<!-- Form POST Code Markup -->
<div id="confirm-sheet-markup-form" class="wiki-pane hidden space-y-4">
<div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-slate-455 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code
</button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;!-- 1. Trigger Button --&gt;
{{ "{{" }} ui::sheet_trigger(target_id="delete-sheet-confirm-123", label="Delete Account", variant="destructive") {{ "}}" }}
&lt;!-- 2. Slide Drawer with Form --&gt;
{{ "{%" }} call ui::sheet_open(id="delete-sheet-confirm-123", title="Confirm Account Deletion", max_width_class="max-w-md") {{ "%}" }}
&lt;form action="/accounts/123/delete" method="POST" class="flex flex-col justify-between h-full space-y-6"&gt;
&lt;div class="space-y-4 mt-2"&gt;
&lt;p class="text-xs text-muted-foreground leading-relaxed"&gt;
You are about to delete this account. This will invalidate all associated access tokens and permanently purge database profiles.
&lt;/p&gt;
&lt;div class="p-3.5 rounded-2xl bg-destructive/10 border border-destructive/20 text-[11px] text-destructive-foreground leading-normal"&gt;
Warning: This action is irreversible. Enter confirmation parameters if required.
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="flex gap-3 pt-4 border-t border-border mt-auto"&gt;
&lt;button type="button" class="sheet-close flex-1 py-2.5 rounded-xl bg-secondary border border-border hover:bg-secondary/80 transition text-xs font-semibold text-slate-200"&gt;
Cancel
&lt;/button&gt;
&lt;button type="submit" class="flex-1 py-2.5 rounded-xl bg-destructive text-destructive-foreground hover:opacity-90 shadow-md transition text-xs font-bold"&gt;
Confirm Delete
&lt;/button&gt;
&lt;/div&gt;
&lt;/form&gt;
{{ "{%" }} call ui::sheet_close(save_label="") {{ "%}" }}</code></pre>
</div>
</div>
<!-- HTMX AJAX Code Markup -->
<div id="confirm-sheet-markup-htmx" class="wiki-pane hidden space-y-4">
<div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-slate-455 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code
</button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;!-- 1. Trigger Button --&gt;
{{ "{{" }} ui::sheet_trigger(target_id="delete-sheet-htmx-123", label="Delete Account", variant="destructive") {{ "}}" }}
&lt;!-- 2. Slide Drawer with HTMX --&gt;
{{ "{%" }} call ui::sheet_open(id="delete-sheet-htmx-123", title="Confirm Account Deletion", max_width_class="max-w-md") {{ "%}" }}
&lt;div class="flex flex-col justify-between h-full space-y-6"&gt;
&lt;div class="space-y-4 mt-2"&gt;
&lt;p class="text-xs text-muted-foreground leading-relaxed"&gt;
Are you sure you want to delete this account? This triggers an async HTMX request and removes the element.
&lt;/p&gt;
&lt;/div&gt;
&lt;div class="flex gap-3 pt-4 border-t border-border mt-auto"&gt;
&lt;button type="button" class="sheet-close flex-1 py-2.5 rounded-xl bg-secondary border border-border hover:bg-secondary/80 transition text-xs font-semibold text-slate-200"&gt;
Cancel
&lt;/button&gt;
&lt;button type="button"
hx-delete="/accounts/123"
hx-target="#account-row-123"
hx-swap="outerHTML"
class="sheet-close flex-1 py-2.5 rounded-xl bg-destructive text-destructive-foreground hover:opacity-90 shadow-md transition text-xs font-bold"&gt;
Confirm Delete
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
{{ "{%" }} call ui::sheet_close(save_label="") {{ "%}" }}</code></pre>
</div>
</div>
</div>
</section>
</div> </div>
</div> </div>
@@ -128,6 +233,27 @@
<p class="text-xs text-slate-405 leading-relaxed">This slide-over panel demonstrates real-time sidebar parameter adjustments, sliding in from the right edge with hardware transitions.</p> <p class="text-xs text-slate-405 leading-relaxed">This slide-over panel demonstrates real-time sidebar parameter adjustments, sliding in from the right edge with hardware transitions.</p>
{{ ui::sheet_close(save_label="Save Properties") }} {{ ui::sheet_close(save_label="Save Properties") }}
<!-- Interactive Confirm Delete Sheet Element -->
{{ ui::sheet_open(id="wiki-confirm-delete-sheet", title="Confirm Account Deletion", max_width_class="max-w-sm") }}
<p class="text-xs text-slate-400 leading-relaxed mt-2">
Are you sure you want to permanently delete user <strong class="text-slate-200">ahmed_shaamil</strong>? All database records and linked files will be removed.
</p>
</div> <!-- Closes .space-y-4 -->
<!-- Custom Footer Buttons -->
<div class="flex gap-3 pt-4 border-t border-border mt-auto">
<button class="sheet-close flex-1 py-2.5 rounded-xl bg-secondary border border-border hover:bg-secondary/80 transition text-xs font-semibold text-slate-200">
Cancel
</button>
<button class="sheet-close flex-1 py-2.5 rounded-xl bg-rose-600 hover:bg-rose-500 text-white transition text-xs font-bold shadow-lg shadow-rose-650/10" onclick="showToast('User account ahmed_shaamil deleted successfully!')">
Confirm Delete
</button>
</div>
</div> <!-- Closes .sheet-content -->
</div> <!-- Closes .absolute -->
</div> <!-- Closes .sheet-dialog -->
<script> <script>
function toggleWikiTabs(btn, paneId) { function toggleWikiTabs(btn, paneId) {
const card = btn.closest('.border-border'); const card = btn.closest('.border-border');