|
| 1 | +import type { JSXElement } from "revolution/jsx-runtime"; |
| 2 | +import { CodeWindow } from "./code-window.tsx"; |
| 3 | + |
| 4 | +interface UseCase { |
| 5 | + tab: string; |
| 6 | + filename: string; |
| 7 | + code: string; |
| 8 | + note: string; |
| 9 | +} |
| 10 | + |
| 11 | +const CASES: UseCase[] = [ |
| 12 | + { |
| 13 | + tab: "Timeout", |
| 14 | + filename: "timeout.ts", |
| 15 | + code: `function* fetchUser(id) { |
| 16 | + return yield* race([ |
| 17 | + fetchJSON(\`/users/\${id}\`), |
| 18 | + sleep(5000), |
| 19 | + ]); |
| 20 | +}`, |
| 21 | + note: |
| 22 | + "Nothing here cancels the losing request — you'd reach for an AbortController next. You don't have to. When one branch wins, the other is already halted.", |
| 23 | + }, |
| 24 | + { |
| 25 | + tab: "Parallel", |
| 26 | + filename: "parallel.ts", |
| 27 | + code: `function* loadDashboard() { |
| 28 | + const [user, feed, prefs] = yield* all([ |
| 29 | + fetchUser(), |
| 30 | + fetchFeed(), |
| 31 | + fetchPrefs(), |
| 32 | + ]); |
| 33 | + return { user, feed, prefs }; |
| 34 | +}`, |
| 35 | + note: |
| 36 | + "Promise.all rejects on the first failure but lets the other requests finish anyway. Here, one failure halts the siblings — no orphaned work.", |
| 37 | + }, |
| 38 | + { |
| 39 | + tab: "Stream", |
| 40 | + filename: "stream.ts", |
| 41 | + code: `function* listen(socket) { |
| 42 | + for (const message of yield* each(socket)) { |
| 43 | + yield* handle(message); |
| 44 | + yield* each.next(); |
| 45 | + } |
| 46 | +}`, |
| 47 | + note: |
| 48 | + "No socket.close(), no removeEventListener. Leave the loop — return, throw, or cancel — and the subscription closes itself.", |
| 49 | + }, |
| 50 | + { |
| 51 | + tab: "Background", |
| 52 | + filename: "background.ts", |
| 53 | + code: `function* entrypoint() { |
| 54 | + yield* spawn(heartbeat); |
| 55 | + yield* serveRequests(); |
| 56 | +}`, |
| 57 | + note: |
| 58 | + "heartbeat runs for as long as entrypoint does. No handle to keep, no teardown to write — when entrypoint returns or is cancelled, it's halted with it.", |
| 59 | + }, |
| 60 | + { |
| 61 | + tab: "Async teardown", |
| 62 | + filename: "database.ts", |
| 63 | + code: `function* report() { |
| 64 | + const db = yield* resource(function* (provide) { |
| 65 | + const conn = yield* connect(DATABASE_URL); |
| 66 | + yield* ensure(() => conn.close()); |
| 67 | + yield* provide(conn); |
| 68 | + }); |
| 69 | + return yield* runQueries(db); |
| 70 | +}`, |
| 71 | + note: |
| 72 | + "conn.close() is async — an await inside a plain finally is exactly what gets abandoned on cancel. The resource's teardown runs to completion when report() exits, every time.", |
| 73 | + }, |
| 74 | +]; |
| 75 | + |
| 76 | +/** |
| 77 | + * UseCaseTabs — five real Effection use cases that each look too simple to be |
| 78 | + * correct. CSS-only tabs (the site's radio/checkbox-hack convention, no JS): a |
| 79 | + * hidden radio per case drives which pill is active and which panel shows, |
| 80 | + * via `:checked ~` sibling selectors in a scoped <style> block. |
| 81 | + */ |
| 82 | +export function UseCaseTabs(): JSXElement { |
| 83 | + let GRADIENT = "linear-gradient(45deg,#f74d7b,#a855f7,#26abe8)"; |
| 84 | + let css = |
| 85 | + CASES.map((_, i) => |
| 86 | + `#uc-tab-${i}:checked~.uc-tablist label[for="uc-tab-${i}"]{color:#fff!important;border-color:transparent!important;background-image:${GRADIENT}!important;}` + |
| 87 | + `#uc-tab-${i}:checked~.uc-panels .uc-p${i}{display:block}` |
| 88 | + ).join("\n") + |
| 89 | + "\n.uc-panel{display:none}.uc-panel{animation:ucfade .25s ease}" + |
| 90 | + "@keyframes ucfade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}"; |
| 91 | + |
| 92 | + return ( |
| 93 | + <div class="uc-tabs mx-auto max-w-[680px]"> |
| 94 | + <> |
| 95 | + {CASES.map((_, i) => ( |
| 96 | + <input |
| 97 | + class="hidden" |
| 98 | + type="radio" |
| 99 | + name="uc-tabs" |
| 100 | + id={`uc-tab-${i}`} |
| 101 | + checked={i === 0} |
| 102 | + /> |
| 103 | + ))} |
| 104 | + </> |
| 105 | + <div class="uc-tablist mb-5 flex flex-wrap justify-center gap-1.5"> |
| 106 | + {CASES.map((c, i) => ( |
| 107 | + <label |
| 108 | + for={`uc-tab-${i}`} |
| 109 | + class="cursor-pointer rounded-full border-2 border-gray-200 bg-white px-4 py-2 text-sm font-bold text-gray-600 transition-colors hover:border-[#26ABE8] hover:text-[#14315D] dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:hover:text-[#26ABE8]" |
| 110 | + > |
| 111 | + {c.tab} |
| 112 | + </label> |
| 113 | + ))} |
| 114 | + </div> |
| 115 | + <div class="uc-panels"> |
| 116 | + {CASES.map((c, i) => ( |
| 117 | + <div class={`uc-panel uc-p${i}`}> |
| 118 | + <CodeWindow filename={c.filename} code={c.code} /> |
| 119 | + <p class="mx-auto mt-4 max-w-lg text-center text-[15px] leading-relaxed text-gray-600 dark:text-gray-300"> |
| 120 | + {c.note} |
| 121 | + </p> |
| 122 | + </div> |
| 123 | + ))} |
| 124 | + </div> |
| 125 | + <style>{css}</style> |
| 126 | + </div> |
| 127 | + ); |
| 128 | +} |
0 commit comments