Skip to content

Commit f46545f

Browse files
v0.0.14
1 parent 3bc167d commit f46545f

File tree

7 files changed

+133
-312
lines changed

7 files changed

+133
-312
lines changed

CHANGELOG.md

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

33
## Version History
44

5+
### v0.0.14
6+
7+
- Added updated versions for the `lunchbox-css` and `@lunchbox/ui` modules.
8+
- Added shields, links, and keyboard navigation instructions.
9+
510
### v0.0.13
611

712
- Added the `<PointlessProcess⁄>`, `<Countdown⁄>`, and `<ImageModal⁄>` islands.

components/Shieldio.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function (props: { src: string; alt: string; href: string }) {
2+
return (
3+
<a href={props.href} tabIndex={0}>
4+
<img
5+
class="vignette grayscale"
6+
src={props.src}
7+
alt={props.alt}
8+
/>
9+
</a>
10+
);
11+
}

deno.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
"@/": "./",
2222
"@carcajada/teclas": "jsr:@carcajada/teclas@^1.0.8",
2323
"@egamagz/time-ago": "jsr:@egamagz/time-ago@^2025.4.9",
24-
"@lunchbox/ui": "jsr:@lunchbox/ui@3.0.0",
25-
"lunchbox-css": "npm:lunchbox-css@^0.1.6",
24+
"@lunchbox/ui": "jsr:@lunchbox/ui@3.0.1",
25+
"lunchbox-css": "npm:lunchbox-css@^0.1.7",
2626
"@vyn/cn": "jsr:@vyn/cn@^0.1.2",
2727
"@tailwindcss/typography": "npm:@tailwindcss/typography@^0.5.16",
2828
"daisyui": "npm:daisyui@^5.0.37",

islands/Keynav.tsx

Lines changed: 1 addition & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,5 @@
11
import { useEffect } from "preact/hooks";
2-
3-
const DIRECTIONS = [
4-
"ArrowUp",
5-
"ArrowDown",
6-
"ArrowLeft",
7-
"ArrowRight",
8-
];
9-
type Direction = typeof DIRECTIONS[number];
10-
11-
const SHAKE_CLASSES = [
12-
"shake_up",
13-
"shake_down",
14-
"shake_left",
15-
"shake_right",
16-
"shake",
17-
] as const;
18-
type ShakeClass = typeof SHAKE_CLASSES[number];
19-
20-
function overlaps(aMin: number, aMax: number, bMin: number, bMax: number) {
21-
return !(aMax < bMin || aMin > bMax);
22-
}
23-
24-
function findCandidate(
25-
current: HTMLElement,
26-
direction: Direction,
27-
candidates: HTMLElement[],
28-
padding: number = 0,
29-
): HTMLElement | null {
30-
const currentRect = current.getBoundingClientRect();
31-
const paddedY = {
32-
min: currentRect.top - padding,
33-
max: currentRect.bottom + padding,
34-
};
35-
const paddedX = {
36-
min: currentRect.left - padding,
37-
max: currentRect.right + padding,
38-
};
39-
40-
type Entry = { el: HTMLElement; distance: number };
41-
const entries: Entry[] = [];
42-
43-
for (const el of candidates) {
44-
if (el === current) continue;
45-
const rect = el.getBoundingClientRect();
46-
let distance = Infinity;
47-
let ok = false;
48-
49-
switch (direction) {
50-
case "ArrowRight":
51-
ok = rect.left > currentRect.right &&
52-
overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max);
53-
if (ok) distance = rect.left - currentRect.right;
54-
break;
55-
case "ArrowLeft":
56-
ok = rect.right < currentRect.left &&
57-
overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max);
58-
if (ok) distance = currentRect.left - rect.right;
59-
break;
60-
case "ArrowDown":
61-
ok = rect.top > currentRect.bottom &&
62-
overlaps(rect.left, rect.right, paddedX.min, paddedX.max);
63-
if (ok) distance = rect.top - currentRect.bottom;
64-
break;
65-
case "ArrowUp":
66-
ok = rect.bottom < currentRect.top &&
67-
overlaps(rect.left, rect.right, paddedX.min, paddedX.max);
68-
if (ok) distance = currentRect.top - rect.bottom;
69-
break;
70-
}
71-
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;
81-
}
82-
83-
return best.el;
84-
}
85-
86-
function resetShake(el: HTMLElement, exclude?: ShakeClass) {
87-
SHAKE_CLASSES.forEach((cls) => {
88-
if (cls !== exclude && el.classList.contains(cls)) {
89-
el.classList.remove(cls);
90-
}
91-
});
92-
}
93-
94-
function handleKeyDown(this: HTMLElement, e: KeyboardEvent) {
95-
const { key } = e;
96-
97-
if (key === "Enter") {
98-
resetShake(this);
99-
void this.offsetWidth;
100-
this.classList.add("shake");
101-
return;
102-
}
103-
104-
if (key === "Esc") this.blur();
105-
106-
if (!DIRECTIONS.includes(key)) return;
107-
108-
e.preventDefault();
109-
resetShake(this);
110-
111-
const tabbedElements = Array.from(
112-
document.querySelectorAll<HTMLElement>('[tabindex="0"]'),
113-
);
114-
const candidate = findCandidate(this, key, tabbedElements, 100);
115-
116-
if (candidate) {
117-
this.removeEventListener("keydown", handleKeyDown);
118-
candidate.focus();
119-
return;
120-
}
121-
122-
const dir = key.replace("Arrow", "").toLowerCase();
123-
const shakeClass = `shake_${dir}` as ShakeClass;
124-
125-
this.classList.add(shakeClass);
126-
}
127-
128-
/**
129-
* Attach the `handleKeyDown()` listener when an element is focused.
130-
*/
131-
function handleFocusIn(e: FocusEvent) {
132-
const t = e.target;
133-
if (t instanceof HTMLElement && t.tabIndex === 0) {
134-
t.addEventListener("keydown", handleKeyDown);
135-
}
136-
}
137-
138-
/**
139-
* Remove the `handleKeyDown()` listener when an element is focused.
140-
*/
141-
function handleFocusOut(e: FocusEvent) {
142-
const t = e.target;
143-
if (t instanceof HTMLElement && t.tabIndex === 0) {
144-
t.removeEventListener("keydown", handleKeyDown);
145-
resetShake(t);
146-
}
147-
}
148-
149-
function keynav() {
150-
document.addEventListener("focusin", handleFocusIn);
151-
document.addEventListener("focusout", handleFocusOut);
152-
}
2+
import { keynav } from "@lunchbox/ui";
1533

1544
export default function () {
1555
useEffect(keynav, []);

islands/RandomQuote.tsx

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function () {
1919
<div class="prose p-1-2 bg-base-200">
2020
<h2>Random quote generator</h2>
2121
</div>
22-
<div class="bg-dotted p-1-2 mb-1-1">
22+
<div class="bg-dotted p-1-2">
2323
<div class="prose">
2424
<p class="mb-1-1">
2525
This feed is brought to you by the{" "}
@@ -43,23 +43,27 @@ export default function () {
4343
Give me a quote!
4444
</button>
4545
</div>
46-
<div class="stack w-full">
47-
{isLoading ? <div class="skeleton min-h-16"></div> : null}
48-
{quotes.map((quote, i) => (
49-
<div
50-
class="bg-base-200 border border-base-300 p-1-2 rounded cursor-pointer"
51-
key={i}
52-
tabindex={i === 0 ? 0 : -1}
53-
onClick={() => setQuotes(quotes.slice(1))}
54-
onKeyUp={handleKeyboard([{
55-
keys: [Key.Enter],
56-
cb: () => setQuotes(quotes.slice(1)),
57-
}])}
58-
>
59-
{quote}
46+
{quotes.length > 0 || isLoading
47+
? (
48+
<div class="stack w-full mt-1-1">
49+
{isLoading ? <div class="skeleton min-h-16"></div> : null}
50+
{quotes.map((quote, i) => (
51+
<div
52+
class="bg-base-200 border border-base-300 p-1-2 rounded cursor-pointer"
53+
key={i}
54+
tabindex={i === 0 ? 0 : -1}
55+
onClick={() => setQuotes(quotes.slice(1))}
56+
onKeyUp={handleKeyboard([{
57+
keys: [Key.Enter],
58+
cb: () => setQuotes(quotes.slice(1)),
59+
}])}
60+
>
61+
{quote}
62+
</div>
63+
))}
6064
</div>
61-
))}
62-
</div>
65+
)
66+
: null}
6367
</div>
6468
);
6569
}

routes/index.tsx

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { define } from "@/utils.ts";
22
import Logo from "@/components/Logo.tsx";
3+
import Shieldio from "@/components/Shieldio.tsx";
34
import RandomQuote from "@/islands/RandomQuote.tsx";
45
import GibberishChat from "@/islands/GibberishChat.tsx";
56
import PointlessProcess from "@/islands/PointlessProcess.tsx";
@@ -10,44 +11,90 @@ export default define.page(function Home() {
1011
return (
1112
<>
1213
<main class="layout my-3-1" style={{ gridTemplateRows: "masonry" }}>
13-
<div class="col-span-full">
14-
<div tabindex={0} class="flex items-center gap-1-1 bg-dotted">
15-
<Logo size={164} />
16-
<div class="prose">
17-
<h1 class="mb-1-4">Welcome to Fresh Lunchbox</h1>
18-
<p></p>
19-
</div>
20-
<p></p>
21-
</div>
22-
</div>
23-
<div class="col-span-full">
24-
<a
25-
href="https://github.com/CarcajadaArtificial/lunchbox/discussions/12"
26-
class="btn btn-small btn-soft"
27-
tabindex={0}
28-
>
29-
Suggest a widget
30-
</a>
31-
</div>
14+
<Header />
15+
<Links />
3216
<Counter count={useSignal(3)} />
3317
<GibberishChat />
3418
<RandomQuote />
3519
<PointlessProcess />
3620
</main>
37-
<div class="bg-dotted">
38-
<footer class="footer layout min-h-36 py-2-1">
39-
<nav class="col-span-full md:col-span-3 lg:col-span-4">
40-
<h6 class="footer-title">Links</h6>
41-
<a
42-
class="link link-hover"
43-
href="https://github.com/CarcajadaArtificial/lunchbox"
44-
tabindex={0}
45-
>
46-
GitHub
47-
</a>
48-
</nav>
49-
</footer>
50-
</div>
21+
<Footer />
5122
</>
5223
);
5324
});
25+
26+
const Footer = () => (
27+
<div class="bg-dotted">
28+
<footer class="footer layout min-h-36 py-2-1">
29+
<nav class="col-span-full md:col-span-3 lg:col-span-4">
30+
<h6 class="footer-title">Links</h6>
31+
<a
32+
class="link link-hover"
33+
href="https://github.com/CarcajadaArtificial/lunchbox"
34+
tabindex={0}
35+
>
36+
GitHub
37+
</a>
38+
</nav>
39+
</footer>
40+
</div>
41+
);
42+
43+
const Links = () => (
44+
<div class="col-span-full">
45+
<div class="mb-1-1 flex gap-1-2">
46+
<Shieldio
47+
alt="GitHub Repo stars"
48+
src="https://img.shields.io/github/stars/CarcajadaArtificial/lunchbox"
49+
href="https://github.com/CarcajadaArtificial/lunchbox"
50+
/>
51+
<Shieldio
52+
alt="JSR Version"
53+
src="https://img.shields.io/jsr/v/%40lunchbox/ui"
54+
href="https://jsr.io/@lunchbox/ui"
55+
/>
56+
</div>
57+
<div class="flex gap-1-2">
58+
<a
59+
href="https://github.com/CarcajadaArtificial/lunchbox/discussions/12"
60+
class="btn btn-small btn-soft"
61+
tabindex={0}
62+
>
63+
Suggest a widget
64+
</a>
65+
<a
66+
href="https://stalecity.net/posts/lunchbox"
67+
class="btn btn-small btn-soft"
68+
tabindex={0}
69+
>
70+
Read the story
71+
</a>
72+
</div>
73+
</div>
74+
);
75+
76+
const Header = () => (
77+
<div class="col-span-full">
78+
<header
79+
tabindex={0}
80+
class="flex flex-col md:flex-row items-center gap-1-1 px-1-2 pb-3-4 bg-dotted"
81+
>
82+
<Logo size={164} />
83+
<div class="prose mt-3-4">
84+
<h1 class="mb-1-4">Welcome to Fresh Lunchbox</h1>
85+
<p>
86+
A lightweight, server-first styling layer built on top of TailwindCSS
87+
v4 and DaisyUI v5, tailor-made for Deno Fresh v2 and Preact.
88+
</p>
89+
<p class="touchscreen-hidden">
90+
This site was made for fun keyboard navigation. For this use the{" "}
91+
<kbd class="kbd -mt-1-4 mx-1-4">Tab</kbd>{" "}
92+
key, then move around using the arrow keys.
93+
<kbd class="kbd -mt-1-4 ml-1-2"></kbd>{" "}
94+
<kbd class="kbd -mt-1-4"></kbd> <kbd class="kbd -mt-1-4"></kbd>{" "}
95+
<kbd class="kbd -mt-1-4"></kbd>
96+
</p>
97+
</div>
98+
</header>
99+
</div>
100+
);

0 commit comments

Comments
 (0)