This commit is contained in:
2026-03-22 21:14:40 +03:00
parent 8aa1182641
commit 2dbe7866ff
9 changed files with 174 additions and 60 deletions
+3
View File
@@ -17,6 +17,7 @@
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"tailwindcss": "^4.1.12", "tailwindcss": "^4.1.12",
"use-debounce": "^10.0.6", "use-debounce": "^10.0.6",
"web-haptics": "^0.0.6",
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.33.0", "@eslint/js": "^9.33.0",
@@ -1152,6 +1153,8 @@
"vite-plugin-ssr-ssg": ["vite-plugin-ssr-ssg@1.4.1", "", { "dependencies": { "compression": "^1.7.4", "consola": "^2.15.3", "express": "^4.17.1", "fs-extra": "^9.1.0", "recursive-readdir": "^2.2.2", "yargs": "^16.2.0" }, "bin": { "vite-ssrg": "dist/bin.js" } }, "sha512-lXw6VyzIrHNN5aOdtJmaf3rVb8g/stRu/xSJ653PDbO2P6J/4i2PxXoCStwj4Exry7JE3YpxujOJ4KOtgwCT3A=="], "vite-plugin-ssr-ssg": ["vite-plugin-ssr-ssg@1.4.1", "", { "dependencies": { "compression": "^1.7.4", "consola": "^2.15.3", "express": "^4.17.1", "fs-extra": "^9.1.0", "recursive-readdir": "^2.2.2", "yargs": "^16.2.0" }, "bin": { "vite-ssrg": "dist/bin.js" } }, "sha512-lXw6VyzIrHNN5aOdtJmaf3rVb8g/stRu/xSJ653PDbO2P6J/4i2PxXoCStwj4Exry7JE3YpxujOJ4KOtgwCT3A=="],
"web-haptics": ["web-haptics@0.0.6", "", { "peerDependencies": { "react": ">=18", "react-dom": ">=18", "svelte": ">=4", "vue": ">=3" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-eCzcf1LDi20+Fr0x9V3OkX92k0gxEQXaHajmhXHitsnk6SxPeshv8TBtBRqxyst8HI1uf2FyFVE7QS3jo1gkrw=="],
"webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
"whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
+6
View File
@@ -41,6 +41,7 @@
<title>dx 4</title> <title>dx 4</title>
</head> </head>
<body> <body>
<div id="root"> <div id="root">
<style> <style>
div[data-ktkz-loading] { div[data-ktkz-loading] {
@@ -113,6 +114,11 @@
</div> </div>
</div> </div>
</div> </div>
<div k-version>
dx4 v2.1
</div>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="/src/main.jsx"></script>
</body> </body>
</html> </html>
+2 -1
View File
@@ -21,7 +21,8 @@
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"tailwindcss": "^4.1.12", "tailwindcss": "^4.1.12",
"use-debounce": "^10.0.6" "use-debounce": "^10.0.6",
"web-haptics": "^0.0.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.33.0", "@eslint/js": "^9.33.0",
+10 -1
View File
@@ -34,6 +34,15 @@ input {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E");
} }
body > *:not(#root):not(vite-error-overlay) { /* free ad blocker*/
body:not(.dev) > *:not(#root):not(vite-error-overlay):not([k-version]) {
display: none !important; display: none !important;
} }
[k-version] {
position: fixed;
right: 10px;
bottom: 10px;
font-size: 1rem;
color: #eee5;
}
+5 -1
View File
@@ -22,8 +22,12 @@ import "@fontsource/jost/700.css";
import "@fontsource/jost/800.css"; import "@fontsource/jost/800.css";
//import "@fontsource/jost/900.css"; //import "@fontsource/jost/900.css";
if (import.meta.env.DEV) {
document.body.classList.add("dev");
}
createRoot(document.getElementById("root")).render( createRoot(document.getElementById("root")).render(
<StrictMode> <StrictMode>
<App /> <App />
</StrictMode> </StrictMode>,
); );
+48 -23
View File
@@ -10,24 +10,20 @@ import {
import Logo from "../assets/logo.svg"; import Logo from "../assets/logo.svg";
import LogoAlternative from "../assets/fully_alternative_logo.svg"; import LogoAlternative from "../assets/fully_alternative_logo.svg";
import { import {
Switch, // AnimatedCanvas,
LoadingSpinner, LoadingSpinner,
DatePicker, DatePicker,
TextPreloader, TextPreloader,
// CoolMouseBg, // CoolMouseBg,
} from "../trash/trash-components"; } from "../trash/trash-components";
import { import { AdminReq, classUserFRequest, RaspGet } from "../trash/req_mgr";
AdminReq,
AdminUserFRequest,
classUserFRequest,
RaspGet,
} from "../trash/req_mgr";
import { AnimatePresence, LayoutGroup, motion } from "motion/react"; import { AnimatePresence, LayoutGroup, motion } from "motion/react";
import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi"; import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { API_BASE_URL } from "../App"; import { API_BASE_URL } from "../App";
import { copyDXLink, ifstyle } from "../trash/helpers"; import { copyDXLink, ifstyle } from "../trash/helpers";
import { useWebHaptics } from "web-haptics/react";
export function DX4App({ export function DX4App({
classIDAtom, classIDAtom,
@@ -78,7 +74,7 @@ export function DX4App({
(err) => { (err) => {
if (cancelled) return; if (cancelled) return;
setRasp([true, "ошибка: " + err]); setRasp([true, "ошибка: " + err]);
} },
); );
} else { } else {
if (cancelled) return; if (cancelled) return;
@@ -133,15 +129,15 @@ export function DX4App({
if (classId !== "") if (classId !== "")
promises.push( promises.push(
dengi_cash.withCacheAsync(`userfr-${classId}`, async () => dengi_cash.withCacheAsync(`userfr-${classId}`, async () =>
classUserFRequest(API_BASE_URL, classId) classUserFRequest(API_BASE_URL, classId),
) ),
); );
else promises.push(Promise.resolve([0])); else promises.push(Promise.resolve([0]));
if (adminId !== "") if (adminId !== "")
promises.push( promises.push(
dengi_cash.withCacheAsync(`admindata-${adminId}`, async () => dengi_cash.withCacheAsync(`admindata-${adminId}`, async () =>
AdminReq(API_BASE_URL, adminId) AdminReq(API_BASE_URL, adminId),
) ),
); );
else promises.push(Promise.resolve([0])); else promises.push(Promise.resolve([0]));
Promise.all(promises).then(([classRes, adminRes]) => { Promise.all(promises).then(([classRes, adminRes]) => {
@@ -192,6 +188,9 @@ export function DX4App({
if (classId === "") { if (classId === "") {
setsetRoute(setRoute, "/"); setsetRoute(setRoute, "/");
} }
const { trigger } = useWebHaptics();
const raspDragStartX = useRef(0);
return ( return (
<> <>
<motion.div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2"> <motion.div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
@@ -236,7 +235,12 @@ export function DX4App({
> >
<BiDotsHorizontalRounded <BiDotsHorizontalRounded
size={35} size={35}
onClick={() => setOpenUserSettings(!openUserSettings)} onClick={() => {
setOpenUserSettings(!openUserSettings);
trigger([{ duration: 10 }, { delay: 100, duration: 10 }], {
intensity: 1,
});
}}
onContextMenu={(e) => { onContextMenu={(e) => {
e.preventDefault(); e.preventDefault();
setPickmeMode(!pickmeMode); setPickmeMode(!pickmeMode);
@@ -269,7 +273,7 @@ export function DX4App({
{adminId != "" && managing && ( {adminId != "" && managing && (
<motion.div <motion.div
className="flex w-full justify-end" className="flex w-full justify-end not-md:px-4"
layout="position" layout="position"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
@@ -326,7 +330,28 @@ export function DX4App({
</a> </a>
)} )}
</div> </div>
<div className="relative"> <motion.div
className="relative min-h-[50vh]"
drag="x"
dragConstraints={{ top: 0, right: 0, bottom: 0, left: 0 }}
dragElastic={0.2}
onDragStart={(ev) => {
raspDragStartX.current = ev.clientX;
}}
onDragEnd={(ev) => {
if (raspDragStartX.current > ev.clientX + 150) {
const d = new Date(selectedDate);
d.setDate(d.getDate() + 1);
setSelectedDate(d);
trigger([{ duration: 10 }], { intensity: 1 });
} else if (raspDragStartX.current < ev.clientX - 150) {
const d = new Date(selectedDate);
d.setDate(d.getDate() - 1);
setSelectedDate(d);
trigger([{ duration: 10 }], { intensity: 1 });
}
}}
>
<LayoutGroup> <LayoutGroup>
<RaspView {...{ rasp, pickmeMode, selectedDate }} /> <RaspView {...{ rasp, pickmeMode, selectedDate }} />
</LayoutGroup> </LayoutGroup>
@@ -343,7 +368,7 @@ export function DX4App({
<LoadingSpinner size={25} /> <LoadingSpinner size={25} />
</motion.div> </motion.div>
)} )}
</div> </motion.div>
</motion.div> </motion.div>
</motion.div> </motion.div>
</motion.div> </motion.div>
@@ -370,8 +395,8 @@ export const RaspView = memo(({ rasp, pickmeMode, selectedDate }) => {
key={selectedDate} key={selectedDate}
className={ className={
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br mb-10 " + "w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br mb-10 " +
(pickmeMode ? "from-pink-400/30" : "from-zinc-800/40") + (pickmeMode ? "from-pink-400/30" : "from-[#101011ee]") +
" to-transparent rounded-2xl selectable" " to-[#030303fa] rounded-2xl"
} }
onContextMenu={(ev) => { onContextMenu={(ev) => {
ev.preventDefault(); ev.preventDefault();
@@ -542,13 +567,13 @@ const UserSettings = memo(
> >
{item[0]} {item[1]} {item[0]} {item[1]}
</div> </div>
) ),
)} )}
</motion.div> </motion.div>
)} )}
</> </>
); );
} },
); );
class GlobalListener { class GlobalListener {
@@ -592,10 +617,10 @@ const TableItem = memo(({ d }) => {
<tr <tr
//layout="position" //layout="position"
className={ className={
"transition-colors *:transition-colors " + "transition-colors *:transition-colors last:*:first:rounded-bl-2xl last:*:last:rounded-br-2xl " +
(sel (sel
? "bg-zinc-300/15 lg:hover:bg-zinc-300/20" ? "*:bg-zinc-300/15 lg:hover:*:bg-zinc-300/20"
: " lg:hover:bg-zinc-300/15") : " lg:hover:*:bg-zinc-300/15")
} }
onClick={() => { onClick={() => {
setSel(!sel); setSel(!sel);
+12 -6
View File
@@ -22,6 +22,7 @@ import {
import { TableEditor } from "../trash/editorTable"; import { TableEditor } from "../trash/editorTable";
import { ifstyle } from "../trash/helpers"; import { ifstyle } from "../trash/helpers";
import { API_BASE_URL } from "../App"; import { API_BASE_URL } from "../App";
import { useWebHaptics } from "web-haptics/react";
export default function EditorPage(props) { export default function EditorPage(props) {
const [classId, _setUserId] = useAtom(props.classIDAtom); const [classId, _setUserId] = useAtom(props.classIDAtom);
@@ -49,17 +50,18 @@ export default function EditorPage(props) {
let cancelled = false; let cancelled = false;
const promises = []; const promises = [];
if (classId !== "") if (classId !== "")
// TODO: rewrite this bullshit into one pretty request
promises.push( promises.push(
props.dengi_cash.withCacheAsync(`userfr-${classId}`, async () => props.dengi_cash.withCacheAsync(`userfr-${classId}`, async () =>
classUserFRequest(API_BASE_URL, classId) classUserFRequest(API_BASE_URL, classId),
) ),
); );
else promises.push(Promise.resolve([0])); else promises.push(Promise.resolve([0]));
if (adminId !== "") if (adminId !== "")
promises.push( promises.push(
props.dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () => props.dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () =>
AdminUserFRequest(API_BASE_URL, adminId) AdminUserFRequest(API_BASE_URL, adminId),
) ),
); );
else promises.push(Promise.resolve([0])); else promises.push(Promise.resolve([0]));
Promise.all(promises).then(([classRes, adminRes]) => { Promise.all(promises).then(([classRes, adminRes]) => {
@@ -79,9 +81,12 @@ export default function EditorPage(props) {
props.dengi_cash.delete( props.dengi_cash.delete(
"editor_schedule", "editor_schedule",
"editor_changes", "editor_changes",
"editor_rings" "editor_rings",
); );
}, []); // clean on full rerender }, []); // clean on full rerender
const { trigger } = useWebHaptics();
// tabs // tabs
const tabs = [ const tabs = [
[ [
@@ -189,7 +194,7 @@ export default function EditorPage(props) {
"flex items-center gap-2 transition-colors " + "flex items-center gap-2 transition-colors " +
ifstyle( ifstyle(
selectedTab == 2, selectedTab == 2,
"text-transparent pointer-events-none select-none" "text-transparent pointer-events-none select-none",
) )
} }
> >
@@ -238,6 +243,7 @@ export default function EditorPage(props) {
key={idx} key={idx}
onClick={() => { onClick={() => {
if (selectedTab == idx) return; if (selectedTab == idx) return;
trigger([{ duration: 35 }], { intensity: 1 });
if (!changesWereMade) { if (!changesWereMade) {
selectTab(idx); selectTab(idx);
} else { } else {
+28 -11
View File
@@ -42,6 +42,7 @@ import {
EditorRemoveRequest, EditorRemoveRequest,
} from "./editor_req_mgr"; } from "./editor_req_mgr";
import { API_BASE_URL } from "../App"; import { API_BASE_URL } from "../App";
import { useWebHaptics } from "web-haptics/react";
// Configuration // Configuration
const COLUMNS = [ const COLUMNS = [
@@ -171,7 +172,7 @@ function SortableRow({
transition, transition,
opacity: isDragging ? 0.5 : 1, opacity: isDragging ? 0.5 : 1,
}; };
const { trigger } = useWebHaptics();
return ( return (
<div <div
ref={setNodeRef} ref={setNodeRef}
@@ -207,7 +208,10 @@ function SortableRow({
<span className="w-[98%] mx-auto h-[1px] bg-zinc-500" /> <span className="w-[98%] mx-auto h-[1px] bg-zinc-500" />
<div className="flex justify-end w-full"> <div className="flex justify-end w-full">
<button <button
onClick={() => onDuplicate(item.id)} onClick={() => {
onDuplicate(item.id);
trigger([{ duration: 25 }], { intensity: 0.7 });
}}
className="min-w-12 min-h-12 transition-colors bg-zinc-600/20 text-zinc-400 hover:bg-zinc-600/40 hover:text-zinc-300 flex items-center justify-center rounded-l-md" className="min-w-12 min-h-12 transition-colors bg-zinc-600/20 text-zinc-400 hover:bg-zinc-600/40 hover:text-zinc-300 flex items-center justify-center rounded-l-md"
> >
<BiCopy size={20} /> <BiCopy size={20} />
@@ -220,7 +224,14 @@ function SortableRow({
<BiDotsVertical size={24} /> <BiDotsVertical size={24} />
</div> </div>
<button <button
onClick={() => onDelete(item.id)} onClick={() => {
onDelete(item.id);
trigger([
{ duration: 40, intensity: 0.7 },
{ delay: 40, duration: 40, intensity: 0.7 },
{ delay: 40, duration: 40, intensity: 0.9 },
]);
}}
className="min-w-12 min-h-12 transition-colors bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60 flex items-center justify-center rounded-r-md" className="min-w-12 min-h-12 transition-colors bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60 flex items-center justify-center rounded-r-md"
> >
<BiX size={24} /> <BiX size={24} />
@@ -235,7 +246,7 @@ async function getPrefilledRasp(tab, date, adminId) {
API_BASE_URL, API_BASE_URL,
tab, tab,
adminId, adminId,
apiFormatDate(date) apiFormatDate(date),
); );
if (a[0] == 1 && a[1]) { if (a[0] == 1 && a[1]) {
return convertApiToEditor(a[1]); return convertApiToEditor(a[1]);
@@ -291,7 +302,7 @@ export function TableEditor({
delay: 200, delay: 200,
tolerance: 8, tolerance: 8,
}, },
}) }),
); );
const updateItems = (newItems) => { const updateItems = (newItems) => {
@@ -330,7 +341,7 @@ export function TableEditor({
clearTimeout(updateTimeoutRef.current[key]); clearTimeout(updateTimeoutRef.current[key]);
} }
const newItems = items.map((item) => const newItems = items.map((item) =>
item.id === id ? { ...item, [field]: value } : item item.id === id ? { ...item, [field]: value } : item,
); );
updateItems(newItems); updateItems(newItems);
updateTimeoutRef.current[key] = setTimeout(() => { updateTimeoutRef.current[key] = setTimeout(() => {
@@ -338,7 +349,7 @@ export function TableEditor({
const itemsWithOriginalValue = items.map((item) => const itemsWithOriginalValue = items.map((item) =>
item.id === id item.id === id
? { ...item, [field]: previousValueRef.current[key] } ? { ...item, [field]: previousValueRef.current[key] }
: item : item,
); );
setUndoItems([itemsWithOriginalValue, ...undoItems]); setUndoItems([itemsWithOriginalValue, ...undoItems]);
@@ -406,7 +417,7 @@ export function TableEditor({
const lastPosition = const lastPosition =
items.length > 0 items.length > 0
? String( ? String(
parseInt(sortedItems[sortedItems.length - 1].position || "0") + 1 parseInt(sortedItems[sortedItems.length - 1].position || "0") + 1,
) )
: "1"; : "1";
@@ -425,7 +436,7 @@ export function TableEditor({
left: window.scrollX, // preserve current X left: window.scrollX, // preserve current X
behavior: "smooth", behavior: "smooth",
}), }),
100 100,
); );
}; };
@@ -461,7 +472,7 @@ export function TableEditor({
selectedTab, selectedTab,
adminId, adminId,
apiFormatDate(date), apiFormatDate(date),
JSON.stringify(convertEditorToApi(items)) JSON.stringify(convertEditorToApi(items)),
).then(() => { ).then(() => {
setLoading(false); setLoading(false);
}); });
@@ -471,6 +482,11 @@ export function TableEditor({
setDeleteDiag(true); setDeleteDiag(true);
}; };
const { trigger } = useWebHaptics();
const handleDragOver = (ev) => {
trigger([{ duration: 25 }], { intensity: 0.7 });
};
let deleteDialogBusy = false; // NOTE: we don't need a state let deleteDialogBusy = false; // NOTE: we don't need a state
return ( return (
<> <>
@@ -496,7 +512,7 @@ export function TableEditor({
API_BASE_URL, API_BASE_URL,
selectedTab, selectedTab,
adminId, adminId,
apiFormatDate(date) apiFormatDate(date),
).then(() => { ).then(() => {
deleteDialogBusy = false; // for 100% sure deleteDialogBusy = false; // for 100% sure
setDeleteDiag(false); setDeleteDiag(false);
@@ -573,6 +589,7 @@ export function TableEditor({
sensors={sensors} sensors={sensors}
collisionDetection={closestCenter} collisionDetection={closestCenter}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
> >
<SortableContext <SortableContext
items={items.map((item) => item.id)} items={items.map((item) => item.id)}
+59 -16
View File
@@ -7,9 +7,11 @@ import {
BiShow, BiShow,
BiX, BiX,
} from "react-icons/bi"; } from "react-icons/bi";
import { formatDate, getDateTags } from "./dengi.js"; import { apiFormatDate, formatDate, getDateTags } from "./dengi.js";
import { AnimatePresence, motion, LayoutGroup } from "motion/react"; import { AnimatePresence, motion, LayoutGroup } from "motion/react";
import trashcss from "./trash.module.css"; import trashcss from "./trash.module.css";
import { useWebHaptics } from "web-haptics/react";
export function Switch({ export function Switch({
checked, checked,
defaultChecked = false, defaultChecked = false,
@@ -98,6 +100,7 @@ export function LoadingSpinner({ size }) {
} }
function DatePickerButton({ next = false, date, setDate, pink = false }) { function DatePickerButton({ next = false, date, setDate, pink = false }) {
const { trigger } = useWebHaptics();
return ( return (
<span <span
className={`cursor-pointer ${ className={`cursor-pointer ${
@@ -109,6 +112,7 @@ function DatePickerButton({ next = false, date, setDate, pink = false }) {
const d = new Date(date); const d = new Date(date);
d.setDate(d.getDate() + (next ? 1 : -1)); d.setDate(d.getDate() + (next ? 1 : -1));
setDate(d); setDate(d);
trigger([{ duration: 15 }], { intensity: 1 });
}} }}
> >
{!next ? <BiLeftArrowAlt size={34} /> : <BiRightArrowAlt size={34} />} {!next ? <BiLeftArrowAlt size={34} /> : <BiRightArrowAlt size={34} />}
@@ -118,7 +122,7 @@ function DatePickerButton({ next = false, date, setDate, pink = false }) {
export const DatePicker = (props) => { export const DatePicker = (props) => {
const dateTag = getDateTags(props.date); const dateTag = getDateTags(props.date);
const { trigger } = useWebHaptics();
return ( return (
<LayoutGroup> <LayoutGroup>
<div <div
@@ -133,20 +137,27 @@ export const DatePicker = (props) => {
/> />
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
<motion.span <motion.span
key={formatDate(props.date)} // key={formatDate(props.date)}
className={`cursor-pointer flex items-center gap-1 justify-center w-[80%] lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl text-zinc-300 ${ className={`cursor-pointer flex items-center gap-1 justify-center w-[80%] lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl text-zinc-300 ${
!props.pink !props.pink
? "lg:hover:bg-white lg:hover:*:text-black" ? "lg:hover:bg-white lg:hover:*:text-black"
: "lg:hover:bg-pink-500 lg:hover:*:text-white" : "lg:hover:bg-pink-500 lg:hover:*:text-white"
} px-3 overflow-hidden `} } px-3 overflow-hidden `}
variants={{ // variants={{
a: { opacity: 0, filter: "blur(1px)" }, // a: { opacity: 0, filter: "blur(1px)" },
b: { opacity: 1, filter: "blur(0px)" }, // b: { opacity: 1, filter: "blur(0px)" },
}} // }}
initial="a" // initial="a"
animate="b" // animate="b"
exit="a" // exit="a"
onClick={() => { onClick={() => {
if (apiFormatDate(props.date) != apiFormatDate(new Date()))
trigger([
{ duration: 10, intensity: 1 },
{ delay: 40, duration: 20, intensity: 0.26 },
{ delay: 90, duration: 20, intensity: 0.34 },
]);
props.setDate(new Date()); props.setDate(new Date());
}} }}
> >
@@ -160,7 +171,7 @@ export const DatePicker = (props) => {
: " bg-transparent") : " bg-transparent")
} }
layoutId="datepicker_indicator" layoutId="datepicker_indicator"
layout="position" // layout="position"
/> />
<motion.span <motion.span
layoutId="datepicker_date" layoutId="datepicker_date"
@@ -173,7 +184,7 @@ export const DatePicker = (props) => {
<> <>
<motion.div <motion.div
className="flex" className="flex"
layoutId="datepicker_date_div" // layoutId="datepicker_date_div"
layout="position" layout="position"
> >
<span <span
@@ -183,9 +194,9 @@ export const DatePicker = (props) => {
/> />
</motion.div> </motion.div>
<motion.span <motion.span
initial={{ x: 10, opacity: 0 }} initial={{ opacity: 0 }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
animate={{ x: 0, opacity: 1 }} animate={{ opacity: 1 }}
className="text-zinc-600 italic" className="text-zinc-600 italic"
> >
{dateTag} {dateTag}
@@ -211,6 +222,8 @@ export function DengiButton({
disabled = false, disabled = false,
className = "", className = "",
}) { }) {
const { trigger } = useWebHaptics();
return ( return (
<span <span
role="button" role="button"
@@ -223,7 +236,19 @@ export function DengiButton({
(className ? className : "") (className ? className : "")
} }
aria-disabled={disabled} aria-disabled={disabled}
onClick={disabled ? null : onClick} onClick={
disabled
? null
: () => {
onClick();
trigger([
{
duration: 40,
intensity: 0.8,
},
]);
}
}
> >
{children} {children}
</span> </span>
@@ -285,6 +310,22 @@ export function DengiYesOrNoDialog({
show, show,
onAnswer, onAnswer,
}) { }) {
const { trigger } = useWebHaptics();
useEffect(() => {
if (show) {
trigger([
{
duration: 40,
intensity: 0.8,
},
{
delay: 15,
duration: 170,
intensity: 0.9,
},
]);
}
}, [show]);
return ( return (
<> <>
<AnimatePresence> <AnimatePresence>
@@ -296,8 +337,10 @@ export function DengiYesOrNoDialog({
exit="off" exit="off"
className="fixed z-99 bg-black/50 backdrop-blur-xl top-0 left-0 w-full h-full overflow-hidden flex items-center justify-center" className="fixed z-99 bg-black/50 backdrop-blur-xl top-0 left-0 w-full h-full overflow-hidden flex items-center justify-center"
onClick={(e) => { onClick={(e) => {
if (e.target === e.currentTarget && content.bgDissmis) if (e.target === e.currentTarget && content.bgDissmis) {
onAnswer(false); onAnswer(false);
trigger([{ duration: 40, intensity: 1 }]);
}
}} }}
> >
<motion.div <motion.div