|
| 1 | +--- |
| 2 | +/** |
| 3 | + * DateGate — conditionally render content based on build-time date. |
| 4 | + * |
| 5 | + * Defines a date window via `after` and/or `before` props. At build time, |
| 6 | + * the component determines which time period "now" falls into and renders |
| 7 | + * exactly one of three named slots: "before", "during", or "after". |
| 8 | + * |
| 9 | + * Any omitted slot simply renders nothing — use 2 slots for a simple |
| 10 | + * on/off gate or all 3 for a full before/during/after split. |
| 11 | + * |
| 12 | + * Date strings are parsed as UTC midnight when no time portion is given |
| 13 | + * (e.g. "2026-07-14" = 2026-07-14T00:00:00.000Z). For precise time-of-day |
| 14 | + * gating in a specific timezone, include the offset: |
| 15 | + * after="2026-07-14T00:00:00+02:00" (midnight CEST) |
| 16 | + * |
| 17 | + * @example |
| 18 | + * // 3-state: before CfP / open / closed |
| 19 | + * <DateGate after="2026-06-01" before="2026-07-01"> |
| 20 | + * <p slot="before">CfP opens June 1st!</p> |
| 21 | + * <a slot="during" href="/cfp">Submit your talk</a> |
| 22 | + * <p slot="after">CfP closed. See you in Kraków!</p> |
| 23 | + * </DateGate> |
| 24 | + * |
| 25 | + * @example |
| 26 | + * // 2-state: teaser → live (omit "after" slot) |
| 27 | + * <DateGate after="2026-07-14"> |
| 28 | + * <p slot="before">Tickets coming soon!</p> |
| 29 | + * <a slot="during" href="/tickets">Buy Tickets</a> |
| 30 | + * </DateGate> |
| 31 | + */ |
| 32 | +
|
| 33 | +interface Props { |
| 34 | + /** |
| 35 | + * Window start — ISO 8601 date string, inclusive. |
| 36 | + * Date-only strings (e.g. "2026-07-14") are parsed as UTC midnight. |
| 37 | + * Include offset for timezone-aware gating: "2026-07-14T00:00:00+02:00" |
| 38 | + */ |
| 39 | + after?: string; |
| 40 | + /** |
| 41 | + * Window end — ISO 8601 date string, inclusive. |
| 42 | + * Same parsing rules as `after`. |
| 43 | + */ |
| 44 | + before?: string; |
| 45 | +} |
| 46 | +
|
| 47 | +const { after, before } = Astro.props; |
| 48 | +
|
| 49 | +// --- Parse dates --- |
| 50 | +// Date-only strings (no "T") get interpreted as UTC midnight by new Date(). |
| 51 | +// DateTime strings with offset (e.g. "...T00:00:00+02:00") parse correctly. |
| 52 | +const parseDate = (s: string): Date => { |
| 53 | + const iso = s.includes("T") ? s : `${s}T00:00:00.000Z`; |
| 54 | + return new Date(iso); |
| 55 | +}; |
| 56 | +
|
| 57 | +const now = new Date(); |
| 58 | +const afterDate = after ? parseDate(after) : null; |
| 59 | +const beforeDate = before ? parseDate(before) : null; |
| 60 | +
|
| 61 | +// --- Determine which period "now" falls into --- |
| 62 | +type Period = "before" | "during" | "after"; |
| 63 | +
|
| 64 | +let period: Period = "during"; |
| 65 | +
|
| 66 | +if (afterDate && beforeDate) { |
| 67 | + if (now < afterDate) period = "before"; |
| 68 | + else if (now > beforeDate) period = "after"; |
| 69 | + else period = "during"; |
| 70 | +} else if (afterDate) { |
| 71 | + period = now < afterDate ? "before" : "during"; |
| 72 | +} else if (beforeDate) { |
| 73 | + period = now > beforeDate ? "after" : "during"; |
| 74 | +} |
| 75 | +--- |
| 76 | + |
| 77 | +{period === "before" ? <slot name="before" /> : null} |
| 78 | +{period === "during" ? <slot name="during" /> : null} |
| 79 | +{period === "after" ? <slot name="after" /> : null} |
0 commit comments