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";
|
||||
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";
|
||||
application.register("map-with-pins", MapWithPins);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user