Use native datetime-local instead of AirDatepicker

This commit is contained in:
Alex Lion
2026-08-03 09:48:38 +07:00
parent a103a6e996
commit 73b458e998
7 changed files with 58 additions and 89 deletions

View File

@@ -1,4 +1,3 @@
@import 'air-datepicker/air-datepicker.css';
@import 'animate.css/animate.min.css';
@import 'tailwindcss';
@@ -545,20 +544,6 @@
transform:rotate(-20deg);
}
/* Air datepicker */
.air-datepicker-body--day-name {
color: var(--color-primary-600);
}
.air-datepicker-cell.-selected-, .air-datepicker-cell.-selected-.-current- {
background-color: var(--color-primary-500);
color: var(--color-white);
}
.air-datepicker-cell.-current- {
color: var(--color-secondary-500);
}
.animate__slow_slow {
--animate-duration: 5s;
}

View File

@@ -6,14 +6,6 @@ import { LiveSocket } from "phoenix_live_view";
import topbar from "../vendor/topbar";
import Alpine from "alpinejs";
import moment from "moment-timezone";
import AirDatepicker from "air-datepicker";
import airdatepickerLocaleEn from "air-datepicker/locale/en";
import airdatepickerLocaleFr from "air-datepicker/locale/fr";
import airdatepickerLocaleDe from "air-datepicker/locale/de";
import airdatepickerLocaleEs from "air-datepicker/locale/es";
import airdatepickerLocaleNl from "air-datepicker/locale/nl";
import airdatepickerLocaleIt from "air-datepicker/locale/it";
import airdatepickerLocaleHu from "air-datepicker/locale/hu";
import "moment/locale/de";
import "moment/locale/fr";
import "moment/locale/es";
@@ -42,43 +34,19 @@ const supportedLocales = window.claperConfig?.supportedLocales || [
"lv",
];
const airdatePickrSupportedLocales = window.claperConfig?.supportedLocales || [
"en",
"fr",
"de",
"es",
"nl",
"it",
"hu",
];
var locale =
document.querySelector("html").getAttribute("lang") ||
navigator.language.split("-")[0];
var airdatepickrLocale = locale;
if (!supportedLocales.includes(locale)) {
locale = "en";
}
if (!airdatePickrSupportedLocales.includes(airdatepickrLocale)) {
airdatepickrLocale = "en";
}
window.moment.locale("en");
window.moment.locale(locale);
window.Alpine = Alpine;
Alpine.start();
let airdatePickrLocales = {
en: airdatepickerLocaleEn,
fr: airdatepickerLocaleFr,
de: airdatepickerLocaleDe,
es: airdatepickerLocaleEs,
nl: airdatepickerLocaleNl,
it: airdatepickerLocaleIt,
hu: airdatepickerLocaleHu,
};
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
@@ -450,31 +418,52 @@ Hooks.CalendarLocalDate = {
this.el.innerHTML = moment.utc(this.el.dataset.date).local().calendar();
},
};
Hooks.Pickr = {
Hooks.DateTimeLocal = {
mounted() {
const localTime = this.el.querySelector("input[type=text]");
const utcTime = this.el.querySelector("input[type=hidden]");
localTime.value = moment
.utc(utcTime.value)
.local()
.format("DD-MM-YYYY HH:mm");
this.pickr = new AirDatepicker(localTime, {
dateFormat: "dd-MM-yyyy",
timepicker: true,
minutesStep: 5,
minDate: moment(),
timeFormat: "HH:mm",
selectedDates: [moment(localTime.value, "DD-MM-YYYY HH:mm").toDate()],
onSelect: ({ date }) => {
const utc = moment(date).utc().format("YYYY-MM-DDTHH:mm:ss");
utcTime.value = utc;
},
locale: airdatePickrLocales[airdatepickrLocale] || airdatePickrLocales["en"],
});
this.localTime = this.el.querySelector("input[type=datetime-local]");
this.utcTime = this.el.querySelector("input[type=hidden]");
this.syncLocalTime();
this.handleInput = ({ target }) => {
if (target === this.localTime) this.syncUtcTime();
};
this.el.addEventListener("input", this.handleInput);
},
updated() {
this.localTime = this.el.querySelector("input[type=datetime-local]");
this.utcTime = this.el.querySelector("input[type=hidden]");
this.syncLocalTime();
},
syncLocalTime() {
if (!this.utcTime.value) {
this.localTime.value = "";
return;
}
const value = this.utcTime.value.replace(" ", "T").replace(/Z$/, "");
const date = new Date(`${value}Z`);
if (!Number.isNaN(date.getTime())) {
const pad = (part) => String(part).padStart(2, "0");
this.localTime.value =
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
`T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
},
syncUtcTime() {
if (!this.localTime.value) {
this.utcTime.value = "";
return;
}
const date = new Date(this.localTime.value);
if (!Number.isNaN(date.getTime())) {
this.utcTime.value = date.toISOString().slice(0, 19);
}
},
updated() {},
destroyed() {
if (this.pickr) this.pickr.destroy();
this.el.removeEventListener("input", this.handleInput);
},
};
Hooks.UpdateAttendees = {

View File

@@ -5,7 +5,6 @@
"packages": {
"": {
"dependencies": {
"air-datepicker": "^3.5.0",
"animate.css": "^4.1.1",
"chart.js": "^4.4.0",
"chartjs-adapter-moment": "^1.0.1",
@@ -896,11 +895,6 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true
},
"node_modules/air-datepicker": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.0.tgz",
"integrity": "sha512-WOpn1MaSl5drcXSwkXg5Gh/jXX/VFfamNnIb8E43AY4UKuW/bNEW06e3GGsiWLDBQLabD22L6b6cP7KHnAy54w=="
},
"node_modules/alpinejs": {
"version": "3.13.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz",
@@ -1930,11 +1924,6 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true
},
"air-datepicker": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.0.tgz",
"integrity": "sha512-WOpn1MaSl5drcXSwkXg5Gh/jXX/VFfamNnIb8E43AY4UKuW/bNEW06e3GGsiWLDBQLabD22L6b6cP7KHnAy54w=="
},
"alpinejs": {
"version": "3.13.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz",

View File

@@ -11,7 +11,6 @@
"tailwindcss": "^4.1.11"
},
"dependencies": {
"air-datepicker": "^3.5.0",
"animate.css": "^4.1.1",
"chart.js": "^4.4.0",
"chartjs-adapter-moment": "^1.0.1",

View File

@@ -259,14 +259,12 @@
</p>
</div>
<div class="flex flex-col gap-1" phx-update="ignore" id="date-picker">
<div class="flex flex-col gap-1" id="date-picker">
<ClaperWeb.Component.Input.date
form={f}
key={:started_at}
name={gettext("Start date and time")}
required="true"
from={Date.add(Date.utc_today(), -1)}
to={Date.add(Date.utc_today(), 365)}
/>
<p class="text-xs text-base-content/60">{gettext("Shown in your local time.")}</p>
</div>

View File

@@ -277,14 +277,16 @@ defmodule ClaperWeb.Component.Input do
~H"""
<div>
<div class="relative" id="date" phx-hook="Pickr">
{label(@form, @key, @name, class: "block text-sm font-bold text-base-content")}
<div class="relative" id={"#{input_id(@form, @key)}-picker"} phx-hook="DateTimeLocal">
{label(@form, :local_date, @name, class: "block text-sm font-bold text-base-content")}
<div class="mt-1 relative">
{hidden_input(@form, @key)}
{text_input(@form, :local_date,
{datetime_local_input(@form, :local_date,
required: @required,
readonly: @readonly,
autofocus: @autofocus,
placeholder: @placeholder,
autocomplete: false,
autocomplete: "off",
step: 300,
class:
"input h-12 w-full bg-white px-4 read-only:opacity-50 #{if Keyword.has_key?(@form.errors, @key), do: "input-error", else: ""}"
)}

View File

@@ -44,6 +44,13 @@ defmodule ClaperWeb.EventLiveTest do
assert has_element?(edit_live, "#event-editor-title", "Edit event")
assert has_element?(edit_live, "#presentation-heading", "Presentation")
assert has_element?(edit_live, "#event-details-heading", "Event details")
assert has_element?(edit_live, ~s(#date-picker input[type="datetime-local"]))
assert has_element?(
edit_live,
~s(#date-picker input[name="event[started_at]"][type="hidden"])
)
assert has_element?(edit_live, "#facilitators-section")
assert has_element?(edit_live, "#event-danger-zone", "Delete event")
assert has_element?(edit_live, ~s(button[form="event-form"]), "Save changes")