@bearmenu/ui 0.8.6 → 0.8.7

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.
@@ -3,10 +3,10 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const alertVariants: (props?: ({
6
- variant?: "default" | "destructive" | "warning" | "success" | null | undefined;
6
+ variant?: "default" | "destructive" | "success" | "warning" | null | undefined;
7
7
  } & class_variance_authority_types.ClassProp) | undefined) => string;
8
8
  declare const Alert: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & VariantProps<(props?: ({
9
- variant?: "default" | "destructive" | "warning" | "success" | null | undefined;
9
+ variant?: "default" | "destructive" | "success" | "warning" | null | undefined;
10
10
  } & class_variance_authority_types.ClassProp) | undefined) => string> & React.RefAttributes<HTMLDivElement>>;
11
11
  declare const AlertTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & React.RefAttributes<HTMLParagraphElement>>;
12
12
  declare const AlertDescription: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const badgeVariants: (props?: ({
7
- variant?: "default" | "destructive" | "warning" | "success" | "outline" | "secondary" | "glass" | "overlay" | "brown" | null | undefined;
7
+ variant?: "default" | "destructive" | "outline" | "secondary" | "glass" | "overlay" | "brown" | "success" | "warning" | null | undefined;
8
8
  live?: boolean | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "default" | "destructive" | "link" | "outline" | "secondary" | "ghost" | "glass" | "overlay" | "brown" | "antagonist" | null | undefined;
6
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | "glass" | "overlay" | "brown" | "antagonist" | null | undefined;
7
7
  size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
8
8
  fullWidth?: boolean | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
@@ -3,12 +3,12 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const cardVariants: (props?: ({
6
- variant?: "muted" | "outline" | "glass" | "surface" | "elevated" | "elevatedPanel" | null | undefined;
6
+ variant?: "outline" | "glass" | "muted" | "surface" | "elevated" | "elevatedPanel" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  interactive?: boolean | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & VariantProps<(props?: ({
11
- variant?: "muted" | "outline" | "glass" | "surface" | "elevated" | "elevatedPanel" | null | undefined;
11
+ variant?: "outline" | "glass" | "muted" | "surface" | "elevated" | "elevatedPanel" | null | undefined;
12
12
  size?: "default" | "sm" | "lg" | null | undefined;
13
13
  interactive?: boolean | null | undefined;
14
14
  } & class_variance_authority_types.ClassProp) | undefined) => string> & React.RefAttributes<HTMLDivElement>>;
@@ -4,11 +4,11 @@ import { DialogProps } from '@radix-ui/react-dialog';
4
4
 
5
5
  declare const Command: React.ForwardRefExoticComponent<Omit<{
6
6
  children?: React.ReactNode;
7
- } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
7
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
8
8
  ref?: React.Ref<HTMLDivElement>;
9
9
  } & {
10
10
  asChild?: boolean;
11
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
11
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
12
12
  label?: string;
13
13
  shouldFilter?: boolean;
14
14
  filter?: (value: string, search: string, keywords?: string[]) => number;
@@ -28,61 +28,61 @@ declare const CommandInput: React.ForwardRefExoticComponent<Omit<Omit<Pick<Pick<
28
28
  ref?: React.Ref<HTMLInputElement>;
29
29
  } & {
30
30
  asChild?: boolean;
31
- }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "onChange" | "type" | "value"> & {
31
+ }, "key" | "asChild" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "onChange" | "type" | "value"> & {
32
32
  value?: string;
33
33
  onValueChange?: (search: string) => void;
34
34
  } & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
35
35
  declare const CommandList: React.ForwardRefExoticComponent<Omit<{
36
36
  children?: React.ReactNode;
37
- } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
37
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
38
38
  ref?: React.Ref<HTMLDivElement>;
39
39
  } & {
40
40
  asChild?: boolean;
41
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
41
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
42
42
  label?: string;
43
43
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
44
44
  declare const CommandEmpty: React.ForwardRefExoticComponent<Omit<{
45
45
  children?: React.ReactNode;
46
- } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
46
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
47
47
  ref?: React.Ref<HTMLDivElement>;
48
48
  } & {
49
49
  asChild?: boolean;
50
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
50
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
51
51
  declare const CommandLoading: React.ForwardRefExoticComponent<Omit<{
52
52
  children?: React.ReactNode;
53
- } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
53
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
54
54
  ref?: React.Ref<HTMLDivElement>;
55
55
  } & {
56
56
  asChild?: boolean;
57
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
57
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
58
58
  progress?: number;
59
59
  label?: string;
60
60
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
61
61
  declare const CommandGroup: React.ForwardRefExoticComponent<Omit<{
62
62
  children?: React.ReactNode;
63
- } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
63
+ } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
64
64
  ref?: React.Ref<HTMLDivElement>;
65
65
  } & {
66
66
  asChild?: boolean;
67
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key">, "heading" | "value"> & {
67
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "heading" | "value"> & {
68
68
  heading?: React.ReactNode;
69
69
  value?: string;
70
70
  forceMount?: boolean;
71
71
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
72
- declare const CommandSeparator: React.ForwardRefExoticComponent<Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
72
+ declare const CommandSeparator: React.ForwardRefExoticComponent<Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
73
73
  ref?: React.Ref<HTMLDivElement>;
74
74
  } & {
75
75
  asChild?: boolean;
76
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
76
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
77
77
  alwaysRender?: boolean;
78
78
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
79
79
  declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
80
80
  children?: React.ReactNode;
81
- } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
81
+ } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
82
82
  ref?: React.Ref<HTMLDivElement>;
83
83
  } & {
84
84
  asChild?: boolean;
85
- }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key">, "onSelect" | "disabled" | "value"> & {
85
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "onSelect" | "disabled" | "value"> & {
86
86
  disabled?: boolean;
87
87
  onSelect?: (value: string) => void;
88
88
  value?: string;
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- declare const DateInput: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "type"> & {
3
+ declare const DateInput: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> & {
4
4
  /** Native input type. Defaults to "date". */
5
5
  type?: "date" | "datetime-local" | "time" | "month" | "week";
6
6
  size?: "sm" | "default" | "lg";
@@ -36,6 +36,7 @@ var EventCard = memo(function EventCard2({
36
36
  }) {
37
37
  var _a;
38
38
  const [imgError, setImgError] = useState(false);
39
+ const time = hideWhen ? "" : formatters.time(event.starts_at);
39
40
  const fee = resolveEventEntryFee(event);
40
41
  const showPoster = !!event.poster_url && !imgError;
41
42
  const venue = (_a = event.place_name) != null ? _a : event.location;
@@ -90,8 +91,10 @@ var EventCard = memo(function EventCard2({
90
91
  ] }),
91
92
  !hideWhen && /* @__PURE__ */ jsxs("div", { className: "mt-[3px] flex items-center gap-1.5 text-xs text-muted-foreground", children: [
92
93
  /* @__PURE__ */ jsx("span", { children: dayLabel != null ? dayLabel : formatters.day(event.starts_at) }),
93
- /* @__PURE__ */ jsx("span", { className: "text-border", "aria-hidden": true, children: "\xB7" }),
94
- /* @__PURE__ */ jsx("span", { children: formatters.time(event.starts_at) }),
94
+ time && /* @__PURE__ */ jsxs(Fragment, { children: [
95
+ /* @__PURE__ */ jsx("span", { className: "text-border", "aria-hidden": true, children: "\xB7" }),
96
+ /* @__PURE__ */ jsx("span", { children: time })
97
+ ] }),
95
98
  showPrice && fee.kind === "paid" && /* @__PURE__ */ jsxs(Fragment, { children: [
96
99
  /* @__PURE__ */ jsx("span", { className: "text-border", "aria-hidden": true, children: "\xB7" }),
97
100
  /* @__PURE__ */ jsx("span", { children: formatters.price(fee.amountRon) })
@@ -32,7 +32,8 @@ type EventCompactRowProps = {
32
32
  event: EventPreview;
33
33
  labels: EventCardLabels;
34
34
  formatters: EventFormatters;
35
- /** Drops the leading time from the sub line. */
35
+ /** Drops the leading time from the sub line. An empty `formatters.time`
36
+ * result (hour unknown) drops it the same way, without the prop. */
36
37
  hideTime?: boolean;
37
38
  /**
38
39
  * Replaces the default "time · venue" sub-line entirely. For surfaces where
@@ -33,6 +33,7 @@ var EventCompactRow = memo(function EventCompactRow2({
33
33
  const fee = resolveEventEntryFee(event);
34
34
  const showPoster = !!event.poster_url && !imgError;
35
35
  const venue = (_a = event.place_name) != null ? _a : event.location;
36
+ const time = formatters.time(event.starts_at);
36
37
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
37
38
  /* @__PURE__ */ jsx("div", { className: "relative aspect-[4/5] w-16 shrink-0 overflow-hidden rounded-[9px] bg-muted", children: showPoster ? (
38
39
  // eslint-disable-next-line @next/next/no-img-element
@@ -68,8 +69,8 @@ var EventCompactRow = memo(function EventCompactRow2({
68
69
  }
69
70
  ),
70
71
  /* @__PURE__ */ jsx("div", { className: "truncate text-[11.5px] text-muted-foreground", children: subtitle ? subtitle : /* @__PURE__ */ jsxs(Fragment, { children: [
71
- !hideTime && /* @__PURE__ */ jsxs(Fragment, { children: [
72
- /* @__PURE__ */ jsx("span", { className: "font-mono tabular-nums", children: formatters.time(event.starts_at) }),
72
+ !hideTime && time && /* @__PURE__ */ jsxs(Fragment, { children: [
73
+ /* @__PURE__ */ jsx("span", { className: "font-mono tabular-nums", children: time }),
73
74
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \xB7 " })
74
75
  ] }),
75
76
  venue
@@ -71,7 +71,11 @@ declare function resolveEventEntryFee(event: EventPreview): EventEntryFee;
71
71
  * locale and no currency of its own.
72
72
  */
73
73
  type EventFormatters = {
74
- /** e.g. `"20:00"` */
74
+ /**
75
+ * e.g. `"20:00"`. Return `""` when the event's hour is not known: the card
76
+ * and the compact row then print the day (or the venue) ALONE, with no
77
+ * separator — never a placeholder word and never "00:00".
78
+ */
75
79
  time: (isoDateTime: string) => string;
76
80
  /** e.g. `"Sat, Sep 5"` — used when a card appears outside its own day section. */
77
81
  day: (isoDateTime: string) => string;
@@ -3,12 +3,12 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const itemVariants: (props?: ({
6
- variant?: "default" | "muted" | "outline" | null | undefined;
6
+ variant?: "default" | "outline" | "muted" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  interactive?: boolean | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare const Item: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & VariantProps<(props?: ({
11
- variant?: "default" | "muted" | "outline" | null | undefined;
11
+ variant?: "default" | "outline" | "muted" | null | undefined;
12
12
  size?: "default" | "sm" | "lg" | null | undefined;
13
13
  interactive?: boolean | null | undefined;
14
14
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
@@ -4,11 +4,11 @@ import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const spinnerVariants: (props?: ({
6
6
  size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
7
- tone?: "default" | "destructive" | "inherit" | "primary" | null | undefined;
7
+ tone?: "inherit" | "default" | "destructive" | "primary" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare const Spinner: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
10
10
  size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
11
- tone?: "default" | "destructive" | "inherit" | "primary" | null | undefined;
11
+ tone?: "inherit" | "default" | "destructive" | "primary" | null | undefined;
12
12
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
13
13
  /** Visually-hidden label for screen readers. Defaults to "Loading". */
14
14
  label?: string;
@@ -3,11 +3,11 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const tagVariants: (props?: ({
6
- variant?: "default" | "destructive" | "warning" | "success" | "primary" | "outline" | "quiet" | null | undefined;
6
+ variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | "quiet" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare const Tag: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
10
- variant?: "default" | "destructive" | "warning" | "success" | "primary" | "outline" | "quiet" | null | undefined;
10
+ variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | "quiet" | null | undefined;
11
11
  size?: "default" | "sm" | "lg" | null | undefined;
12
12
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
13
13
  onRemove?: () => void;
@@ -22,9 +22,9 @@ import { VariantProps } from 'class-variance-authority';
22
22
  * Weights are 400 / 600 / 700. 500 is not in the system.
23
23
  */
24
24
  declare const textVariants: (props?: ({
25
- variant?: "small" | "display" | "h1" | "h2" | "h3" | "h4" | "body-lg" | "body" | "body-sm" | "caption" | "label" | "overline" | null | undefined;
26
- color?: "default" | "destructive" | "inherit" | "muted" | "primary" | "primary-foreground" | null | undefined;
27
- weight?: "bold" | "normal" | "medium" | "semibold" | null | undefined;
25
+ variant?: "h2" | "h3" | "label" | "body" | "caption" | "h1" | "h4" | "small" | "display" | "body-lg" | "body-sm" | "overline" | null | undefined;
26
+ color?: "inherit" | "default" | "destructive" | "muted" | "primary" | "primary-foreground" | null | undefined;
27
+ weight?: "bold" | "medium" | "normal" | "semibold" | null | undefined;
28
28
  leading?: "none" | "normal" | "tight" | "snug" | "relaxed" | "loose" | null | undefined;
29
29
  tracking?: "normal" | "tight" | "wide" | "wider" | null | undefined;
30
30
  wrap?: "balance" | "pretty" | "nowrap" | null | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -108,6 +108,17 @@ describe("EventCard", () => {
108
108
  expect(screen.queryByText("Sat, Sep 5")).not.toBeInTheDocument();
109
109
  });
110
110
 
111
+ it("prints the day alone, with no separator, when the time formatter returns an empty string", () => {
112
+ // "" is the formatter's way of saying the hour is unknown; the card must
113
+ // not print a dangling "·" or invent a placeholder for it.
114
+ const { container } = renderCard({
115
+ formatters: { ...formatters, time: () => "" },
116
+ });
117
+ expect(screen.getByText("Sat, Sep 5")).toBeInTheDocument();
118
+ expect(container.textContent).not.toContain("·");
119
+ expect(container.textContent).not.toContain("20:00");
120
+ });
121
+
111
122
  it("hides the whole meta line when hideWhen is set", () => {
112
123
  renderCard({ hideWhen: true });
113
124
  expect(screen.queryByText("20:00")).not.toBeInTheDocument();
@@ -118,6 +118,7 @@ export const EventCard = memo(function EventCard({
118
118
  fallback = <EventPosterFallback />,
119
119
  }: EventCardProps) {
120
120
  const [imgError, setImgError] = useState(false);
121
+ const time = hideWhen ? "" : formatters.time(event.starts_at);
121
122
 
122
123
  const fee = resolveEventEntryFee(event);
123
124
  const showPoster = !!event.poster_url && !imgError;
@@ -193,10 +194,17 @@ export const EventCard = memo(function EventCard({
193
194
  {!hideWhen && (
194
195
  <div className="mt-[3px] flex items-center gap-1.5 text-xs text-muted-foreground">
195
196
  <span>{dayLabel ?? formatters.day(event.starts_at)}</span>
196
- <span className="text-border" aria-hidden>
197
- ·
198
- </span>
199
- <span>{formatters.time(event.starts_at)}</span>
197
+ {/* An empty time is "hour unknown" (see `EventFormatters.time`): the
198
+ day stands alone, no dangling separator. Consumers used to have
199
+ to `hideWhen` these cards and lose the day with the time. */}
200
+ {time && (
201
+ <>
202
+ <span className="text-border" aria-hidden>
203
+ ·
204
+ </span>
205
+ <span>{time}</span>
206
+ </>
207
+ )}
200
208
  {showPrice && fee.kind === "paid" && (
201
209
  <>
202
210
  <span className="text-border" aria-hidden>
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { EventCompactRow } from "./event-compact-row";
4
+ import type { EventCardLabels, EventFormatters, EventPreview } from "./event-types";
5
+
6
+ const labels: EventCardLabels = { free: "Free", liveNow: "Live now" };
7
+
8
+ const formatters: EventFormatters = {
9
+ time: () => "20:00",
10
+ day: () => "Sat, Sep 5",
11
+ price: (amount) => `${amount} RON`,
12
+ };
13
+
14
+ const event: EventPreview = {
15
+ id: "e1",
16
+ title: "Psihotrop Live",
17
+ location: "Atelier Cafe",
18
+ starts_at: "2026-09-05T20:00:00Z",
19
+ place_name: "Atelier Cafe",
20
+ };
21
+
22
+ describe("EventCompactRow sub line", () => {
23
+ it("leads with the time and the venue by default", () => {
24
+ const { container } = render(
25
+ <EventCompactRow event={event} labels={labels} formatters={formatters} />
26
+ );
27
+ expect(screen.getByText("20:00")).toBeInTheDocument();
28
+ expect(container.textContent).toContain("·");
29
+ expect(screen.getByText("Atelier Cafe")).toBeInTheDocument();
30
+ });
31
+
32
+ it("prints the venue alone when the time formatter returns an empty string (hour unknown)", () => {
33
+ const { container } = render(
34
+ <EventCompactRow
35
+ event={event}
36
+ labels={labels}
37
+ formatters={{ ...formatters, time: () => "" }}
38
+ />
39
+ );
40
+ expect(container.textContent).not.toContain("·");
41
+ expect(screen.getByText("Atelier Cafe")).toBeInTheDocument();
42
+ });
43
+
44
+ it("still honours hideTime for callers that know the hour is stated elsewhere", () => {
45
+ const { container } = render(
46
+ <EventCompactRow event={event} labels={labels} formatters={formatters} hideTime />
47
+ );
48
+ expect(container.textContent).not.toContain("20:00");
49
+ expect(container.textContent).not.toContain("·");
50
+ });
51
+ });
@@ -49,7 +49,8 @@ export type EventCompactRowProps = {
49
49
  event: EventPreview;
50
50
  labels: EventCardLabels;
51
51
  formatters: EventFormatters;
52
- /** Drops the leading time from the sub line. */
52
+ /** Drops the leading time from the sub line. An empty `formatters.time`
53
+ * result (hour unknown) drops it the same way, without the prop. */
53
54
  hideTime?: boolean;
54
55
  /**
55
56
  * Replaces the default "time · venue" sub-line entirely. For surfaces where
@@ -99,6 +100,7 @@ export const EventCompactRow = memo(function EventCompactRow({
99
100
  const fee = resolveEventEntryFee(event);
100
101
  const showPoster = !!event.poster_url && !imgError;
101
102
  const venue = event.place_name ?? event.location;
103
+ const time = formatters.time(event.starts_at);
102
104
 
103
105
  const body = (
104
106
  <>
@@ -157,9 +159,9 @@ export const EventCompactRow = memo(function EventCompactRow({
157
159
  subtitle
158
160
  ) : (
159
161
  <>
160
- {!hideTime && (
162
+ {!hideTime && time && (
161
163
  <>
162
- <span className="font-mono tabular-nums">{formatters.time(event.starts_at)}</span>
164
+ <span className="font-mono tabular-nums">{time}</span>
163
165
  <span aria-hidden> · </span>
164
166
  </>
165
167
  )}
@@ -76,7 +76,11 @@ export function resolveEventEntryFee(event: EventPreview): EventEntryFee {
76
76
  * locale and no currency of its own.
77
77
  */
78
78
  export type EventFormatters = {
79
- /** e.g. `"20:00"` */
79
+ /**
80
+ * e.g. `"20:00"`. Return `""` when the event's hour is not known: the card
81
+ * and the compact row then print the day (or the venue) ALONE, with no
82
+ * separator — never a placeholder word and never "00:00".
83
+ */
80
84
  time: (isoDateTime: string) => string;
81
85
  /** e.g. `"Sat, Sep 5"` — used when a card appears outside its own day section. */
82
86
  day: (isoDateTime: string) => string;