@djangocfg/ui-core 2.1.571 → 2.1.573
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/components/data/relative-time-card/index.tsx +112 -54
- package/src/components/index.ts +2 -2
- package/src/components/specialized/flag/Flag.tsx +36 -13
- package/src/components/specialized/flag/country-flag-strings.d.ts +5 -0
- package/src/components/specialized/flag/flag-map.ts +270 -23
- package/src/components/specialized/flag/index.ts +2 -2
- package/src/hooks/time/useCountdown.ts +14 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.573",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
|
-
"@djangocfg/i18n": "^2.1.
|
|
133
|
+
"@djangocfg/i18n": "^2.1.573",
|
|
134
134
|
"consola": "^3.4.2",
|
|
135
135
|
"lucide-react": "^0.545.0",
|
|
136
136
|
"moment": "^2.30.1",
|
|
@@ -206,9 +206,9 @@
|
|
|
206
206
|
"vaul": "1.1.2"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
210
|
-
"@djangocfg/i18n": "^2.1.
|
|
211
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/eslint-config": "^2.1.573",
|
|
210
|
+
"@djangocfg/i18n": "^2.1.573",
|
|
211
|
+
"@djangocfg/typescript-config": "^2.1.573",
|
|
212
212
|
"@storybook/react-vite": "^10.5.0",
|
|
213
213
|
"@types/node": "^24.13.3",
|
|
214
214
|
"@types/react": "19.2.15",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import moment from "moment";
|
|
5
4
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
5
|
import { cn } from "../../../lib/utils";
|
|
7
6
|
|
|
@@ -31,13 +30,16 @@ const relativeTimeCardVariants = cva(
|
|
|
31
30
|
interface RelativeTimeCardProps
|
|
32
31
|
extends React.HTMLAttributes<HTMLSpanElement>,
|
|
33
32
|
VariantProps<typeof relativeTimeCardVariants> {
|
|
34
|
-
/** Date value —
|
|
35
|
-
date:
|
|
36
|
-
/**
|
|
33
|
+
/** Date value — a `Date`, an ISO 8601 string, or a timestamp in milliseconds */
|
|
34
|
+
date: DateInput;
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated Ignored. Dates are parsed as ISO 8601; arbitrary input patterns
|
|
37
|
+
* went away with moment. Kept so existing call sites still type-check.
|
|
38
|
+
*/
|
|
37
39
|
inputFormat?: string;
|
|
38
40
|
/** Display format: relative | auto | datetime | custom */
|
|
39
41
|
format?: "relative" | "auto" | "datetime" | "custom";
|
|
40
|
-
/**
|
|
42
|
+
/** Output pattern for `format="custom"`. Supports `YYYY MM DD HH mm ss`. */
|
|
41
43
|
outputFormat?: string;
|
|
42
44
|
/** Update interval in ms for relative format. @default 60000 */
|
|
43
45
|
updateInterval?: number;
|
|
@@ -51,65 +53,117 @@ interface RelativeTimeCardProps
|
|
|
51
53
|
utc?: boolean;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
56
|
+
/* Built on `Intl`, not moment.
|
|
57
|
+
*
|
|
58
|
+
* moment bundles 137 locale files and this module was the last thing importing
|
|
59
|
+
* it here. Because the package entry is `export *` under a file-level
|
|
60
|
+
* `'use client'`, that cost reached every consumer of the barrel, not just the
|
|
61
|
+
* ones rendering a date: measured on the MLS Caribbean property pages
|
|
62
|
+
* 2026-10-17, two 805 KB chunks on every listing, for a component those pages
|
|
63
|
+
* never render.
|
|
64
|
+
*
|
|
65
|
+
* `Intl.RelativeTimeFormat` and `Intl.DateTimeFormat` are in every browser this
|
|
66
|
+
* package targets, and they localise from the `locale` prop rather than from a
|
|
67
|
+
* global `moment.locale()` call — which was shared mutable state, so two cards
|
|
68
|
+
* with different locales fought over it.
|
|
69
|
+
*
|
|
70
|
+
* Two deliberate narrowings, both of moment-only behaviour:
|
|
71
|
+
* - `inputFormat` no longer parses arbitrary patterns. `Date` accepts ISO
|
|
72
|
+
* 8601; anything else was already locale-dependent guesswork.
|
|
73
|
+
* - `outputFormat` accepts the moment-style tokens this component actually
|
|
74
|
+
* shipped with (`YYYY MM DD HH mm ss`), not the full moment grammar.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
type DateInput = Date | string | number;
|
|
78
|
+
|
|
79
|
+
/** A timestamp with no zone designator is UTC here, as the APIs emit it. */
|
|
80
|
+
function toDate(value: DateInput, useUtc: boolean): Date {
|
|
81
|
+
if (value instanceof Date) return value;
|
|
82
|
+
if (typeof value === "number") return new Date(value);
|
|
73
83
|
if (typeof value === "string") {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return m(value);
|
|
84
|
+
const iso =
|
|
85
|
+
useUtc && !/(?:Z|[+-]\d{2}:?\d{2})$/.test(value) ? `${value}Z` : value;
|
|
86
|
+
return new Date(iso);
|
|
78
87
|
}
|
|
79
|
-
|
|
80
|
-
return m();
|
|
88
|
+
return new Date();
|
|
81
89
|
}
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
const RELATIVE_UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
|
|
92
|
+
["year", 365 * 24 * 60 * 60],
|
|
93
|
+
["month", 30 * 24 * 60 * 60],
|
|
94
|
+
["day", 24 * 60 * 60],
|
|
95
|
+
["hour", 60 * 60],
|
|
96
|
+
["minute", 60],
|
|
97
|
+
["second", 1],
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
function getRelativeTime(date: Date, locale: string = "en"): string {
|
|
101
|
+
const seconds = Math.round((date.getTime() - Date.now()) / 1000);
|
|
102
|
+
const absolute = Math.abs(seconds);
|
|
103
|
+
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
104
|
+
|
|
105
|
+
for (const [unit, size] of RELATIVE_UNITS) {
|
|
106
|
+
if (absolute >= size || unit === "second") {
|
|
107
|
+
return formatter.format(Math.round(seconds / size), unit);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return formatter.format(0, "second");
|
|
86
111
|
}
|
|
87
112
|
|
|
88
|
-
function getAutoFormat(date:
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
const diffHours = now.diff(date, "hours");
|
|
92
|
-
const diffDays = now.diff(date, "days");
|
|
113
|
+
function getAutoFormat(date: Date, locale: string = "en"): string {
|
|
114
|
+
const diffHours = (Date.now() - date.getTime()) / 3_600_000;
|
|
115
|
+
const diffDays = diffHours / 24;
|
|
93
116
|
|
|
94
117
|
if (diffHours < 24) {
|
|
95
|
-
return
|
|
118
|
+
return new Intl.DateTimeFormat(locale, {
|
|
119
|
+
hour: "2-digit",
|
|
120
|
+
minute: "2-digit",
|
|
121
|
+
hour12: false,
|
|
122
|
+
}).format(date);
|
|
96
123
|
}
|
|
97
124
|
if (diffDays < 7) {
|
|
98
|
-
return
|
|
125
|
+
return new Intl.DateTimeFormat(locale, { weekday: "short" }).format(date);
|
|
99
126
|
}
|
|
100
127
|
if (diffDays < 365) {
|
|
101
|
-
return
|
|
128
|
+
return new Intl.DateTimeFormat(locale, {
|
|
129
|
+
month: "short",
|
|
130
|
+
day: "numeric",
|
|
131
|
+
}).format(date);
|
|
102
132
|
}
|
|
103
|
-
return
|
|
133
|
+
return new Intl.DateTimeFormat(locale, {
|
|
134
|
+
year: "numeric",
|
|
135
|
+
month: "short",
|
|
136
|
+
day: "numeric",
|
|
137
|
+
}).format(date);
|
|
104
138
|
}
|
|
105
139
|
|
|
106
|
-
function getDateTimeFormat(date:
|
|
107
|
-
|
|
108
|
-
|
|
140
|
+
function getDateTimeFormat(date: Date, locale: string = "en"): string {
|
|
141
|
+
return new Intl.DateTimeFormat(locale, {
|
|
142
|
+
year: "numeric",
|
|
143
|
+
month: "short",
|
|
144
|
+
day: "numeric",
|
|
145
|
+
hour: "2-digit",
|
|
146
|
+
minute: "2-digit",
|
|
147
|
+
hour12: false,
|
|
148
|
+
}).format(date);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The moment-style tokens this component shipped with, and only those. */
|
|
152
|
+
function applyCustomFormat(date: Date, pattern: string): string {
|
|
153
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
154
|
+
const tokens: Record<string, string> = {
|
|
155
|
+
YYYY: String(date.getFullYear()),
|
|
156
|
+
MM: pad(date.getMonth() + 1),
|
|
157
|
+
DD: pad(date.getDate()),
|
|
158
|
+
HH: pad(date.getHours()),
|
|
159
|
+
mm: pad(date.getMinutes()),
|
|
160
|
+
ss: pad(date.getSeconds()),
|
|
161
|
+
};
|
|
162
|
+
return pattern.replace(/YYYY|MM|DD|HH|mm|ss/g, (t) => tokens[t] ?? t);
|
|
109
163
|
}
|
|
110
164
|
|
|
111
165
|
function formatDate(
|
|
112
|
-
date:
|
|
166
|
+
date: Date,
|
|
113
167
|
format: "relative" | "auto" | "datetime" | "custom",
|
|
114
168
|
locale: string,
|
|
115
169
|
outputFormat?: string
|
|
@@ -122,8 +176,7 @@ function formatDate(
|
|
|
122
176
|
case "datetime":
|
|
123
177
|
return getDateTimeFormat(date, locale);
|
|
124
178
|
case "custom":
|
|
125
|
-
|
|
126
|
-
return date.format(outputFormat || "YYYY-MM-DD HH:mm");
|
|
179
|
+
return applyCustomFormat(date, outputFormat || "YYYY-MM-DD HH:mm");
|
|
127
180
|
}
|
|
128
181
|
}
|
|
129
182
|
|
|
@@ -149,10 +202,7 @@ const RelativeTimeCard = React.forwardRef<
|
|
|
149
202
|
},
|
|
150
203
|
ref
|
|
151
204
|
) => {
|
|
152
|
-
const dateObj = React.useMemo(
|
|
153
|
-
() => parseDate(date, inputFormat, utc),
|
|
154
|
-
[date, inputFormat, utc]
|
|
155
|
-
);
|
|
205
|
+
const dateObj = React.useMemo(() => toDate(date, utc), [date, utc]);
|
|
156
206
|
|
|
157
207
|
const [display, setDisplay] = React.useState(() =>
|
|
158
208
|
formatDate(dateObj, format, locale, outputFormat)
|
|
@@ -167,8 +217,16 @@ const RelativeTimeCard = React.forwardRef<
|
|
|
167
217
|
return () => clearInterval(id);
|
|
168
218
|
}, [dateObj, format, locale, outputFormat, updateInterval]);
|
|
169
219
|
|
|
170
|
-
|
|
171
|
-
|
|
220
|
+
// An invalid date would throw from `toISOString`, where moment returned
|
|
221
|
+
// null and rendered "Invalid date". Both attributes are omitted instead.
|
|
222
|
+
const valid = !Number.isNaN(dateObj.getTime());
|
|
223
|
+
const isoString = valid ? dateObj.toISOString() : undefined;
|
|
224
|
+
const title = valid
|
|
225
|
+
? new Intl.DateTimeFormat(locale, {
|
|
226
|
+
dateStyle: "full",
|
|
227
|
+
timeStyle: "short",
|
|
228
|
+
}).format(dateObj)
|
|
229
|
+
: undefined;
|
|
172
230
|
|
|
173
231
|
return (
|
|
174
232
|
<time
|
package/src/components/index.ts
CHANGED
|
@@ -286,8 +286,8 @@ export { Primitive, dispatchDiscreteCustomEvent } from './specialized/primitive'
|
|
|
286
286
|
export type { PrimitivePropsWithRef } from './specialized/primitive';
|
|
287
287
|
export { ImageWithFallback } from './specialized/image-with-fallback';
|
|
288
288
|
export type { ImageWithFallbackProps } from './specialized/image-with-fallback';
|
|
289
|
-
export { Flag, LanguageFlag, LANGUAGE_TO_COUNTRY, getLanguageCountryCode,
|
|
290
|
-
export type { FlagProps, LanguageFlagProps,
|
|
289
|
+
export { Flag, LanguageFlag, LANGUAGE_TO_COUNTRY, getLanguageCountryCode, FLAG_LOADERS } from './specialized/flag';
|
|
290
|
+
export type { FlagProps, LanguageFlagProps, FlagLoader } from './specialized/flag';
|
|
291
291
|
|
|
292
292
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
293
293
|
// Effects
|
|
@@ -4,13 +4,11 @@ import * as React from 'react';
|
|
|
4
4
|
|
|
5
5
|
import { cn } from '../../../lib/utils';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { FLAG_LOADERS } from './flag-map';
|
|
8
8
|
import { getLanguageCountryCode } from './language-to-country';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Presentational attributes only.
|
|
12
|
-
* components type their handlers against an element that cannot exist, so no
|
|
13
|
-
* event handler passed here could be given an honest signature.
|
|
11
|
+
* Presentational attributes only.
|
|
14
12
|
*/
|
|
15
13
|
export interface FlagProps {
|
|
16
14
|
/** ISO 3166-1 alpha-2 country code (e.g. `'US'`, `'JP'`). Case-insensitive. */
|
|
@@ -23,24 +21,49 @@ export interface FlagProps {
|
|
|
23
21
|
title?: string;
|
|
24
22
|
}
|
|
25
23
|
|
|
24
|
+
/** Loaded SVG strings, shared by every `Flag` on the page. */
|
|
25
|
+
const loaded = new Map<string, string>();
|
|
26
|
+
|
|
26
27
|
/**
|
|
27
|
-
* SVG country flag (3:2 aspect)
|
|
28
|
-
*
|
|
28
|
+
* SVG country flag (3:2 aspect), loaded on demand — one small chunk per code.
|
|
29
|
+
* Renders an empty box of the same size until it arrives, and nothing for an
|
|
30
|
+
* unknown code.
|
|
29
31
|
*/
|
|
30
|
-
export function Flag({ countryCode, rounded, className,
|
|
32
|
+
export function Flag({ countryCode, rounded, className, width, height, title }: FlagProps) {
|
|
31
33
|
const code = countryCode?.toUpperCase();
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
+
const load = code ? FLAG_LOADERS[code] : undefined;
|
|
35
|
+
const [svg, setSvg] = React.useState(() => (code ? loaded.get(code) : undefined));
|
|
36
|
+
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
if (!code || !load) return;
|
|
39
|
+
const cached = loaded.get(code);
|
|
40
|
+
if (cached) {
|
|
41
|
+
setSvg(cached);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let live = true;
|
|
45
|
+
load().then((m) => {
|
|
46
|
+
loaded.set(code, m.default);
|
|
47
|
+
if (live) setSvg(m.default);
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
live = false;
|
|
51
|
+
};
|
|
52
|
+
}, [code, load]);
|
|
53
|
+
|
|
54
|
+
if (!load) return null;
|
|
34
55
|
return (
|
|
35
|
-
<
|
|
56
|
+
<span
|
|
36
57
|
className={cn(
|
|
37
|
-
'inline-block shrink-0 select-none',
|
|
58
|
+
'inline-block shrink-0 select-none [&>svg]:block [&>svg]:h-full [&>svg]:w-full',
|
|
38
59
|
rounded && 'overflow-hidden rounded-[2px]',
|
|
39
60
|
className,
|
|
40
61
|
)}
|
|
62
|
+
style={{ width, height }}
|
|
63
|
+
title={title}
|
|
41
64
|
aria-hidden="true"
|
|
42
|
-
|
|
43
|
-
{
|
|
65
|
+
// Static SVG from `country-flag-icons`, never user input.
|
|
66
|
+
dangerouslySetInnerHTML={svg ? { __html: svg } : undefined}
|
|
44
67
|
/>
|
|
45
68
|
);
|
|
46
69
|
}
|
|
@@ -1,28 +1,275 @@
|
|
|
1
|
+
/// <reference path="./country-flag-strings.d.ts" />
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* ISO 3166-1 alpha-2 country code → a loader for that one flag's SVG.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* One dynamic import per flag, so a page pays for the flags it shows. The
|
|
6
|
+
* previous static map spread `import * as Flags` into an object, which no
|
|
7
|
+
* bundler can tree-shake: every app that reached `Flag` (a locale switcher
|
|
8
|
+
* with four languages) shipped all ~270 flags, ~250 KB raw.
|
|
9
|
+
*
|
|
10
|
+
* Generated from `country-flag-icons`' own `exports` (`./string/3x2/*`);
|
|
11
|
+
* regenerate when that package adds a code. Subdivision flags (`GB_ENG`,
|
|
12
|
+
* `ES_CT`, …) have no per-file module there, so they are not covered.
|
|
9
13
|
*/
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* `country-flag-icons` declares its components against `HTMLSVGElement` — its
|
|
15
|
-
* own `HTMLElement & SVGElement` intersection, which no DOM node satisfies. Its
|
|
16
|
-
* event-handler props are consequently uncallable with a real SVG event, so
|
|
17
|
-
* `FlagProps` exposes only the presentational attributes `Flag` forwards; a
|
|
18
|
-
* handler would have to be typed against an element that cannot exist.
|
|
19
|
-
*/
|
|
20
|
-
export type FlagSvgComponent = (typeof Flags)[keyof typeof Flags];
|
|
15
|
+
export type FlagLoader = () => Promise<{ default: string }>;
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
export const FLAG_LOADERS: Record<string, FlagLoader> = {
|
|
18
|
+
AC: () => import('country-flag-icons/string/3x2/AC'),
|
|
19
|
+
AD: () => import('country-flag-icons/string/3x2/AD'),
|
|
20
|
+
AE: () => import('country-flag-icons/string/3x2/AE'),
|
|
21
|
+
AF: () => import('country-flag-icons/string/3x2/AF'),
|
|
22
|
+
AG: () => import('country-flag-icons/string/3x2/AG'),
|
|
23
|
+
AI: () => import('country-flag-icons/string/3x2/AI'),
|
|
24
|
+
AL: () => import('country-flag-icons/string/3x2/AL'),
|
|
25
|
+
AM: () => import('country-flag-icons/string/3x2/AM'),
|
|
26
|
+
AO: () => import('country-flag-icons/string/3x2/AO'),
|
|
27
|
+
AQ: () => import('country-flag-icons/string/3x2/AQ'),
|
|
28
|
+
AR: () => import('country-flag-icons/string/3x2/AR'),
|
|
29
|
+
AS: () => import('country-flag-icons/string/3x2/AS'),
|
|
30
|
+
AT: () => import('country-flag-icons/string/3x2/AT'),
|
|
31
|
+
AU: () => import('country-flag-icons/string/3x2/AU'),
|
|
32
|
+
AW: () => import('country-flag-icons/string/3x2/AW'),
|
|
33
|
+
AX: () => import('country-flag-icons/string/3x2/AX'),
|
|
34
|
+
AZ: () => import('country-flag-icons/string/3x2/AZ'),
|
|
35
|
+
BA: () => import('country-flag-icons/string/3x2/BA'),
|
|
36
|
+
BB: () => import('country-flag-icons/string/3x2/BB'),
|
|
37
|
+
BD: () => import('country-flag-icons/string/3x2/BD'),
|
|
38
|
+
BE: () => import('country-flag-icons/string/3x2/BE'),
|
|
39
|
+
BF: () => import('country-flag-icons/string/3x2/BF'),
|
|
40
|
+
BG: () => import('country-flag-icons/string/3x2/BG'),
|
|
41
|
+
BH: () => import('country-flag-icons/string/3x2/BH'),
|
|
42
|
+
BI: () => import('country-flag-icons/string/3x2/BI'),
|
|
43
|
+
BJ: () => import('country-flag-icons/string/3x2/BJ'),
|
|
44
|
+
BL: () => import('country-flag-icons/string/3x2/BL'),
|
|
45
|
+
BM: () => import('country-flag-icons/string/3x2/BM'),
|
|
46
|
+
BN: () => import('country-flag-icons/string/3x2/BN'),
|
|
47
|
+
BO: () => import('country-flag-icons/string/3x2/BO'),
|
|
48
|
+
BQ: () => import('country-flag-icons/string/3x2/BQ'),
|
|
49
|
+
BR: () => import('country-flag-icons/string/3x2/BR'),
|
|
50
|
+
BS: () => import('country-flag-icons/string/3x2/BS'),
|
|
51
|
+
BT: () => import('country-flag-icons/string/3x2/BT'),
|
|
52
|
+
BV: () => import('country-flag-icons/string/3x2/BV'),
|
|
53
|
+
BW: () => import('country-flag-icons/string/3x2/BW'),
|
|
54
|
+
BY: () => import('country-flag-icons/string/3x2/BY'),
|
|
55
|
+
BZ: () => import('country-flag-icons/string/3x2/BZ'),
|
|
56
|
+
CA: () => import('country-flag-icons/string/3x2/CA'),
|
|
57
|
+
CC: () => import('country-flag-icons/string/3x2/CC'),
|
|
58
|
+
CD: () => import('country-flag-icons/string/3x2/CD'),
|
|
59
|
+
CF: () => import('country-flag-icons/string/3x2/CF'),
|
|
60
|
+
CG: () => import('country-flag-icons/string/3x2/CG'),
|
|
61
|
+
CH: () => import('country-flag-icons/string/3x2/CH'),
|
|
62
|
+
CI: () => import('country-flag-icons/string/3x2/CI'),
|
|
63
|
+
CK: () => import('country-flag-icons/string/3x2/CK'),
|
|
64
|
+
CL: () => import('country-flag-icons/string/3x2/CL'),
|
|
65
|
+
CM: () => import('country-flag-icons/string/3x2/CM'),
|
|
66
|
+
CN: () => import('country-flag-icons/string/3x2/CN'),
|
|
67
|
+
CO: () => import('country-flag-icons/string/3x2/CO'),
|
|
68
|
+
CR: () => import('country-flag-icons/string/3x2/CR'),
|
|
69
|
+
CU: () => import('country-flag-icons/string/3x2/CU'),
|
|
70
|
+
CV: () => import('country-flag-icons/string/3x2/CV'),
|
|
71
|
+
CW: () => import('country-flag-icons/string/3x2/CW'),
|
|
72
|
+
CX: () => import('country-flag-icons/string/3x2/CX'),
|
|
73
|
+
CY: () => import('country-flag-icons/string/3x2/CY'),
|
|
74
|
+
CZ: () => import('country-flag-icons/string/3x2/CZ'),
|
|
75
|
+
DE: () => import('country-flag-icons/string/3x2/DE'),
|
|
76
|
+
DJ: () => import('country-flag-icons/string/3x2/DJ'),
|
|
77
|
+
DK: () => import('country-flag-icons/string/3x2/DK'),
|
|
78
|
+
DM: () => import('country-flag-icons/string/3x2/DM'),
|
|
79
|
+
DO: () => import('country-flag-icons/string/3x2/DO'),
|
|
80
|
+
DZ: () => import('country-flag-icons/string/3x2/DZ'),
|
|
81
|
+
EC: () => import('country-flag-icons/string/3x2/EC'),
|
|
82
|
+
EE: () => import('country-flag-icons/string/3x2/EE'),
|
|
83
|
+
EG: () => import('country-flag-icons/string/3x2/EG'),
|
|
84
|
+
EH: () => import('country-flag-icons/string/3x2/EH'),
|
|
85
|
+
ER: () => import('country-flag-icons/string/3x2/ER'),
|
|
86
|
+
ES: () => import('country-flag-icons/string/3x2/ES'),
|
|
87
|
+
ET: () => import('country-flag-icons/string/3x2/ET'),
|
|
88
|
+
EU: () => import('country-flag-icons/string/3x2/EU'),
|
|
89
|
+
FI: () => import('country-flag-icons/string/3x2/FI'),
|
|
90
|
+
FJ: () => import('country-flag-icons/string/3x2/FJ'),
|
|
91
|
+
FK: () => import('country-flag-icons/string/3x2/FK'),
|
|
92
|
+
FM: () => import('country-flag-icons/string/3x2/FM'),
|
|
93
|
+
FO: () => import('country-flag-icons/string/3x2/FO'),
|
|
94
|
+
FR: () => import('country-flag-icons/string/3x2/FR'),
|
|
95
|
+
GA: () => import('country-flag-icons/string/3x2/GA'),
|
|
96
|
+
GB: () => import('country-flag-icons/string/3x2/GB'),
|
|
97
|
+
GD: () => import('country-flag-icons/string/3x2/GD'),
|
|
98
|
+
GE: () => import('country-flag-icons/string/3x2/GE'),
|
|
99
|
+
GF: () => import('country-flag-icons/string/3x2/GF'),
|
|
100
|
+
GG: () => import('country-flag-icons/string/3x2/GG'),
|
|
101
|
+
GH: () => import('country-flag-icons/string/3x2/GH'),
|
|
102
|
+
GI: () => import('country-flag-icons/string/3x2/GI'),
|
|
103
|
+
GL: () => import('country-flag-icons/string/3x2/GL'),
|
|
104
|
+
GM: () => import('country-flag-icons/string/3x2/GM'),
|
|
105
|
+
GN: () => import('country-flag-icons/string/3x2/GN'),
|
|
106
|
+
GP: () => import('country-flag-icons/string/3x2/GP'),
|
|
107
|
+
GQ: () => import('country-flag-icons/string/3x2/GQ'),
|
|
108
|
+
GR: () => import('country-flag-icons/string/3x2/GR'),
|
|
109
|
+
GS: () => import('country-flag-icons/string/3x2/GS'),
|
|
110
|
+
GT: () => import('country-flag-icons/string/3x2/GT'),
|
|
111
|
+
GU: () => import('country-flag-icons/string/3x2/GU'),
|
|
112
|
+
GW: () => import('country-flag-icons/string/3x2/GW'),
|
|
113
|
+
GY: () => import('country-flag-icons/string/3x2/GY'),
|
|
114
|
+
HK: () => import('country-flag-icons/string/3x2/HK'),
|
|
115
|
+
HM: () => import('country-flag-icons/string/3x2/HM'),
|
|
116
|
+
HN: () => import('country-flag-icons/string/3x2/HN'),
|
|
117
|
+
HR: () => import('country-flag-icons/string/3x2/HR'),
|
|
118
|
+
HT: () => import('country-flag-icons/string/3x2/HT'),
|
|
119
|
+
HU: () => import('country-flag-icons/string/3x2/HU'),
|
|
120
|
+
IC: () => import('country-flag-icons/string/3x2/IC'),
|
|
121
|
+
ID: () => import('country-flag-icons/string/3x2/ID'),
|
|
122
|
+
IE: () => import('country-flag-icons/string/3x2/IE'),
|
|
123
|
+
IL: () => import('country-flag-icons/string/3x2/IL'),
|
|
124
|
+
IM: () => import('country-flag-icons/string/3x2/IM'),
|
|
125
|
+
IN: () => import('country-flag-icons/string/3x2/IN'),
|
|
126
|
+
IO: () => import('country-flag-icons/string/3x2/IO'),
|
|
127
|
+
IQ: () => import('country-flag-icons/string/3x2/IQ'),
|
|
128
|
+
IR: () => import('country-flag-icons/string/3x2/IR'),
|
|
129
|
+
IS: () => import('country-flag-icons/string/3x2/IS'),
|
|
130
|
+
IT: () => import('country-flag-icons/string/3x2/IT'),
|
|
131
|
+
JE: () => import('country-flag-icons/string/3x2/JE'),
|
|
132
|
+
JM: () => import('country-flag-icons/string/3x2/JM'),
|
|
133
|
+
JO: () => import('country-flag-icons/string/3x2/JO'),
|
|
134
|
+
JP: () => import('country-flag-icons/string/3x2/JP'),
|
|
135
|
+
KE: () => import('country-flag-icons/string/3x2/KE'),
|
|
136
|
+
KG: () => import('country-flag-icons/string/3x2/KG'),
|
|
137
|
+
KH: () => import('country-flag-icons/string/3x2/KH'),
|
|
138
|
+
KI: () => import('country-flag-icons/string/3x2/KI'),
|
|
139
|
+
KM: () => import('country-flag-icons/string/3x2/KM'),
|
|
140
|
+
KN: () => import('country-flag-icons/string/3x2/KN'),
|
|
141
|
+
KP: () => import('country-flag-icons/string/3x2/KP'),
|
|
142
|
+
KR: () => import('country-flag-icons/string/3x2/KR'),
|
|
143
|
+
KW: () => import('country-flag-icons/string/3x2/KW'),
|
|
144
|
+
KY: () => import('country-flag-icons/string/3x2/KY'),
|
|
145
|
+
KZ: () => import('country-flag-icons/string/3x2/KZ'),
|
|
146
|
+
LA: () => import('country-flag-icons/string/3x2/LA'),
|
|
147
|
+
LB: () => import('country-flag-icons/string/3x2/LB'),
|
|
148
|
+
LC: () => import('country-flag-icons/string/3x2/LC'),
|
|
149
|
+
LI: () => import('country-flag-icons/string/3x2/LI'),
|
|
150
|
+
LK: () => import('country-flag-icons/string/3x2/LK'),
|
|
151
|
+
LR: () => import('country-flag-icons/string/3x2/LR'),
|
|
152
|
+
LS: () => import('country-flag-icons/string/3x2/LS'),
|
|
153
|
+
LT: () => import('country-flag-icons/string/3x2/LT'),
|
|
154
|
+
LU: () => import('country-flag-icons/string/3x2/LU'),
|
|
155
|
+
LV: () => import('country-flag-icons/string/3x2/LV'),
|
|
156
|
+
LY: () => import('country-flag-icons/string/3x2/LY'),
|
|
157
|
+
MA: () => import('country-flag-icons/string/3x2/MA'),
|
|
158
|
+
MC: () => import('country-flag-icons/string/3x2/MC'),
|
|
159
|
+
MD: () => import('country-flag-icons/string/3x2/MD'),
|
|
160
|
+
ME: () => import('country-flag-icons/string/3x2/ME'),
|
|
161
|
+
MF: () => import('country-flag-icons/string/3x2/MF'),
|
|
162
|
+
MG: () => import('country-flag-icons/string/3x2/MG'),
|
|
163
|
+
MH: () => import('country-flag-icons/string/3x2/MH'),
|
|
164
|
+
MK: () => import('country-flag-icons/string/3x2/MK'),
|
|
165
|
+
ML: () => import('country-flag-icons/string/3x2/ML'),
|
|
166
|
+
MM: () => import('country-flag-icons/string/3x2/MM'),
|
|
167
|
+
MN: () => import('country-flag-icons/string/3x2/MN'),
|
|
168
|
+
MO: () => import('country-flag-icons/string/3x2/MO'),
|
|
169
|
+
MP: () => import('country-flag-icons/string/3x2/MP'),
|
|
170
|
+
MQ: () => import('country-flag-icons/string/3x2/MQ'),
|
|
171
|
+
MR: () => import('country-flag-icons/string/3x2/MR'),
|
|
172
|
+
MS: () => import('country-flag-icons/string/3x2/MS'),
|
|
173
|
+
MT: () => import('country-flag-icons/string/3x2/MT'),
|
|
174
|
+
MU: () => import('country-flag-icons/string/3x2/MU'),
|
|
175
|
+
MV: () => import('country-flag-icons/string/3x2/MV'),
|
|
176
|
+
MW: () => import('country-flag-icons/string/3x2/MW'),
|
|
177
|
+
MX: () => import('country-flag-icons/string/3x2/MX'),
|
|
178
|
+
MY: () => import('country-flag-icons/string/3x2/MY'),
|
|
179
|
+
MZ: () => import('country-flag-icons/string/3x2/MZ'),
|
|
180
|
+
NA: () => import('country-flag-icons/string/3x2/NA'),
|
|
181
|
+
NC: () => import('country-flag-icons/string/3x2/NC'),
|
|
182
|
+
NE: () => import('country-flag-icons/string/3x2/NE'),
|
|
183
|
+
NF: () => import('country-flag-icons/string/3x2/NF'),
|
|
184
|
+
NG: () => import('country-flag-icons/string/3x2/NG'),
|
|
185
|
+
NI: () => import('country-flag-icons/string/3x2/NI'),
|
|
186
|
+
NL: () => import('country-flag-icons/string/3x2/NL'),
|
|
187
|
+
NO: () => import('country-flag-icons/string/3x2/NO'),
|
|
188
|
+
NP: () => import('country-flag-icons/string/3x2/NP'),
|
|
189
|
+
NR: () => import('country-flag-icons/string/3x2/NR'),
|
|
190
|
+
NU: () => import('country-flag-icons/string/3x2/NU'),
|
|
191
|
+
NZ: () => import('country-flag-icons/string/3x2/NZ'),
|
|
192
|
+
OM: () => import('country-flag-icons/string/3x2/OM'),
|
|
193
|
+
PA: () => import('country-flag-icons/string/3x2/PA'),
|
|
194
|
+
PE: () => import('country-flag-icons/string/3x2/PE'),
|
|
195
|
+
PF: () => import('country-flag-icons/string/3x2/PF'),
|
|
196
|
+
PG: () => import('country-flag-icons/string/3x2/PG'),
|
|
197
|
+
PH: () => import('country-flag-icons/string/3x2/PH'),
|
|
198
|
+
PK: () => import('country-flag-icons/string/3x2/PK'),
|
|
199
|
+
PL: () => import('country-flag-icons/string/3x2/PL'),
|
|
200
|
+
PM: () => import('country-flag-icons/string/3x2/PM'),
|
|
201
|
+
PN: () => import('country-flag-icons/string/3x2/PN'),
|
|
202
|
+
PR: () => import('country-flag-icons/string/3x2/PR'),
|
|
203
|
+
PS: () => import('country-flag-icons/string/3x2/PS'),
|
|
204
|
+
PT: () => import('country-flag-icons/string/3x2/PT'),
|
|
205
|
+
PW: () => import('country-flag-icons/string/3x2/PW'),
|
|
206
|
+
PY: () => import('country-flag-icons/string/3x2/PY'),
|
|
207
|
+
QA: () => import('country-flag-icons/string/3x2/QA'),
|
|
208
|
+
RE: () => import('country-flag-icons/string/3x2/RE'),
|
|
209
|
+
RO: () => import('country-flag-icons/string/3x2/RO'),
|
|
210
|
+
RS: () => import('country-flag-icons/string/3x2/RS'),
|
|
211
|
+
RU: () => import('country-flag-icons/string/3x2/RU'),
|
|
212
|
+
RW: () => import('country-flag-icons/string/3x2/RW'),
|
|
213
|
+
SA: () => import('country-flag-icons/string/3x2/SA'),
|
|
214
|
+
SB: () => import('country-flag-icons/string/3x2/SB'),
|
|
215
|
+
SC: () => import('country-flag-icons/string/3x2/SC'),
|
|
216
|
+
SD: () => import('country-flag-icons/string/3x2/SD'),
|
|
217
|
+
SE: () => import('country-flag-icons/string/3x2/SE'),
|
|
218
|
+
SG: () => import('country-flag-icons/string/3x2/SG'),
|
|
219
|
+
SH: () => import('country-flag-icons/string/3x2/SH'),
|
|
220
|
+
SI: () => import('country-flag-icons/string/3x2/SI'),
|
|
221
|
+
SJ: () => import('country-flag-icons/string/3x2/SJ'),
|
|
222
|
+
SK: () => import('country-flag-icons/string/3x2/SK'),
|
|
223
|
+
SL: () => import('country-flag-icons/string/3x2/SL'),
|
|
224
|
+
SM: () => import('country-flag-icons/string/3x2/SM'),
|
|
225
|
+
SN: () => import('country-flag-icons/string/3x2/SN'),
|
|
226
|
+
SO: () => import('country-flag-icons/string/3x2/SO'),
|
|
227
|
+
SR: () => import('country-flag-icons/string/3x2/SR'),
|
|
228
|
+
SS: () => import('country-flag-icons/string/3x2/SS'),
|
|
229
|
+
ST: () => import('country-flag-icons/string/3x2/ST'),
|
|
230
|
+
SV: () => import('country-flag-icons/string/3x2/SV'),
|
|
231
|
+
SX: () => import('country-flag-icons/string/3x2/SX'),
|
|
232
|
+
SY: () => import('country-flag-icons/string/3x2/SY'),
|
|
233
|
+
SZ: () => import('country-flag-icons/string/3x2/SZ'),
|
|
234
|
+
TA: () => import('country-flag-icons/string/3x2/TA'),
|
|
235
|
+
TC: () => import('country-flag-icons/string/3x2/TC'),
|
|
236
|
+
TD: () => import('country-flag-icons/string/3x2/TD'),
|
|
237
|
+
TF: () => import('country-flag-icons/string/3x2/TF'),
|
|
238
|
+
TG: () => import('country-flag-icons/string/3x2/TG'),
|
|
239
|
+
TH: () => import('country-flag-icons/string/3x2/TH'),
|
|
240
|
+
TJ: () => import('country-flag-icons/string/3x2/TJ'),
|
|
241
|
+
TK: () => import('country-flag-icons/string/3x2/TK'),
|
|
242
|
+
TL: () => import('country-flag-icons/string/3x2/TL'),
|
|
243
|
+
TM: () => import('country-flag-icons/string/3x2/TM'),
|
|
244
|
+
TN: () => import('country-flag-icons/string/3x2/TN'),
|
|
245
|
+
TO: () => import('country-flag-icons/string/3x2/TO'),
|
|
246
|
+
TR: () => import('country-flag-icons/string/3x2/TR'),
|
|
247
|
+
TT: () => import('country-flag-icons/string/3x2/TT'),
|
|
248
|
+
TV: () => import('country-flag-icons/string/3x2/TV'),
|
|
249
|
+
TW: () => import('country-flag-icons/string/3x2/TW'),
|
|
250
|
+
TZ: () => import('country-flag-icons/string/3x2/TZ'),
|
|
251
|
+
UA: () => import('country-flag-icons/string/3x2/UA'),
|
|
252
|
+
UG: () => import('country-flag-icons/string/3x2/UG'),
|
|
253
|
+
UM: () => import('country-flag-icons/string/3x2/UM'),
|
|
254
|
+
US: () => import('country-flag-icons/string/3x2/US'),
|
|
255
|
+
UY: () => import('country-flag-icons/string/3x2/UY'),
|
|
256
|
+
UZ: () => import('country-flag-icons/string/3x2/UZ'),
|
|
257
|
+
VA: () => import('country-flag-icons/string/3x2/VA'),
|
|
258
|
+
VC: () => import('country-flag-icons/string/3x2/VC'),
|
|
259
|
+
VE: () => import('country-flag-icons/string/3x2/VE'),
|
|
260
|
+
VG: () => import('country-flag-icons/string/3x2/VG'),
|
|
261
|
+
VI: () => import('country-flag-icons/string/3x2/VI'),
|
|
262
|
+
VN: () => import('country-flag-icons/string/3x2/VN'),
|
|
263
|
+
VU: () => import('country-flag-icons/string/3x2/VU'),
|
|
264
|
+
WF: () => import('country-flag-icons/string/3x2/WF'),
|
|
265
|
+
WS: () => import('country-flag-icons/string/3x2/WS'),
|
|
266
|
+
XA: () => import('country-flag-icons/string/3x2/XA'),
|
|
267
|
+
XC: () => import('country-flag-icons/string/3x2/XC'),
|
|
268
|
+
XK: () => import('country-flag-icons/string/3x2/XK'),
|
|
269
|
+
XO: () => import('country-flag-icons/string/3x2/XO'),
|
|
270
|
+
YE: () => import('country-flag-icons/string/3x2/YE'),
|
|
271
|
+
YT: () => import('country-flag-icons/string/3x2/YT'),
|
|
272
|
+
ZA: () => import('country-flag-icons/string/3x2/ZA'),
|
|
273
|
+
ZM: () => import('country-flag-icons/string/3x2/ZM'),
|
|
274
|
+
ZW: () => import('country-flag-icons/string/3x2/ZW'),
|
|
275
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Flag, LanguageFlag } from './Flag';
|
|
2
2
|
export type { FlagProps, LanguageFlagProps } from './Flag';
|
|
3
3
|
export { LANGUAGE_TO_COUNTRY, getLanguageCountryCode } from './language-to-country';
|
|
4
|
-
export {
|
|
5
|
-
export type {
|
|
4
|
+
export { FLAG_LOADERS } from './flag-map';
|
|
5
|
+
export type { FlagLoader } from './flag-map';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import moment from 'moment';
|
|
4
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
4
|
|
|
6
5
|
export interface CountdownState {
|
|
@@ -64,10 +63,22 @@ export const useCountdown = (targetDate: string | null): CountdownState => {
|
|
|
64
63
|
useEffect(() => {
|
|
65
64
|
if (!targetDate) return;
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
// Plain `Date` arithmetic, not moment: the hook only ever needed the gap in
|
|
67
|
+
// seconds, and the conversion moment did (`.utc().local()`) is what a Date
|
|
68
|
+
// already is internally — an absolute instant, rendered in local time only
|
|
69
|
+
// when formatted. Importing moment for this shipped its 137 locale files to
|
|
70
|
+
// every consumer of this package's barrel export.
|
|
71
|
+
//
|
|
72
|
+
// A timestamp with no zone designator means UTC here, so append the `Z`
|
|
73
|
+
// that `Date` would otherwise read as local time.
|
|
74
|
+
const iso = /(?:Z|[+-]\d{2}:?\d{2})$/.test(targetDate)
|
|
75
|
+
? targetDate
|
|
76
|
+
: `${targetDate}Z`;
|
|
77
|
+
const targetMs = new Date(iso).getTime();
|
|
78
|
+
if (Number.isNaN(targetMs)) return;
|
|
68
79
|
|
|
69
80
|
const tick = () => {
|
|
70
|
-
const diff =
|
|
81
|
+
const diff = Math.round((targetMs - Date.now()) / 1000);
|
|
71
82
|
setCountdown(secondsToState(diff));
|
|
72
83
|
};
|
|
73
84
|
|