Add Countdown component
This commit is contained in:
parent
8f382823ad
commit
a83d651a52
58
src/back/jdd-components/countdown/countdown.css
Normal file
58
src/back/jdd-components/countdown/countdown.css
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
@keyframes countdown-tick {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
49% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown {
|
||||||
|
--height: 60px;
|
||||||
|
font-size: 32px;
|
||||||
|
|
||||||
|
.countdown__wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-number {
|
||||||
|
background-color: var(--color-brand-text-bg);
|
||||||
|
color: var(--color-brand-text-fg);
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
flex-flow: column;
|
||||||
|
height: var(--height);
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 4px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.countdown-number__unit {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
display: flex;
|
||||||
|
height: var(--height);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator--seconds {
|
||||||
|
animation: countdown-tick linear infinite 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 400px) {
|
||||||
|
.separator--seconds,
|
||||||
|
[data-unit="second"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
src/back/jdd-components/countdown/countdown.jdd.tsx
Normal file
72
src/back/jdd-components/countdown/countdown.jdd.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { TempstreamJSX } from "tempstream";
|
||||||
|
import type { ComponentToHTMLArgs } from "@sealcode/jdd";
|
||||||
|
import { Component, ComponentArguments } from "@sealcode/jdd";
|
||||||
|
import { calculateDifference } from "./difference.js";
|
||||||
|
|
||||||
|
const component_arguments = {
|
||||||
|
date: new ComponentArguments.ShortText().setExampleValues(["2025-08-05"]),
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
function renderBlock({
|
||||||
|
number,
|
||||||
|
unit,
|
||||||
|
unit_label,
|
||||||
|
}: {
|
||||||
|
number: number;
|
||||||
|
unit: "day" | "hour" | "minute" | "second";
|
||||||
|
unit_label: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div class="countdown-number" data-unit={unit}>
|
||||||
|
<div class="countdown-number__digits" data-countdown-target={unit}>
|
||||||
|
{unit == "second" || unit == "minute"
|
||||||
|
? number.toString().padStart(2, "0")
|
||||||
|
: number}
|
||||||
|
</div>
|
||||||
|
<div class="countdown-number__unit">{unit_label}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Countdown extends Component<typeof component_arguments> {
|
||||||
|
getArguments() {
|
||||||
|
return component_arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
async toHTML({
|
||||||
|
args: { date },
|
||||||
|
classes,
|
||||||
|
jdd_context: { render_markdown, render_image, language },
|
||||||
|
}: ComponentToHTMLArgs<typeof component_arguments>): Promise<string> {
|
||||||
|
const { days, hours, minutes, seconds } = calculateDifference(date);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={["countdown", ...classes]}
|
||||||
|
data-controller="countdown"
|
||||||
|
data-countdown-date-value={date}
|
||||||
|
>
|
||||||
|
<div class="countdown__wrapper">
|
||||||
|
{renderBlock({ number: days, unit: "day", unit_label: "days" })}
|
||||||
|
{renderBlock({ number: hours, unit: "hour", unit_label: "hours" })}
|
||||||
|
<span class="separator">:</span>
|
||||||
|
{renderBlock({
|
||||||
|
number: minutes,
|
||||||
|
unit: "minute",
|
||||||
|
unit_label: "minutes",
|
||||||
|
})}
|
||||||
|
<span
|
||||||
|
class="separator separator--seconds"
|
||||||
|
data-countdown-target="ticker"
|
||||||
|
>
|
||||||
|
:
|
||||||
|
</span>
|
||||||
|
{renderBlock({
|
||||||
|
number: seconds,
|
||||||
|
unit: "second",
|
||||||
|
unit_label: "seconds",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
src/back/jdd-components/countdown/countdown.stimulus.ts
Normal file
40
src/back/jdd-components/countdown/countdown.stimulus.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
||||||
|
import { Controller } from "stimulus";
|
||||||
|
import { calculateDifference } from "./difference.js";
|
||||||
|
|
||||||
|
export default class Countdown extends Controller {
|
||||||
|
currentIndex = 0;
|
||||||
|
interval_id: number;
|
||||||
|
|
||||||
|
declare dateValue: string;
|
||||||
|
|
||||||
|
static values = {
|
||||||
|
date: String,
|
||||||
|
};
|
||||||
|
|
||||||
|
static targets = ["day", "hour", "minute", "second", "ticker"];
|
||||||
|
declare dayTarget: HTMLDivElement;
|
||||||
|
declare hourTarget: HTMLDivElement;
|
||||||
|
declare minuteTarget: HTMLDivElement;
|
||||||
|
declare secondTarget: HTMLDivElement;
|
||||||
|
declare tickerTarget: HTMLDivElement;
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
this.interval_id = setInterval(() => this.tick(), 1000) as unknown as number;
|
||||||
|
this.tickerTarget.style.setProperty("animation", "none"); // we're gonna animate it ourselves to keep it in sync with the js-changed seconds countdown
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect() {
|
||||||
|
clearInterval(this.interval_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
const { days, hours, minutes, seconds } = calculateDifference(this.dateValue);
|
||||||
|
this.dayTarget.textContent = days.toString();
|
||||||
|
this.hourTarget.textContent = hours.toString();
|
||||||
|
this.minuteTarget.textContent = minutes.toString().padStart(2, "0");
|
||||||
|
this.secondTarget.textContent = seconds.toString().padStart(2, "0");
|
||||||
|
this.tickerTarget.style.setProperty("opacity", seconds % 2 ? "0" : "1");
|
||||||
|
}
|
||||||
|
}
|
20
src/back/jdd-components/countdown/difference.ts
Normal file
20
src/back/jdd-components/countdown/difference.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const second = 1000;
|
||||||
|
const minute = 60 * second;
|
||||||
|
const hour = 60 * minute;
|
||||||
|
const day = 24 * hour;
|
||||||
|
|
||||||
|
export function calculateDifference(date: string) {
|
||||||
|
const now = new Date();
|
||||||
|
const target_date = new Date(date);
|
||||||
|
const distance = target_date.getTime() - now.getTime();
|
||||||
|
if (distance <= 0) {
|
||||||
|
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
|
||||||
|
}
|
||||||
|
const days = Math.floor(distance / day);
|
||||||
|
const hours = Math.floor((distance - days * day) / hour);
|
||||||
|
const minutes = Math.floor((distance - days * day - hours * hour) / minute);
|
||||||
|
const seconds = Math.floor(
|
||||||
|
(distance - days * day - hours * hour - minutes * minute) / second
|
||||||
|
);
|
||||||
|
return { days, hours, minutes, seconds };
|
||||||
|
}
|
@ -7,6 +7,9 @@ const application = Application.start();
|
|||||||
import { default as AutoscrollingImages } from "./../back/jdd-components/autoscrolling-images/autoscrolling-images.stimulus.js";
|
import { default as AutoscrollingImages } from "./../back/jdd-components/autoscrolling-images/autoscrolling-images.stimulus.js";
|
||||||
application.register("autoscrolling-images", AutoscrollingImages);
|
application.register("autoscrolling-images", AutoscrollingImages);
|
||||||
|
|
||||||
|
import { default as Countdown } from "./../back/jdd-components/countdown/countdown.stimulus.js";
|
||||||
|
application.register("countdown", Countdown);
|
||||||
|
|
||||||
import { default as MapWithPins } from "./../back/jdd-components/map-with-pins/map-with-pins.stimulus.js";
|
import { default as MapWithPins } from "./../back/jdd-components/map-with-pins/map-with-pins.stimulus.js";
|
||||||
application.register("map-with-pins", MapWithPins);
|
application.register("map-with-pins", MapWithPins);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user