@atelic-action/ui 3.2.0 → 3.3.1
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 +3 -0
- package/package.json +1 -1
- package/src/chrome/SiteShell.tsx +1 -1
- package/src/lib/hours.ts +16 -5
- package/src/sections/HoursTable.tsx +5 -3
- package/src/sections/LocationSection.tsx +2 -2
- package/src/sections/index.ts +2 -1
- package/src/sections/siteConfig.ts +11 -0
- package/src/styles/components.css +12 -0
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Every export, grouped by the entry it is imported from. The gallery column links
|
|
|
23
23
|
| `IconList` | `@atelic-action/ui/components` | A list whose bullets are icons, one for the list or one per item, each in a tone | [#icon-list](https://ui.atelic.me/#icon-list) |
|
|
24
24
|
| `DataTable` | `@atelic-action/ui/components` | A table whose first column names each row, sortable, stacking on a phone | [#data-table](https://ui.atelic.me/#data-table) |
|
|
25
25
|
| `ReportCard` | `@atelic-action/ui/components` | A name, a grade, and a line per row, in labeled groups, each row a link to its section | [#report-card](https://ui.atelic.me/#report-card) |
|
|
26
|
+
| `reportCardLabels`, `ReportCardLabels` | `@atelic-action/ui/components` | The words a screen reader hears around a report card's letters by default, and their type | none |
|
|
26
27
|
| `Rank` | `@atelic-action/ui/components` | A figure with its badge in a tone, for a table cell: a rank, a place in an answer, or a dash | [#rank](https://ui.atelic.me/#rank) |
|
|
27
28
|
| `StatRow` | `@atelic-action/ui/components` | A row of large figures with periods, deltas, and trends | [#stat-row](https://ui.atelic.me/#stat-row) |
|
|
28
29
|
| `Callout` | `@atelic-action/ui/components` | One finding: an icon in its tone, the title, the explainer | [#callout](https://ui.atelic.me/#callout) |
|
|
@@ -512,6 +513,8 @@ The primitives that came with them, in `@atelic-action/ui/primitives`: `ImageFra
|
|
|
512
513
|
|
|
513
514
|
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
515
|
|
|
516
|
+
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.
|
|
517
|
+
|
|
515
518
|
### The Marketing Vite Config and Commands
|
|
516
519
|
|
|
517
520
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atelic-action/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
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;
|
|
@@ -661,11 +661,23 @@
|
|
|
661
661
|
grid-column: 4;
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
|
+
/* A clipping card can defeat a row's break-inside in print engines other
|
|
665
|
+
than Chrome, so the card stops clipping there and the first group
|
|
666
|
+
label rounds its own top corners instead. A label or a heading never
|
|
667
|
+
ends a page alone. */
|
|
664
668
|
@media print {
|
|
665
669
|
.atelic-ui .rc-card {
|
|
670
|
+
overflow: visible;
|
|
666
671
|
box-shadow: none;
|
|
667
672
|
border-color: var(--neutral-200);
|
|
668
673
|
}
|
|
674
|
+
.atelic-ui .rc-card > .rc-group:first-child {
|
|
675
|
+
border-radius: calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0 0;
|
|
676
|
+
}
|
|
677
|
+
.atelic-ui .rc-head,
|
|
678
|
+
.atelic-ui .rc-group {
|
|
679
|
+
break-after: avoid;
|
|
680
|
+
}
|
|
669
681
|
.atelic-ui .rc-row {
|
|
670
682
|
break-inside: avoid;
|
|
671
683
|
}
|