Removing Alpine.js entirely

This commit is contained in:
Ronan Berder
2025-06-20 08:29:29 +08:00
parent 9c01b1db6d
commit cfa7dcbd54
14 changed files with 165 additions and 123 deletions
-2
View File
@@ -75,8 +75,6 @@
<script src="/assets/js/sidebar.js" defer></script>
<script src="/assets/js/tabs.js" defer></script>
<script src="/assets/js/toast.js" defer></script>
{# Alpine.js #}
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
{# HTMX #}
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
{# Highlight.js #}
+15 -20
View File
@@ -1,28 +1,23 @@
{% macro code_block(code, language="html", class="my-4") %}
<div
class="relative {{ class }}"
x-data="{
copied: false,
copy() {
const codeElement = $refs.code;
if (!codeElement) return;
navigator.clipboard.writeText(codeElement.textContent || '').then(() => {
this.copied = true;
setTimeout(() => { this.copied = false }, 2000);
<div class="relative {{ class }}">
<pre class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- code | escape -}}</code></pre>
<button
onclick="
const button = this;
const code = this.parentElement.querySelector('pre code');
if (!code) return;
navigator.clipboard.writeText(code.textContent || '').then(() => {
button.classList.add('copied');
setTimeout(() => { button.classList.remove('copied') }, 2000);
}).catch(err => {
console.error('Failed to copy text: ', err);
});
}
}"
>
<pre x-ref="code" class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- code | escape -}}</code></pre>
<button
@click="copy()"
class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground"
"
class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground group"
>
{% lucide "clipboard", {"x-show": "!copied" } %}
{% lucide "check", {"x-show": "copied" } %}
{% lucide "clipboard", {"class": "group-[.copied]:hidden" } %}
{% lucide "check", {"class": "hidden group-[.copied]:block" } %}
</button>
</div>
{% endmacro %}
@@ -7,20 +7,7 @@
</header>
<div class="p-4">
<div class="group grid w-full max-w-xl gap-4">
<section
x-init
@click="
const clickedSummary = $event.target.closest('summary');
if (!clickedSummary) return;
const clickedDetails = clickedSummary.closest('details');
if (!clickedDetails) return;
$el.querySelectorAll('details').forEach(detailsEl => {
if (detailsEl !== clickedDetails) {
detailsEl.removeAttribute('open');
}
});
"
>
<section class="accordion">
<details class="group border-b last:border-b-0">
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
@@ -55,20 +42,7 @@
</section>
</details>
</section>
<section
x-init
@click="
const clickedSummary = $event.target.closest('summary');
if (!clickedSummary) return;
const clickedDetails = clickedSummary.closest('details');
if (!clickedDetails) return;
$el.querySelectorAll('details').forEach(detailsEl => {
if (detailsEl !== clickedDetails) {
detailsEl.removeAttribute('open');
}
});
"
>
<section class="accordion">
<details class="group border-b last:border-b-0">
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
@@ -92,6 +66,24 @@
</section>
</details>
</section>
<script>
(() => {
const accordions = document.querySelectorAll('.accordion');
accordions.forEach(accordion => {
accordion.addEventListener('click', (event) => {
const summary = event.target.closest('summary');
if (!summary) return;
const details = summary.closest('details');
if (!details) return;
accordion.querySelectorAll('details').forEach(detailsEl => {
if (detailsEl !== details) {
detailsEl.removeAttribute('open');
}
});
});
});
})();
</script>
</div>
</div>
</section>
@@ -9,8 +9,8 @@
</header>
<div class="p-4">
{% set footer %}
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn-primary" @click="open = false">Continue</button>
<button class="btn-outline" onclick="document.getElementById('alert-dialog-demo').close()">Cancel</button>
<button class="btn-primary" onclick="document.getElementById('alert-dialog-demo').close()">Continue</button>
{% endset %}
{{ dialog(
id="alert-dialog-demo",
@@ -10,8 +10,8 @@
<div class="p-4">
<div class="flex flex-wrap items-center gap-4">
{% set footer %}
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn" @click="open = false">Save changes</button>
<button class="btn-outline" onclick="document.getElementById('demo-dialog-edit-profile').close()">Cancel</button>
<button class="btn" onclick="document.getElementById('demo-dialog-edit-profile').close()">Save changes</button>
{% endset %}
{% call dialog(
id="demo-dialog-edit-profile",
@@ -76,9 +76,6 @@
role="menuitemcheckbox"
aria-checked="true"
class="group"
x-data="{ checked: true }"
@click="checked = !checked"
:aria-checked="checked"
>
{% lucide "check", {
"class": "invisible group-aria-checked:visible",
@@ -93,9 +90,6 @@
aria-checked="false"
class="group"
aria-disabled="true"
x-data="{ checked: false }"
@click="checked = !checked"
:aria-checked="checked"
>
{% lucide "check", {
"class": "invisible group-aria-checked:visible",
@@ -109,9 +103,6 @@
role="menuitemcheckbox"
aria-checked="false"
class="group"
x-data="{ checked: false }"
@click="checked = !checked"
:aria-checked="checked"
>
{% lucide "check", {
"class": "invisible group-aria-checked:visible",
@@ -130,6 +121,19 @@
</div>
{% endcall %}
<script>
(() => {
const dropdownMenu = document.querySelector('#dropdown-menu-checkboxes');
const checkboxes = dropdownMenu.querySelectorAll('div[role="menuitemcheckbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', () => {
const isChecked = checkbox.getAttribute('aria-checked') === 'true';
checkbox.setAttribute('aria-checked', !isChecked);
});
});
})();
</script>
{% set profile_menu %}
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
@@ -164,19 +168,18 @@
{% endset %}
{% call dropdown_menu(
id="dropdown-menu-radio-group",
trigger="Radio Group",
trigger_attrs={"class": "btn-outline"},
popover_attrs={"class": "min-w-56"}
) %}
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
<div role="group" aria-labelledby="position-options">
<span id="position-options" role="heading">Panel Position</span>
<hr role="separator">
<div
role="menuitemradio"
aria-checked="false"
class="group"
@click="checked = 'top'"
:aria-checked="checked == 'top'"
>
<div class="size-4 flex items-center justify-center">
<div
@@ -190,10 +193,8 @@
</div>
<div
role="menuitemradio"
aria-checked="false"
aria-checked="true"
class="group"
@click="checked = 'bottom'"
:aria-checked="checked == 'bottom'"
>
<div class="size-4 flex items-center justify-center">
<div
@@ -209,8 +210,6 @@
role="menuitemradio"
aria-checked="false"
class="group"
@click="checked = 'right'"
:aria-checked="checked == 'right'"
>
<div class="size-4 flex items-center justify-center">
<div
@@ -224,6 +223,20 @@
</div>
</div>
{% endcall %}
<script>
(() => {
const dropdownMenu = document.querySelector('#dropdown-menu-radio-group');
const radioButtons = dropdownMenu.querySelectorAll('div[role="menuitemradio"]');
radioButtons.forEach(radioButton => {
radioButton.addEventListener('click', () => {
radioButtons.forEach(radioButton => {
radioButton.setAttribute('aria-checked', 'false');
});
radioButton.setAttribute('aria-checked', 'true');
});
});
})();
</script>
{% set trigger %}
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
@@ -19,8 +19,8 @@
</section>
<script>
(() => {
const sliderEl = document.querySelector('input[type="range"].input');
if (!sliderEl) return;
const sliders = document.querySelectorAll('input[type="range"].input');
if (!sliders) return;
const updateSlider = (el) => {
const min = parseFloat(el.min || 0);
@@ -30,7 +30,9 @@
el.style.setProperty('--slider-value', `${percent}%`);
};
updateSlider(sliderEl);
sliderEl.addEventListener('input', (event) => updateSlider(event.target));
sliders.forEach(slider => {
updateSlider(slider);
slider.addEventListener('input', (event) => updateSlider(event.target));
});
})();
</script>
+10
View File
@@ -1062,6 +1062,16 @@
visibility: visible;
}
}
.group-\[\.copied\]\:block {
&:is(:where(.group):is(.copied) *) {
display: block;
}
}
.group-\[\.copied\]\:hidden {
&:is(:where(.group):is(.copied) *) {
display: none;
}
}
.peer-checked\:border-primary {
&:is(:where(.peer):checked ~ *) {
border-color: var(--color-primary);
+19 -14
View File
@@ -14,20 +14,7 @@ toc:
<h2>There is no dedicated accordion component in Basecoat.</h2>
</div>
{% set code %}<section
x-init
@click="
const clickedSummary = $event.target.closest('summary');
if (!clickedSummary) return;
const clickedDetails = clickedSummary.closest('details');
if (!clickedDetails) return;
$el.querySelectorAll('details').forEach(detailsEl => {
if (detailsEl !== clickedDetails) {
detailsEl.removeAttribute('open');
}
});
"
>
{% set code %}<section class="accordion">
<details class="group border-b last:border-b-0 ">
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
@@ -62,6 +49,24 @@ toc:
</section>
</details>
</section>
<script>
(() => {
const accordions = document.querySelectorAll('.accordion');
accordions.forEach(accordion => {
accordion.addEventListener('click', (event) => {
const summary = event.target.closest('summary');
if (!summary) return;
const details = summary.closest('details');
if (!details) return;
accordion.querySelectorAll('details').forEach(detailsEl => {
if (detailsEl !== details) {
detailsEl.removeAttribute('open');
}
});
});
});
})();
</script>
{% endset %}
{{ code_preview("accordion", code | prettyHtml, "w-full max-w-md") }}
+33 -20
View File
@@ -267,9 +267,6 @@ toc:
role="menuitemcheckbox"
aria-checked="true"
class="group"
x-data="{ checked: true }"
@click="checked = !checked"
:aria-checked="checked"
>
{% lucide "check", {
"class": "invisible group-aria-checked:visible",
@@ -283,10 +280,7 @@ toc:
role="menuitemcheckbox"
aria-checked="false"
class="group"
disabled
x-data="{ checked: false }"
@click="checked = !checked"
:aria-checked="checked"
aria-disabled="true"
>
{% lucide "check", {
"class": "invisible group-aria-checked:visible",
@@ -300,9 +294,6 @@ toc:
role="menuitemcheckbox"
aria-checked="false"
class="group"
x-data="{ checked: false }"
@click="checked = !checked"
:aria-checked="checked"
>
{% lucide "check", {
"class": "invisible group-aria-checked:visible",
@@ -319,7 +310,20 @@ toc:
Logout
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
{% endcall %}{% endset %}
{% endcall %}
<script>
(() => {
const dropdownMenu = document.querySelector('#dropdown-menu-checkboxes');
const checkboxes = dropdownMenu.querySelectorAll('div[role="menuitemcheckbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', () => {
const isChecked = checkbox.getAttribute('aria-checked') === 'true';
checkbox.setAttribute('aria-checked', !isChecked);
});
});
})();
</script>
{% endset %}
{{ code_preview("dropdown-menu-checkboxes", code | prettyHtml) }}
<h3 id="example-radio-group"><a href="#example-radio-group">Radio Group</a></h3>
@@ -330,15 +334,13 @@ toc:
trigger_attrs={"class": "btn-outline"},
popover_attrs={"class": "min-w-56"}
) %}
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
<div role="group" aria-labelledby="position-options">
<span id="position-options" role="heading">Panel Position</span>
<hr role="separator">
<div
role="menuitemradio"
aria-checked="false"
class="group"
@click="checked = 'top'"
:aria-checked="checked == 'top'"
>
<div class="size-4 flex items-center justify-center">
<div
@@ -352,10 +354,8 @@ toc:
</div>
<div
role="menuitemradio"
aria-checked="false"
aria-checked="true"
class="group"
@click="checked = 'bottom'"
:aria-checked="checked == 'bottom'"
>
<div class="size-4 flex items-center justify-center">
<div
@@ -371,8 +371,6 @@ toc:
role="menuitemradio"
aria-checked="false"
class="group"
@click="checked = 'right'"
:aria-checked="checked == 'right'"
>
<div class="size-4 flex items-center justify-center">
<div
@@ -385,5 +383,20 @@ toc:
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
</div>
{% endcall %}{% endset %}
{% endcall %}
<script>
(() => {
const dropdownMenu = document.querySelector('#dropdown-menu-radio-group');
const radioButtons = dropdownMenu.querySelectorAll('div[role="menuitemradio"]');
radioButtons.forEach(radioButton => {
radioButton.addEventListener('click', () => {
radioButtons.forEach(radioButton => {
radioButton.setAttribute('aria-checked', 'false');
});
radioButton.setAttribute('aria-checked', 'true');
});
});
})();
</script>
{% endset %}
{{ code_preview("dropdown-menu", code | prettyHtml) }}
+15 -7
View File
@@ -16,18 +16,26 @@ toc:
min="0"
max="100"
value="50"
x-data="{
updatePercent(el) {
>
<script>
(() => {
const sliders = document.querySelectorAll('input[type="range"].input');
if (!sliders) return;
const updateSlider = (el) => {
const min = parseFloat(el.min || 0);
const max = parseFloat(el.max || 100);
const value = parseFloat(el.value);
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
el.style.setProperty('--slider-value', `${percent}%`);
}
}"
x-init="updatePercent($el)"
@input="updatePercent($el)"
>{% endset %}
};
sliders.forEach(slider => {
updateSlider(slider);
slider.addEventListener('input', (event) => updateSlider(event.target));
});
})();
</script>{% endset %}
{{ code_preview("slider", code, "w-full max-w-sm") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
+9 -3
View File
@@ -232,9 +232,15 @@ layout: layouts/layout.njk
I can't log in.
</div>
</section>
<footer class="flex items-center gap-2" x-data="{ message: '' }">
<input type="text" class="input w-full" placeholder="Type your message here..." x-model="message" />
<button type="button" class="btn-icon" :disabled="!message">
<footer class="flex items-center gap-2">
<input type="text" class="input w-full" placeholder="Type your message here..." oninput="
if (this.value) {
this.parentElement.querySelector('button').removeAttribute('disabled');
} else {
this.parentElement.querySelector('button').setAttribute('disabled', 'true');
}
"/>
<button type="button" class="btn-icon" disabled>
{% lucide "send"%}
</button>
</footer>
+2 -2
View File
@@ -38,11 +38,11 @@
@param description {string} [optional] - The secondary description text.
@param action {object} [optional] - Defines an action button.
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- onclick {string}: JavaScript code to execute on click.
- url {string}: URL for an anchor link button.
@param cancel {object} [optional] - Defines a cancel/dismiss button (similar structure to action).
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- onclick {string}: JavaScript code to execute on click.
- url {string}: URL for an anchor link button.
#}
{% macro toast(
+2 -2
View File
@@ -38,11 +38,11 @@
@param description {string} [optional] - The secondary description text.
@param action {object} [optional] - Defines an action button.
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- onclick {string}: JavaScript code to execute on click.
- url {string}: URL for an anchor link button.
@param cancel {object} [optional] - Defines a cancel/dismiss button (similar structure to action).
- label {string}: Button text.
- click {string}: JavaScript code to execute on click (e.g., '$dispatch(\'custom-event\')').
- onclick {string}: JavaScript code to execute on click.
- url {string}: URL for an anchor link button.
#}
{% macro toast(