Skip to content

Commit f2a84d2

Browse files
authored
Merge pull request #140 from DenisValeev/codex/add-category-selector-for-jokes
Add category selector to jokes apps
2 parents bfad7ed + 67545e2 commit f2a84d2

5 files changed

Lines changed: 263 additions & 12 deletions

File tree

apps/jokes/all-jokes-compact.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
color: inherit;
4949
}
5050

51+
.filter select {
52+
background: rgba(14, 20, 36, 0.9);
53+
border-color: rgba(148, 163, 209, 0.4);
54+
color: inherit;
55+
}
56+
5157
.home-link {
5258
border-color: rgba(148, 163, 209, 0.45);
5359
background: rgba(148, 163, 209, 0.18);
@@ -198,6 +204,25 @@
198204
box-shadow: 0 0 0 3px rgba(112, 132, 255, 0.18);
199205
}
200206

207+
.filter select {
208+
width: 100%;
209+
box-sizing: border-box;
210+
border-radius: 14px;
211+
border: 1px solid var(--table-border);
212+
background: rgba(255, 255, 255, 0.92);
213+
color: inherit;
214+
padding: 12px 16px;
215+
font: inherit;
216+
cursor: pointer;
217+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
218+
}
219+
220+
.filter select:focus-visible {
221+
outline: 2px solid rgba(112, 132, 255, 0.5);
222+
outline-offset: 2px;
223+
box-shadow: 0 0 0 3px rgba(112, 132, 255, 0.18);
224+
}
225+
201226
.table-wrapper {
202227
border: 1px solid var(--table-border);
203228
border-radius: 16px;
@@ -299,6 +324,10 @@ <h1>All Jokes (Compact)</h1>
299324
<form class="filter" data-filter-form>
300325
<label for="compact-joke-filter">Filter jokes</label>
301326
<input id="compact-joke-filter" type="search" name="compact-joke-filter" placeholder="Type to filter…" autocomplete="off" data-filter-input>
327+
<label for="compact-joke-category-select">Filter by category</label>
328+
<select id="compact-joke-category-select" name="compact-joke-category-select" data-category-select aria-label="Joke category" disabled>
329+
<option>Loading categories…</option>
330+
</select>
302331
</form>
303332
<div class="table-wrapper" role="region" aria-live="polite">
304333
<table>

apps/jokes/all-jokes.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
color: inherit;
5454
}
5555

56+
.filter select {
57+
background: rgba(18, 18, 18, 0.9);
58+
border-color: rgba(241, 245, 255, 0.35);
59+
color: inherit;
60+
}
61+
5662
.category-pill {
5763
background: rgba(148, 163, 209, 0.24);
5864
color: rgba(241, 245, 255, 0.9);
@@ -164,6 +170,25 @@
164170
box-shadow: 0 0 0 3px rgba(48, 86, 211, 0.18);
165171
}
166172

173+
.filter select {
174+
width: 100%;
175+
box-sizing: border-box;
176+
border-radius: 12px;
177+
border: 1px solid rgba(0, 0, 0, 0.2);
178+
background: rgba(255, 255, 255, 0.95);
179+
color: inherit;
180+
padding: 10px 14px;
181+
font: inherit;
182+
cursor: pointer;
183+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
184+
}
185+
186+
.filter select:focus-visible {
187+
outline: 2px solid rgba(48, 86, 211, 0.55);
188+
outline-offset: 2px;
189+
box-shadow: 0 0 0 3px rgba(48, 86, 211, 0.18);
190+
}
191+
167192
table {
168193
width: 100%;
169194
border-collapse: collapse;
@@ -210,6 +235,10 @@ <h1>All Jokes</h1>
210235
<form class="filter" data-filter-form>
211236
<label for="joke-filter">Filter jokes</label>
212237
<input id="joke-filter" type="search" name="joke-filter" placeholder="Type to filter…" autocomplete="off" data-filter-input>
238+
<label for="joke-category-select">Filter by category</label>
239+
<select id="joke-category-select" name="joke-category-select" data-category-select aria-label="Joke category" disabled>
240+
<option>Loading categories…</option>
241+
</select>
213242
</form>
214243
<table>
215244
<caption>Setup followed by punchline</caption>

apps/jokes/app.js

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@
44
const prevButton = document.getElementById('prev-button');
55
const nextButton = document.getElementById('next-button');
66
const revealButton = document.getElementById('reveal-button');
7+
const categorySelect = document.getElementById('category-select');
78
const categoryList = document.getElementById('joke-categories');
89

9-
if (!setupEl || !punchlineEl || !prevButton || !nextButton || !revealButton) {
10+
if (!setupEl || !punchlineEl || !prevButton || !nextButton || !revealButton || !categorySelect) {
1011
return;
1112
}
1213

1314
const jokes = Array.isArray(window.jokes)
14-
? window.jokes.filter((entry) => entry && entry.joke)
15+
? window.jokes
16+
.map((entry) => {
17+
if (!entry || typeof entry.joke !== 'string') {
18+
return null;
19+
}
20+
const joke = entry.joke;
21+
const punchline = typeof entry.punchline === 'string' ? entry.punchline : '';
22+
return { joke, punchline };
23+
})
24+
.filter(Boolean)
1525
: [];
1626

1727
let deck = [];
1828
let index = 0;
1929
let punchlineVisible = false;
30+
let activeCategory = 'any';
2031

2132
const categorize =
2233
window.jokeCategoryHelper && typeof window.jokeCategoryHelper.categorize === 'function'
@@ -28,6 +39,57 @@
2839
? window.jokeCategoryHelper.fallback
2940
: 'Classic Dad';
3041

42+
const normalizedJokes = jokes.map((entry) => {
43+
const categories = categorize
44+
? categorize(entry.joke, entry.punchline)
45+
: null;
46+
const list = Array.isArray(categories)
47+
? categories
48+
.map((label) => (typeof label === 'string' ? label.trim() : ''))
49+
.filter((label, idx, array) => label && array.indexOf(label) === idx)
50+
: [];
51+
return {
52+
joke: entry.joke,
53+
punchline: entry.punchline,
54+
categories: list.length ? list : [fallbackCategory],
55+
};
56+
});
57+
58+
const categorySet = new Set();
59+
normalizedJokes.forEach((entry) => {
60+
entry.categories.forEach((label) => {
61+
categorySet.add(label);
62+
});
63+
});
64+
65+
const categoryOptions = Array.from(categorySet).sort((a, b) => a.localeCompare(b));
66+
67+
const optionFragment = document.createDocumentFragment();
68+
const allOption = document.createElement('option');
69+
allOption.value = 'any';
70+
allOption.textContent = 'All jokes';
71+
optionFragment.appendChild(allOption);
72+
73+
categoryOptions.forEach((label) => {
74+
const option = document.createElement('option');
75+
option.value = label;
76+
option.textContent = label;
77+
optionFragment.appendChild(option);
78+
});
79+
80+
categorySelect.appendChild(optionFragment);
81+
categorySelect.value = 'any';
82+
if (categoryOptions.length) {
83+
categorySelect.disabled = false;
84+
}
85+
86+
function getActiveCollection() {
87+
if (activeCategory === 'any') {
88+
return normalizedJokes;
89+
}
90+
return normalizedJokes.filter((entry) => entry.categories.includes(activeCategory));
91+
}
92+
3193
function renderCategories(values) {
3294
if (!categoryList) {
3395
return;
@@ -58,7 +120,8 @@
58120

59121
function ensureDeck() {
60122
if (deck.length === 0) {
61-
deck = shuffle(jokes);
123+
const available = getActiveCollection();
124+
deck = shuffle(available);
62125
index = 0;
63126
}
64127
}
@@ -71,7 +134,9 @@
71134
}
72135

73136
function render() {
74-
if (!jokes.length) {
137+
const available = getActiveCollection();
138+
139+
if (!available.length) {
75140
setupEl.textContent = 'No jokes available.';
76141
punchlineEl.textContent = '';
77142
punchlineEl.classList.remove('is-visible');
@@ -90,10 +155,13 @@
90155

91156
ensureDeck();
92157
const current = deck[index];
158+
if (!current) {
159+
return;
160+
}
93161
const hasPunchline = typeof current.punchline === 'string' && current.punchline.trim().length > 0;
94162
const punchlineText = hasPunchline ? current.punchline : '💩';
95-
const categories = categorize
96-
? categorize(current.joke, current.punchline)
163+
const categories = Array.isArray(current.categories) && current.categories.length
164+
? current.categories
97165
: [fallbackCategory];
98166

99167
setupEl.textContent = current.joke;
@@ -110,12 +178,18 @@
110178

111179
function showNext() {
112180
ensureDeck();
181+
if (!deck.length) {
182+
return;
183+
}
113184
index = (index + 1) % deck.length;
114185
render();
115186
}
116187

117188
function showPrev() {
118189
ensureDeck();
190+
if (!deck.length) {
191+
return;
192+
}
119193
index = (index - 1 + deck.length) % deck.length;
120194
render();
121195
}
@@ -127,6 +201,17 @@
127201
setPunchlineVisible(!punchlineVisible);
128202
}
129203

204+
function resetDeck() {
205+
deck = [];
206+
ensureDeck();
207+
}
208+
209+
categorySelect.addEventListener('change', (event) => {
210+
activeCategory = event.target.value;
211+
resetDeck();
212+
render();
213+
});
214+
130215
prevButton.addEventListener('click', showPrev);
131216
nextButton.addEventListener('click', showNext);
132217
revealButton.addEventListener('click', togglePunchline);
@@ -136,6 +221,12 @@
136221
return;
137222
}
138223

224+
const target = event.target;
225+
const tagName = target && target.tagName;
226+
if (tagName && ['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName)) {
227+
return;
228+
}
229+
139230
if (event.key === 'ArrowRight') {
140231
event.preventDefault();
141232
showNext();

apps/jokes/index.html

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@
179179
width: 100%;
180180
}
181181

182+
.controls__category {
183+
display: flex;
184+
justify-content: center;
185+
width: 100%;
186+
}
187+
182188
button {
183189
border: none;
184190
border-radius: 999px;
@@ -223,6 +229,42 @@
223229
outline-offset: 3px;
224230
}
225231

232+
.category-picker {
233+
position: relative;
234+
display: inline-flex;
235+
align-items: center;
236+
}
237+
238+
.category-picker select {
239+
border-radius: 999px;
240+
border: 1px solid rgba(15, 23, 42, 0.18);
241+
padding: 10px 18px;
242+
font-size: 1rem;
243+
background: rgba(255, 255, 255, 0.92);
244+
color: inherit;
245+
cursor: pointer;
246+
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
247+
min-width: 200px;
248+
}
249+
250+
.category-picker select:focus-visible {
251+
outline: 2px solid rgba(112, 132, 255, 0.6);
252+
outline-offset: 3px;
253+
box-shadow: 0 0 0 3px rgba(112, 132, 255, 0.22);
254+
}
255+
256+
.visually-hidden {
257+
position: absolute;
258+
width: 1px;
259+
height: 1px;
260+
padding: 0;
261+
margin: -1px;
262+
overflow: hidden;
263+
clip: rect(0, 0, 0, 0);
264+
white-space: nowrap;
265+
border: 0;
266+
}
267+
226268
@media (hover: none) and (pointer: coarse) {
227269
button:not(:disabled) {
228270
transition: none;
@@ -235,6 +277,14 @@
235277
box-shadow: none;
236278
}
237279
}
280+
281+
@media (prefers-color-scheme: dark) {
282+
.category-picker select {
283+
background: rgba(14, 20, 36, 0.9);
284+
border-color: rgba(148, 163, 209, 0.35);
285+
color: inherit;
286+
}
287+
}
238288
</style>
239289
</head>
240290
<body>
@@ -260,6 +310,12 @@
260310
<div class="controls__punchline">
261311
<button id="reveal-button" class="secondary" type="button" aria-pressed="false">Punchline</button>
262312
</div>
313+
<div class="controls__category">
314+
<label class="category-picker" for="category-select">
315+
<span class="visually-hidden">Select joke category</span>
316+
<select id="category-select" aria-label="Joke category" disabled></select>
317+
</label>
318+
</div>
263319
</div>
264320
</main>
265321
<script src="jokes.js" defer></script>

0 commit comments

Comments
 (0)