@atelic-action/ui 3.2.0 → 3.3.0
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/README.md
CHANGED
|
@@ -512,6 +512,8 @@ The primitives that came with them, in `@atelic-action/ui/primitives`: `ImageFra
|
|
|
512
512
|
|
|
513
513
|
The entry also exports the helpers the sections use, for a site's own code: `formatClock`, `formatHoursRange`, and `isClosed` for hours, `isHoneypotFilled`, and `trackEvent`.
|
|
514
514
|
|
|
515
|
+
Hours print on the 12 hour clock unless the site's config sets `clock: "24h"`, which turns "9a to 5p" into "09:00 to 17:00" everywhere hours appear: the footer through `SiteShell`, `LocationSection`, and `HoursTable` (which takes `clock` as a prop when used on its own). `formatClock` and `formatHoursRange` take the same `clock` option for a site's own code.
|
|
516
|
+
|
|
515
517
|
### The Marketing Vite Config and Commands
|
|
516
518
|
|
|
517
519
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atelic-action/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Shared UI for the Atelic templates: site chrome, base styles, the component library, the access gate, and the app shell they install",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
package/src/chrome/SiteShell.tsx
CHANGED
|
@@ -124,7 +124,7 @@ export function SiteShell({
|
|
|
124
124
|
email={site.email}
|
|
125
125
|
hours={site.hours.map((row) => ({
|
|
126
126
|
label: row.labelShort,
|
|
127
|
-
range: formatHoursRange(row, { short: true }),
|
|
127
|
+
range: formatHoursRange(row, { short: true, clock: site.clock }),
|
|
128
128
|
}))}
|
|
129
129
|
attribution={site.attribution}
|
|
130
130
|
note={site.footer?.note}
|
package/src/lib/hours.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import type { OpeningHours } from "../sections/siteConfig";
|
|
1
|
+
import type { Clock, OpeningHours } from "../sections/siteConfig";
|
|
2
|
+
|
|
3
|
+
/** How a time prints: `short` drops a whole hour's minutes on the 12 hour clock. */
|
|
4
|
+
export interface ClockOptions {
|
|
5
|
+
short?: boolean;
|
|
6
|
+
/** "12h" (the default) or "24h"; see `SiteConfig.clock`. */
|
|
7
|
+
clock?: Clock;
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
11
|
* Formats a 24 hour "HH:MM" time as the display style used across the
|
|
5
12
|
* marketing components: "11:00" -> "11:00a", "19:00" -> "7:00p".
|
|
6
13
|
* With short: true, whole hours drop their minutes: "11a", "7p".
|
|
14
|
+
* With clock: "24h", the time prints as "HH:MM" with a leading zero
|
|
15
|
+
* ("09:00", "17:00") and short changes nothing.
|
|
7
16
|
*/
|
|
8
|
-
export function formatClock(time: string, opts:
|
|
17
|
+
export function formatClock(time: string, opts: ClockOptions = {}): string {
|
|
9
18
|
const [hourStr, minuteStr = "00"] = time.split(":");
|
|
10
19
|
const hour24 = Number(hourStr);
|
|
20
|
+
if (opts.clock === "24h") return `${String(hour24 % 24).padStart(2, "0")}:${minuteStr}`;
|
|
11
21
|
const suffix = hour24 % 24 >= 12 ? "p" : "a";
|
|
12
22
|
const hour = hour24 % 12 || 12;
|
|
13
23
|
if (opts.short && minuteStr === "00") return `${hour}${suffix}`;
|
|
@@ -19,10 +29,11 @@ export function isClosed(row: OpeningHours): boolean {
|
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
/**
|
|
22
|
-
* "11:00a to 7:00p" (or "11a to 7p" with short: true
|
|
23
|
-
*
|
|
32
|
+
* "11:00a to 7:00p" (or "11a to 7p" with short: true, or "11:00 to 19:00"
|
|
33
|
+
* with clock: "24h"). Rows without times render their note ("By
|
|
34
|
+
* appointment") when present, else "Closed".
|
|
24
35
|
*/
|
|
25
|
-
export function formatHoursRange(row: OpeningHours, opts:
|
|
36
|
+
export function formatHoursRange(row: OpeningHours, opts: ClockOptions = {}): string {
|
|
26
37
|
if (!row.opens || !row.closes) return row.note ?? "Closed";
|
|
27
38
|
return `${formatClock(row.opens, opts)} to ${formatClock(row.closes, opts)}`;
|
|
28
39
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { formatHoursRange, isClosed } from "../lib/hours";
|
|
2
|
-
import type { OpeningHours } from "./siteConfig";
|
|
2
|
+
import type { Clock, OpeningHours } from "./siteConfig";
|
|
3
3
|
|
|
4
4
|
export interface HoursTableProps {
|
|
5
5
|
hours: OpeningHours[];
|
|
6
|
+
/** "12h" (the default) or "24h"; pass the site's `SiteConfig.clock`. */
|
|
7
|
+
clock?: Clock;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
/** Day-by-day hours rows with dashed rules and mono times. */
|
|
9
|
-
export function HoursTable({ hours }: HoursTableProps) {
|
|
11
|
+
export function HoursTable({ hours, clock }: HoursTableProps) {
|
|
10
12
|
return (
|
|
11
13
|
<div>
|
|
12
14
|
{hours.map((row) => (
|
|
@@ -15,7 +17,7 @@ export function HoursTable({ hours }: HoursTableProps) {
|
|
|
15
17
|
className={["hours-row", isClosed(row) && "closed"].filter(Boolean).join(" ")}
|
|
16
18
|
>
|
|
17
19
|
<span className="day">{row.label}</span>
|
|
18
|
-
<span className="time">{formatHoursRange(row)}</span>
|
|
20
|
+
<span className="time">{formatHoursRange(row, { clock })}</span>
|
|
19
21
|
</div>
|
|
20
22
|
))}
|
|
21
23
|
</div>
|
|
@@ -7,7 +7,7 @@ import type { SiteConfig } from "./siteConfig";
|
|
|
7
7
|
|
|
8
8
|
/** The keys of the site config the section reads; a whole `SiteConfig` passes as it is. */
|
|
9
9
|
export type LocationSectionSite = MapEmbedSite &
|
|
10
|
-
Pick<SiteConfig, "serviceAreas" | "hours" | "phone">;
|
|
10
|
+
Pick<SiteConfig, "serviceAreas" | "hours" | "clock" | "phone">;
|
|
11
11
|
|
|
12
12
|
export interface LocationSectionProps {
|
|
13
13
|
site: LocationSectionSite;
|
|
@@ -41,7 +41,7 @@ export function LocationSection({ site, eyebrow = "Find us", title, lead }: Loca
|
|
|
41
41
|
|
|
42
42
|
<div className="loc-block">
|
|
43
43
|
<h4>Hours</h4>
|
|
44
|
-
<HoursTable hours={site.hours} />
|
|
44
|
+
<HoursTable hours={site.hours} clock={site.clock} />
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<div className="loc-actions">
|
package/src/sections/index.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* its own.
|
|
7
7
|
*/
|
|
8
8
|
export { isHoneypotFilled } from "../lib/honeypot";
|
|
9
|
-
export { formatClock, formatHoursRange, isClosed } from "../lib/hours";
|
|
9
|
+
export { type ClockOptions, formatClock, formatHoursRange, isClosed } from "../lib/hours";
|
|
10
10
|
export { type EventParams, trackEvent } from "../lib/trackEvent";
|
|
11
11
|
export { BlogIndex, type BlogIndexItem, type BlogIndexProps } from "./BlogIndex";
|
|
12
12
|
export { BlogPost, type BlogPostDoc, type BlogPostProps } from "./BlogPost";
|
|
@@ -65,6 +65,7 @@ export { type SocialPhoto, SocialStrip, type SocialStripProps } from "./SocialSt
|
|
|
65
65
|
export { SplitSection, type SplitSectionProps } from "./SplitSection";
|
|
66
66
|
export type {
|
|
67
67
|
Address,
|
|
68
|
+
Clock,
|
|
68
69
|
CTA,
|
|
69
70
|
Founder,
|
|
70
71
|
GateConfig,
|
|
@@ -24,6 +24,9 @@ export interface CTA {
|
|
|
24
24
|
external?: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** How opening hours print: "12h" reads "9a to 5p", "24h" reads "09:00 to 17:00". */
|
|
28
|
+
export type Clock = "12h" | "24h";
|
|
29
|
+
|
|
27
30
|
export interface OpeningHours {
|
|
28
31
|
/** Row label as displayed, e.g. "Monday" or "Friday to Sunday". */
|
|
29
32
|
label: string;
|
|
@@ -137,6 +140,14 @@ export interface SiteConfig {
|
|
|
137
140
|
*/
|
|
138
141
|
mapImage?: string;
|
|
139
142
|
hours: OpeningHours[];
|
|
143
|
+
/**
|
|
144
|
+
* The clock the hours print on, wherever they appear (the footer, the
|
|
145
|
+
* hours table, the location section). "12h" (the default) reads "9a to 5p"
|
|
146
|
+
* in the footer and "9:00a to 5:00p" in the table; "24h" reads "09:00 to
|
|
147
|
+
* 17:00" in both, with leading zeros. Structured data is unaffected, since
|
|
148
|
+
* `opens` and `closes` are already 24 hour.
|
|
149
|
+
*/
|
|
150
|
+
clock?: Clock;
|
|
140
151
|
/** Neighborhoods or cities served; feeds local SEO copy and structured data. */
|
|
141
152
|
serviceAreas: string[];
|
|
142
153
|
priceRange?: string;
|