Skip to content

Commit 3bc167d

Browse files
v0.0.13
1 parent 6087caf commit 3bc167d

File tree

11 files changed

+425
-153
lines changed

11 files changed

+425
-153
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Version History
44

5+
### v0.0.13
6+
7+
- Added the `<PointlessProcess⁄>`, `<Countdown⁄>`, and `<ImageModal⁄>` islands.
8+
- Minor updates in the `<Counter/>`, `<GibberishChat/>`, and `<RandomQuote/>`
9+
islands.
10+
511
### v0.0.12
612

713
- Started the development for the `<PointlessProcess⁄>` island.

islands/Countdown.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useEffect, useState } from "preact/hooks";
2+
3+
export default function CountdownTimer(
4+
props: { seconds: number; onFinish?: () => void },
5+
) {
6+
const [timeLeft, setTimeLeft] = useState(props.seconds);
7+
8+
useEffect(() => {
9+
const timer = setInterval(() => {
10+
setTimeLeft((t) => {
11+
if (t <= 1) {
12+
if (props.onFinish) props.onFinish();
13+
clearInterval(timer);
14+
return 0;
15+
}
16+
return t - 1;
17+
});
18+
}, 1000);
19+
return () => clearInterval(timer);
20+
}, []);
21+
22+
const minutes = String(Math.floor(timeLeft / 60));
23+
const seconds = String(timeLeft % 60);
24+
25+
return (
26+
<span
27+
class="countdown font-mono text-6xl"
28+
aria-live="polite"
29+
>
30+
<span style={{ "--value": minutes }} aria-label={minutes}>
31+
{minutes}
32+
</span>
33+
:
34+
<span style={{ "--value": seconds }} aria-label={seconds}>
35+
{seconds}
36+
</span>
37+
</span>
38+
);
39+
}

islands/Counter.tsx

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,35 @@ interface CounterProps {
66

77
export default function Counter(props: CounterProps) {
88
return (
9-
<div class="flex gap-8 py-6">
10-
<button
11-
type="button"
12-
class="btn btn-primary"
13-
onClick={() => props.count.value -= 1}
14-
tabIndex={0}
15-
>
16-
-1
17-
</button>
18-
<p class="text-3xl tabular-nums">{props.count}</p>
19-
<button
20-
type="button"
21-
class="btn btn-primary"
22-
onClick={() => props.count.value += 1}
23-
tabIndex={0}
24-
>
25-
+1
26-
</button>
9+
<div class="col-span-full md:col-span-3 lg:col-span-4">
10+
<div class="prose p-1-2 bg-base-200">
11+
<h2>Counter</h2>
12+
</div>
13+
<div class="bg-dotted p-1-2">
14+
<div class="prose">
15+
This is the signal based Counter island that comes with Fresh init.
16+
</div>
17+
18+
<div class="flex justify-center gap-3-4 py-1-1 mx-auto">
19+
<button
20+
type="button"
21+
class="btn btn-primary btn-sm"
22+
onClick={() => props.count.value -= 1}
23+
tabIndex={0}
24+
>
25+
-1
26+
</button>
27+
<span class="text-xl tabular-nums">{props.count}</span>
28+
<button
29+
type="button"
30+
class="btn btn-primary btn-sm"
31+
onClick={() => props.count.value += 1}
32+
tabIndex={0}
33+
>
34+
+1
35+
</button>
36+
</div>
37+
</div>
2738
</div>
2839
);
2940
}

islands/GibberishChat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default function () {
4444
<form class="join" onSubmit={userSubmit}>
4545
<input
4646
id="gibberish-input"
47-
class="join-item input input-sm"
47+
class="join-item input input-sm mr-px"
4848
type="text"
4949
tabindex={0}
5050
/>

islands/ImageModal.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useEffect, useRef } from "preact/hooks";
2+
import { handleKeyboard, Key } from "@carcajada/teclas";
3+
4+
export default function ImageModal(props: { src: string }) {
5+
const dialogRef = useRef<HTMLDialogElement>(null);
6+
7+
useEffect(() => {
8+
const dlg = dialogRef.current!;
9+
const onBackdropClick = (e: MouseEvent) => {
10+
if (e.target === dlg) dlg.close();
11+
};
12+
dlg.addEventListener("click", onBackdropClick);
13+
return () => dlg.removeEventListener("click", onBackdropClick);
14+
}, []);
15+
16+
return (
17+
<div>
18+
<img
19+
class="vignette cursor-pointer"
20+
tabindex={0}
21+
src={props.src}
22+
onClick={() => dialogRef.current!.showModal()}
23+
onKeyUp={handleKeyboard([
24+
{ keys: [Key.Enter], cb: () => dialogRef.current!.showModal() },
25+
])}
26+
/>
27+
<dialog ref={dialogRef} class="modal">
28+
<div class="modal-box inline-block w-auto max-w-none overflow-auto p-0">
29+
<img
30+
class="vignette max-w-none max-h-[90vh] block"
31+
style={{ width: "auto", height: "auto" }}
32+
src={props.src}
33+
/>
34+
</div>
35+
<form method="dialog" class="modal-backdrop">
36+
<button type="submit">close</button>
37+
</form>
38+
</dialog>
39+
</div>
40+
);
41+
}

islands/Keynav.tsx

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,67 +37,50 @@ function findCandidate(
3737
max: currentRect.right + padding,
3838
};
3939

40-
let closest: HTMLElement | null = null;
41-
let minDistance = Infinity;
40+
type Entry = { el: HTMLElement; distance: number };
41+
const entries: Entry[] = [];
4242

4343
for (const el of candidates) {
4444
if (el === current) continue;
4545
const rect = el.getBoundingClientRect();
46-
let distance: number = Infinity;
46+
let distance = Infinity;
47+
let ok = false;
4748

4849
switch (direction) {
4950
case "ArrowRight":
50-
if (
51-
rect.left > currentRect.right &&
52-
overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max)
53-
) {
54-
distance = rect.left - currentRect.right;
55-
} else {
56-
continue;
57-
}
51+
ok = rect.left > currentRect.right &&
52+
overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max);
53+
if (ok) distance = rect.left - currentRect.right;
5854
break;
59-
6055
case "ArrowLeft":
61-
if (
62-
rect.right < currentRect.left &&
63-
overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max)
64-
) {
65-
distance = currentRect.left - rect.right;
66-
} else {
67-
continue;
68-
}
56+
ok = rect.right < currentRect.left &&
57+
overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max);
58+
if (ok) distance = currentRect.left - rect.right;
6959
break;
70-
7160
case "ArrowDown":
72-
if (
73-
rect.top > currentRect.bottom &&
74-
overlaps(rect.left, rect.right, paddedX.min, paddedX.max)
75-
) {
76-
distance = rect.top - currentRect.bottom;
77-
} else {
78-
continue;
79-
}
61+
ok = rect.top > currentRect.bottom &&
62+
overlaps(rect.left, rect.right, paddedX.min, paddedX.max);
63+
if (ok) distance = rect.top - currentRect.bottom;
8064
break;
81-
8265
case "ArrowUp":
83-
if (
84-
rect.bottom < currentRect.top &&
85-
overlaps(rect.left, rect.right, paddedX.min, paddedX.max)
86-
) {
87-
distance = currentRect.top - rect.bottom;
88-
} else {
89-
continue;
90-
}
66+
ok = rect.bottom < currentRect.top &&
67+
overlaps(rect.left, rect.right, paddedX.min, paddedX.max);
68+
if (ok) distance = currentRect.top - rect.bottom;
9169
break;
9270
}
9371

94-
if (distance < minDistance) {
95-
minDistance = distance;
96-
closest = el;
97-
}
72+
if (ok) entries.push({ el, distance });
73+
}
74+
75+
if (entries.length === 0) return null;
76+
77+
// pick the entry with the smallest distance
78+
let best = entries[0];
79+
for (const e of entries) {
80+
if (e.distance < best.distance) best = e;
9881
}
9982

100-
return closest;
83+
return best.el;
10184
}
10285

10386
function resetShake(el: HTMLElement, exclude?: ShakeClass) {

0 commit comments

Comments
 (0)