@bearmenu/ui 0.8.17 → 0.8.18

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.
Files changed (71) hide show
  1. package/dist/{chunk-WYTJR2YU.js → chunk-5CPBSNWQ.js} +6 -1
  2. package/dist/{chunk-54FQQZZJ.js → chunk-EQYB4Q77.js} +1 -1
  3. package/dist/chunk-LYREUZH6.js +156 -0
  4. package/dist/chunk-PW247TG2.js +81 -0
  5. package/dist/components/alert.d.ts +2 -2
  6. package/dist/components/badge.d.ts +1 -1
  7. package/dist/components/button.d.ts +1 -1
  8. package/dist/components/card.d.ts +2 -2
  9. package/dist/components/command.d.ts +15 -15
  10. package/dist/components/concept-compare-rail.js +2 -2
  11. package/dist/components/concept-compare.js +2 -2
  12. package/dist/components/event-card.d.ts +22 -4
  13. package/dist/components/event-card.js +95 -50
  14. package/dist/components/event-day-header.d.ts +5 -3
  15. package/dist/components/event-day-header.js +12 -4
  16. package/dist/components/event-door.d.ts +41 -0
  17. package/dist/components/event-door.js +65 -0
  18. package/dist/components/event-hub-hero.d.ts +10 -2
  19. package/dist/components/event-hub-hero.js +11 -5
  20. package/dist/components/event-tonight-strip.d.ts +50 -0
  21. package/dist/components/event-tonight-strip.js +67 -0
  22. package/dist/components/item.d.ts +2 -2
  23. package/dist/components/menu-hub-hero.d.ts +7 -1
  24. package/dist/components/menu-hub-hero.js +2 -1
  25. package/dist/components/place-card.js +1 -1
  26. package/dist/components/poster-stack.d.ts +36 -3
  27. package/dist/components/poster-stack.js +49 -18
  28. package/dist/components/snap-rail.d.ts +2 -2
  29. package/dist/components/snap-rail.js +1 -1
  30. package/dist/components/spinner.d.ts +2 -2
  31. package/dist/components/tag.d.ts +2 -2
  32. package/dist/components/text.d.ts +3 -3
  33. package/dist/components/timeline-day.d.ts +54 -0
  34. package/dist/components/timeline-day.js +65 -0
  35. package/dist/components/timeline-hour-puck.d.ts +47 -0
  36. package/dist/components/timeline-hour-puck.js +3 -0
  37. package/dist/components/timeline-hour.d.ts +96 -0
  38. package/dist/components/{timeline-band.js → timeline-hour.js} +2 -1
  39. package/dist/components/timeline-playhead.js +1 -3
  40. package/package.json +1 -1
  41. package/src/components/event-card.tsx +70 -13
  42. package/src/components/event-day-header.tsx +19 -7
  43. package/src/components/event-door.stories.tsx +88 -0
  44. package/src/components/event-door.tsx +97 -0
  45. package/src/components/event-hub-hero.tsx +21 -3
  46. package/src/components/event-tonight-strip.stories.tsx +78 -0
  47. package/src/components/event-tonight-strip.test.tsx +59 -0
  48. package/src/components/event-tonight-strip.tsx +134 -0
  49. package/src/components/menu-hub-hero.test.tsx +13 -0
  50. package/src/components/menu-hub-hero.tsx +9 -2
  51. package/src/components/poster-stack.stories.tsx +43 -1
  52. package/src/components/poster-stack.tsx +98 -19
  53. package/src/components/snap-rail.tsx +6 -1
  54. package/src/components/timeline-day.stories.tsx +201 -0
  55. package/src/components/timeline-day.test.tsx +78 -0
  56. package/src/components/timeline-day.tsx +114 -0
  57. package/src/components/timeline-hour-puck.tsx +139 -0
  58. package/src/components/timeline-hour.tsx +266 -0
  59. package/src/components/timeline-playhead.tsx +1 -1
  60. package/dist/chunk-NSNEANOQ.js +0 -104
  61. package/dist/components/hour-band.d.ts +0 -66
  62. package/dist/components/hour-band.js +0 -81
  63. package/dist/components/hour-group.d.ts +0 -67
  64. package/dist/components/hour-group.js +0 -59
  65. package/dist/components/timeline-band.d.ts +0 -64
  66. package/src/components/hour-band.stories.tsx +0 -182
  67. package/src/components/hour-band.tsx +0 -146
  68. package/src/components/hour-group.stories.tsx +0 -100
  69. package/src/components/hour-group.tsx +0 -124
  70. package/src/components/timeline-band.stories.tsx +0 -206
  71. package/src/components/timeline-band.tsx +0 -247
@@ -1,9 +1,27 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
2
3
 
3
4
  /**
4
5
  * PosterStack — three overlapping, gently rotated posters that float, the
5
6
  * hub hero's aside slot in the source design.
6
7
  *
8
+ * TAPPABLE SINCE 0.8.18. The stack was three decorative images: the loudest
9
+ * thing on the hub's first screen, and the one thing on it a reader could not
10
+ * tap. A poster with `href` is an anchor now — the event's detail page for
11
+ * the crawler and a modified click, whatever the app's `linkComponent` does
12
+ * for a plain tap (the hub opens its preview drawer). `caption` prints the
13
+ * title at the poster's foot: it gives the tap a visible reason on touch,
14
+ * where there is no hover, and it is the anchor's accessible name, so the
15
+ * `<img>` goes `alt=""` whenever a caption is set. Without `href` a poster is
16
+ * decorative, as before.
17
+ *
18
+ * The float and the press live on DIFFERENT elements. The wrapper carries
19
+ * the keyframe `transform` (rotation + drift); the anchor inside it carries
20
+ * the press scale and the focus ring. One element cannot animate one
21
+ * `transform` and transition another. The pointed poster comes to the front
22
+ * (`hover:z-[4]`, `focus-within:z-[4]`) — z-index is not animated, it is the
23
+ * "pick this one" cue.
24
+ *
7
25
  * Two motion rules, both mandatory:
8
26
  * 1. Stops entirely under `prefers-reduced-motion: reduce` — enforced via
9
27
  * an injected `<style>` media query rather than relying on Tailwind's
@@ -15,16 +33,31 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
15
33
  * spends a paint budget while it isn't visible. Skipped entirely when
16
34
  * reduced motion is on, since nothing is animating to pause.
17
35
  */
36
+ type PosterStackLinkProps = {
37
+ href: string;
38
+ className?: string;
39
+ children: React.ReactNode;
40
+ onClick?: (mouseEvent: React.MouseEvent) => void;
41
+ };
18
42
  type PosterStackPoster = {
19
- /** Absent renders a neutral placeholder block instead of an image. */
43
+ /** Absent renders `fallback` instead of an image. */
20
44
  src?: string;
21
45
  alt: string;
46
+ /** The event's detail page. With it the poster is an anchor; without it, decorative. */
47
+ href?: string;
48
+ /** Caption at the poster's foot — the event title. Becomes the anchor's
49
+ * accessible name; the `<img>` then gets `alt=""`. */
50
+ caption?: string;
51
+ /** Rendered when `src` is absent or fails. Defaults to `<EventPosterFallback />`. */
52
+ fallback?: React.ReactNode;
22
53
  };
23
54
  type PosterStackProps = {
24
55
  /** Rendered in order; only the first three are shown. */
25
56
  posters: PosterStackPoster[];
57
+ /** Defaults to a plain `<a>`. Pass `next/link`, or an anchor that opens the app's preview. */
58
+ linkComponent?: React.ComponentType<PosterStackLinkProps>;
26
59
  className?: string;
27
60
  };
28
- declare function PosterStack({ posters, className }: PosterStackProps): react_jsx_runtime.JSX.Element;
61
+ declare function PosterStack({ posters, linkComponent: LinkComponent, className, }: PosterStackProps): react_jsx_runtime.JSX.Element;
29
62
 
30
- export { PosterStack, type PosterStackPoster, type PosterStackProps };
63
+ export { PosterStack, type PosterStackLinkProps, type PosterStackPoster, type PosterStackProps };
@@ -1,8 +1,13 @@
1
+ import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
1
2
  import { cn } from '../chunk-FEK5XGZW.js';
2
- import '../chunk-MMUBH76A.js';
3
+ import { __objRest, __spreadValues } from '../chunk-MMUBH76A.js';
3
4
  import { useRef, useState, useEffect } from 'react';
4
- import { jsxs, jsx } from 'react/jsx-runtime';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
6
 
7
+ function DefaultLink(_a) {
8
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
9
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
10
+ }
6
11
  var POSTER_STACK_OBSERVER_ROOT_MARGIN = "200px";
7
12
  var POSTER_STACK_ANIMATION_STYLE = `
8
13
  @keyframes ds-poster-stack-float-1 { to { transform: rotate(-5deg) translateY(-16px); } }
@@ -14,27 +19,31 @@ var POSTER_STACK_ANIMATION_STYLE = `
14
19
  `;
15
20
  var POSTER_SLOTS = [
16
21
  {
17
- positionClassName: "left-[6px] top-[34px] w-[120px] md:left-0 md:top-[52px] md:w-[196px]",
22
+ positionClassName: "left-0 top-[28px] w-[112px] md:left-0 md:top-[52px] md:w-[196px]",
18
23
  restTransform: "rotate(-5deg)",
19
24
  animationName: "ds-poster-stack-float-1",
20
25
  animationDurationMs: 9e3
21
26
  },
22
27
  {
23
- positionClassName: "left-[110px] top-0 w-[140px] md:left-[168px] md:w-[232px]",
28
+ positionClassName: "left-[104px] top-0 w-[128px] md:left-[168px] md:w-[232px]",
24
29
  restTransform: "rotate(3deg)",
25
30
  animationName: "ds-poster-stack-float-2",
26
31
  animationDurationMs: 11e3,
27
32
  zIndexClassName: "z-[3]"
28
33
  },
29
34
  {
30
- positionClassName: "left-[238px] top-[58px] w-[120px] md:left-[378px] md:top-24 md:w-[196px]",
35
+ positionClassName: "left-[224px] top-[44px] w-[112px] md:left-[378px] md:top-24 md:w-[196px]",
31
36
  restTransform: "rotate(6deg)",
32
37
  animationName: "ds-poster-stack-float-3",
33
38
  animationDurationMs: 1e4
34
39
  }
35
40
  ];
36
41
  var POSTER_SHADOW = "0 22px 50px oklch(0.3 0.02 60 / 0.24)";
37
- function PosterStack({ posters, className }) {
42
+ function PosterStack({
43
+ posters,
44
+ linkComponent: LinkComponent = DefaultLink,
45
+ className
46
+ }) {
38
47
  const containerRef = useRef(null);
39
48
  const posterRefs = useRef([]);
40
49
  const [failedIndices, setFailedIndices] = useState(/* @__PURE__ */ new Set());
@@ -56,11 +65,32 @@ function PosterStack({ posters, className }) {
56
65
  return () => observer.disconnect();
57
66
  }, []);
58
67
  const visiblePosters = posters.slice(0, POSTER_SLOTS.length);
59
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative h-[250px] md:h-[400px]", className), children: [
68
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative h-[212px] md:h-[400px]", className), children: [
60
69
  /* @__PURE__ */ jsx("style", { children: POSTER_STACK_ANIMATION_STYLE }),
61
70
  visiblePosters.map((poster, index) => {
71
+ var _a;
62
72
  const slot = POSTER_SLOTS[index];
63
73
  const showPoster = !!poster.src && !failedIndices.has(index);
74
+ const face = /* @__PURE__ */ jsxs(Fragment, { children: [
75
+ showPoster ? (
76
+ // eslint-disable-next-line @next/next/no-img-element
77
+ /* @__PURE__ */ jsx(
78
+ "img",
79
+ {
80
+ src: poster.src,
81
+ alt: poster.caption ? "" : poster.alt,
82
+ loading: "lazy",
83
+ decoding: "async",
84
+ className: cn(
85
+ "h-full w-full object-cover",
86
+ poster.href && "transition-transform duration-300 group-hover/poster:scale-[1.03] motion-reduce:transition-none"
87
+ ),
88
+ onError: () => setFailedIndices((current) => new Set(current).add(index))
89
+ }
90
+ )
91
+ ) : (_a = poster.fallback) != null ? _a : /* @__PURE__ */ jsx(EventPosterFallback, {}),
92
+ poster.caption && /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-2 pb-2 pt-6", children: /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-[11px] font-semibold leading-[1.25] text-white md:text-[12.5px]", children: poster.caption }) })
93
+ ] });
64
94
  return /* @__PURE__ */ jsx(
65
95
  "div",
66
96
  {
@@ -71,7 +101,8 @@ function PosterStack({ posters, className }) {
71
101
  className: cn(
72
102
  "absolute aspect-[3/4] overflow-hidden rounded-xl bg-muted",
73
103
  slot.positionClassName,
74
- slot.zIndexClassName
104
+ slot.zIndexClassName,
105
+ poster.href && "hover:z-[4] focus-within:z-[4]"
75
106
  ),
76
107
  style: {
77
108
  transform: slot.restTransform,
@@ -82,18 +113,18 @@ function PosterStack({ posters, className }) {
82
113
  animationIterationCount: "infinite",
83
114
  animationDirection: "alternate"
84
115
  },
85
- children: showPoster && // eslint-disable-next-line @next/next/no-img-element
86
- /* @__PURE__ */ jsx(
87
- "img",
116
+ children: poster.href ? /* @__PURE__ */ jsx(
117
+ LinkComponent,
88
118
  {
89
- src: poster.src,
90
- alt: poster.alt,
91
- loading: "lazy",
92
- decoding: "async",
93
- className: "h-full w-full object-cover",
94
- onError: () => setFailedIndices((current) => new Set(current).add(index))
119
+ href: poster.href,
120
+ className: cn(
121
+ "group/poster relative block h-full w-full overflow-hidden rounded-xl",
122
+ "transition-transform duration-150 active:scale-[0.98] motion-reduce:transition-none",
123
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
124
+ ),
125
+ children: face
95
126
  }
96
- )
127
+ ) : /* @__PURE__ */ jsx("div", { className: "relative h-full w-full", children: face })
97
128
  },
98
129
  poster.alt || index
99
130
  );
@@ -21,7 +21,7 @@ import { VariantProps } from 'class-variance-authority';
21
21
  * `card` is the 14px inset of a hub card; `page` is the 16px page gutter.
22
22
  */
23
23
  declare const snapRailVariants: (props?: ({
24
- itemWidth?: "sm" | "lg" | "full" | "md" | null | undefined;
24
+ itemWidth?: "xs" | "sm" | "lg" | "full" | "md" | null | undefined;
25
25
  gap?: "default" | "sm" | null | undefined;
26
26
  bleed?: "none" | "page" | "card" | null | undefined;
27
27
  } & class_variance_authority_types.ClassProp) | undefined) => string;
@@ -43,7 +43,7 @@ type SnapRailProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof
43
43
  onActiveChange?: (index: number) => void;
44
44
  };
45
45
  declare const SnapRail: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & VariantProps<(props?: ({
46
- itemWidth?: "sm" | "lg" | "full" | "md" | null | undefined;
46
+ itemWidth?: "xs" | "sm" | "lg" | "full" | "md" | null | undefined;
47
47
  gap?: "default" | "sm" | null | undefined;
48
48
  bleed?: "none" | "page" | "card" | null | undefined;
49
49
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
@@ -1,3 +1,3 @@
1
- export { SnapRail, SnapRailDots } from '../chunk-WYTJR2YU.js';
1
+ export { SnapRail, SnapRailDots } from '../chunk-5CPBSNWQ.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-MMUBH76A.js';
@@ -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?: "success" | "warning" | "default" | "destructive" | "outline" | "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?: "success" | "warning" | "default" | "destructive" | "outline" | "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?: "body" | "caption" | "h1" | "h2" | "h3" | "h4" | "label" | "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;
@@ -0,0 +1,54 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * TimelineDay — one day's programme on the timeline: a column of
6
+ * `TimelineHour`s that becomes, from `lg`, the three-column row
7
+ * lane │ spine │ content that `TimelineBand` used to draw.
8
+ *
9
+ * ONE DOM FOR BOTH VIEWPORTS. `TimelineBand` took the lane as a prop — a
10
+ * second parent — which forced the consumer to render two trees and pick one
11
+ * in JavaScript, and a server render cannot pick. Here the lane is grid
12
+ * column 1, and the day's hero is an ordinary child in its chronological
13
+ * slot that the grid PLACES in that column from `lg` (see `TimelineHour`
14
+ * `role="hero"`). Source order is the phone's visual order, which is the
15
+ * primary surface and the one a screen reader gets on both.
16
+ *
17
+ * `rows` is the number of children that are NOT the hero — the column-3
18
+ * items. It sets `--timeline-rows`, which both the lane and the spine span,
19
+ * and the explicit row track count. It has to be a number the caller
20
+ * supplies: the grid needs the lane to cover every row so auto-placement
21
+ * never drops a later hour into the lane column, and this component cannot
22
+ * tell a hero child from an hour child without inspecting props.
23
+ *
24
+ * `laneTop` is the lane's sticky `top` as a CSS value. `position: sticky` on
25
+ * a grid item is bounded by its grid area — column 1, rows 1…N, the day's
26
+ * full height — so the poster holds while column 3 scrolls and releases when
27
+ * the day runs out, with no scroll listener and no measured height.
28
+ *
29
+ * The spine is a vertical `Separator` in column 2 spanning every row, drawn
30
+ * here so consumers never forget it; the pucks that sit on it are
31
+ * `TimelineHour`'s.
32
+ */
33
+ type TimelineDayProps = {
34
+ /** The column-3 children count — every child except the hero. */
35
+ rows: number;
36
+ /** Sticky `top` for the lane, as a CSS value. Omitted, the lane scrolls. */
37
+ laneTop?: string;
38
+ /** `TimelineHour`s, in chronological order, the hero among them. */
39
+ children: React.ReactNode;
40
+ className?: string;
41
+ };
42
+ declare function TimelineDay({ rows, laneTop, children, className }: TimelineDayProps): react_jsx_runtime.JSX.Element;
43
+ type TimelineDaySkeletonProps = {
44
+ /** Hours to reserve space for. */
45
+ hourCount?: number;
46
+ className?: string;
47
+ };
48
+ /**
49
+ * Holds the day's geometry at both widths so nothing jumps when data lands:
50
+ * the phone's stacked hours, the desktop's lane / spine / grid.
51
+ */
52
+ declare function TimelineDaySkeleton({ hourCount, className }: TimelineDaySkeletonProps): react_jsx_runtime.JSX.Element;
53
+
54
+ export { TimelineDay, type TimelineDayProps, TimelineDaySkeleton, type TimelineDaySkeletonProps };
@@ -0,0 +1,65 @@
1
+ import { TimelineHourSkeleton } from '../chunk-LYREUZH6.js';
2
+ import '../chunk-PW247TG2.js';
3
+ import { Separator } from '../chunk-NWZGSLPU.js';
4
+ import { Skeleton } from '../chunk-XBBEX4SF.js';
5
+ import { cn } from '../chunk-FEK5XGZW.js';
6
+ import { __spreadValues } from '../chunk-MMUBH76A.js';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ function TimelineDay({ rows, laneTop, children, className }) {
10
+ const style = __spreadValues({
11
+ "--timeline-rows": String(Math.max(rows, 1))
12
+ }, laneTop !== void 0 ? { "--timeline-lane-top": laneTop } : {});
13
+ return /* @__PURE__ */ jsxs(
14
+ "div",
15
+ {
16
+ className: cn(
17
+ "flex flex-col",
18
+ "lg:grid lg:grid-cols-[var(--timeline-lane)_var(--timeline-spine)_minmax(0,1fr)]",
19
+ "lg:[grid-template-rows:repeat(var(--timeline-rows),auto)]",
20
+ className
21
+ ),
22
+ style,
23
+ children: [
24
+ /* @__PURE__ */ jsx(
25
+ "div",
26
+ {
27
+ "aria-hidden": true,
28
+ className: cn(
29
+ "hidden lg:col-start-2 lg:row-start-1 lg:flex lg:justify-center lg:self-stretch",
30
+ "lg:[grid-row-end:span_var(--timeline-rows)]"
31
+ ),
32
+ children: /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-full" })
33
+ }
34
+ ),
35
+ children
36
+ ]
37
+ }
38
+ );
39
+ }
40
+ function TimelineDaySkeleton({ hourCount = 2, className }) {
41
+ return /* @__PURE__ */ jsxs(
42
+ "div",
43
+ {
44
+ "aria-hidden": "true",
45
+ className: cn(
46
+ "flex flex-col",
47
+ "lg:grid lg:grid-cols-[var(--timeline-lane)_var(--timeline-spine)_minmax(0,1fr)]",
48
+ "lg:[grid-template-rows:repeat(var(--timeline-rows),auto)]",
49
+ className
50
+ ),
51
+ style: { "--timeline-rows": String(hourCount) },
52
+ children: [
53
+ /* @__PURE__ */ jsxs("div", { className: "hidden lg:col-start-1 lg:row-start-1 lg:flex lg:flex-col lg:gap-3 lg:pt-3.5 lg:[grid-row-end:span_var(--timeline-rows)]", children: [
54
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[3/4] w-full rounded-xl" }),
55
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[18px] w-[70%]" }),
56
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[13px] w-[45%]" })
57
+ ] }),
58
+ /* @__PURE__ */ jsx("div", { className: "hidden lg:col-start-2 lg:row-start-1 lg:flex lg:justify-center lg:self-stretch lg:[grid-row-end:span_var(--timeline-rows)]", children: /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-full" }) }),
59
+ Array.from({ length: hourCount }, (_, index) => /* @__PURE__ */ jsx(TimelineHourSkeleton, { cardCount: index === 0 ? 3 : 2 }, index))
60
+ ]
61
+ }
62
+ );
63
+ }
64
+
65
+ export { TimelineDay, TimelineDaySkeleton };
@@ -0,0 +1,47 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * The phone axis gutter, shared by `TimelineHour` and `TimelinePlayhead` so
5
+ * the hour chips and the current-time label sit in one column. 58px is the
6
+ * width of a five-glyph 24-hour label ("20:00") in the chip's mono type plus
7
+ * its padding; a 12-hour locale's "09:00 AM" overflows it, which is one of the
8
+ * reasons the events surfaces format 24-hour everywhere.
9
+ */
10
+ declare const TIMELINE_MOBILE_GUTTER_CLASS = "w-[58px]";
11
+ type TimelineHourPuckProps = {
12
+ /** The hour, already formatted and localised — or an em dash for a quiet day. */
13
+ time: string;
14
+ /** Marks this as the hour currently in progress. */
15
+ isLive?: boolean;
16
+ /** Shown under the puck when live, e.g. "NOW ON". Required to render it. */
17
+ liveLabel?: string;
18
+ /**
19
+ * `station`: the bordered puck that sits ON the hairline, for the desktop
20
+ * spine. `chip`: a filled, fixed-width lozenge with the hairline starting
21
+ * BELOW it, for the phone gutter — an app's time label, not a station on a
22
+ * rail. `responsive` (the default): the chip below `lg`, the station from
23
+ * `lg` — ONE node that changes its look with the viewport, so a
24
+ * server-rendered timeline carries a single DOM for both widths. In the
25
+ * chip look the live label is visually hidden: the tinted chip plus a small
26
+ * on-air glyph are the visible signal (the glyph so the state never rests on
27
+ * colour alone once the pulse is reduced away) and the label stays for
28
+ * screen readers only.
29
+ */
30
+ variant?: "station" | "chip" | "responsive";
31
+ className?: string;
32
+ };
33
+ /**
34
+ * The station on the timeline's axis — ONE object, positioned by its host
35
+ * (`TimelineHour`): pinned to the top of the phone gutter, centred on the
36
+ * hour's heading rule out on the desktop spine.
37
+ *
38
+ * The `bg-background` fill punches through the hairline so the rail reads as
39
+ * continuous behind the label rather than butting into it.
40
+ *
41
+ * The live label is absolutely positioned rather than a flex sibling: hosts
42
+ * centre this puck with `-translate-y-1/2`, and a two-item column would centre
43
+ * the STACK, lifting the puck off the line it exists to sit on.
44
+ */
45
+ declare function TimelineHourPuck({ time, isLive, liveLabel, variant, className, }: TimelineHourPuckProps): react_jsx_runtime.JSX.Element;
46
+
47
+ export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineHourPuck, type TimelineHourPuckProps };
@@ -0,0 +1,3 @@
1
+ export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineHourPuck } from '../chunk-PW247TG2.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-MMUBH76A.js';
@@ -0,0 +1,96 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * TimelineHour — one hour of a day's programme, ONE DOM for both viewports.
6
+ *
7
+ * Replaces `HourBand` (the phone band: a 58px gutter with a filled chip and
8
+ * a hairline dropping from it, the hour's cards beside) and `HourGroup` (the
9
+ * desktop heading: a station out on `TimelineDay`'s spine, a rule and a
10
+ * count). They were two components because the timeline picked one per
11
+ * viewport in JavaScript, and a server render cannot know the viewport — so
12
+ * the server rendered nothing. This is both, with CSS deciding:
13
+ *
14
+ * below `lg` the section carries a 70px left inset; the puck sits at the
15
+ * top-left of that gutter as a chip, a hairline drops beneath
16
+ * it, and the heading row is hidden — the chip IS the heading.
17
+ * from `lg` the inset goes, the heading row shows (rule + count), and the
18
+ * puck moves out to the spine, centred on that rule, as a
19
+ * station. Its vertical position is a constant (`--timeline-
20
+ * heading` below), which is why the heading row has a fixed
21
+ * height: "top-1/2 of the row" needs the puck INSIDE the row,
22
+ * and inside the row it cannot also be the phone's gutter chip.
23
+ *
24
+ * The invariant `TimelineBand` used to state still holds: AN HOUR APPEARS
25
+ * EXACTLY ONCE, AND ALWAYS ON THE SPINE. Roles are how a day keeps it while
26
+ * one of its events is lifted into the desktop lane:
27
+ *
28
+ * hour the default — a station, the hour's events.
29
+ * hero the day's feature. Below `lg` a normal hour holding one
30
+ * card, in its chronological slot. From `lg` the node moves
31
+ * to `TimelineDay`'s lane column (grid placement, not source
32
+ * order), pins, and loses its puck and heading — the lane
33
+ * card carries its own start time, see `EventCard` `hideWhen`
34
+ * `"below-lg"`.
35
+ * continuation what is left of the hero's hour. Below `lg` its chip is
36
+ * `invisible` (the gutter and hairline stay, so the reader sees
37
+ * ONE band for that hour, hero first); from `lg` it shows its
38
+ * station, because the hero is in the lane now and the hour
39
+ * needs its puck back. When the hero was its hour's only
40
+ * event there is no continuation, and the hour simply leaves
41
+ * the desktop column — exactly what `HourGroup`'s
42
+ * `excludeEventId` used to do.
43
+ * unscheduled events the ingest dated but never timed. No station at any
44
+ * width — the axis encodes clock position and these have none
45
+ * — and no gutter inset: an inline heading (the caller's
46
+ * label, a rule, the count) at both widths, after the day's
47
+ * real programme.
48
+ *
49
+ * `layout`: `cards` stacks posters with a gap (a card is its own frame, a
50
+ * hairline between two posters reads as a third object) and from `lg` lays
51
+ * them on a two/three-up grid; `rows` rules between children on both widths.
52
+ * Children are read with `Children.toArray`, which drops `null`/`false`, so a
53
+ * conditional first row never inherits a rule meant for a second; pass rows
54
+ * as siblings, not inside a fragment.
55
+ */
56
+ type TimelineHourRole = "hour" | "hero" | "continuation" | "unscheduled";
57
+ type TimelineHourProps = {
58
+ /** The hour, already formatted and localised — "20:00". For `unscheduled`,
59
+ * the caller's label for the group ("Time to be confirmed"). */
60
+ time: string;
61
+ role?: TimelineHourRole;
62
+ /** Marks this as the hour currently in progress. */
63
+ isLive?: boolean;
64
+ /** Under the station when live, e.g. "NOW ON"; screen-reader-only in the chip. */
65
+ liveLabel?: string;
66
+ /** Desktop heading count, e.g. "3 events" — mirrors `EventDayHeader`'s `meta`.
67
+ * Also the phone heading for `unscheduled`. */
68
+ countLabel?: string;
69
+ /** Trailing note after the children, e.g. "No start time was published". */
70
+ note?: string;
71
+ layout?: "cards" | "rows";
72
+ /** The hour's events — each direct child one card or row. */
73
+ children: React.ReactNode;
74
+ /**
75
+ * Events past the hour's card cap, after `children`: cards on the phone
76
+ * (the same stack, same gap), rows from `lg` (ruled, under the grid) —
77
+ * pass `EventCard density="row-lg"`. The desktop's density ladder inside
78
+ * one hour: a few posters, then rows.
79
+ */
80
+ overflow?: React.ReactNode;
81
+ className?: string;
82
+ };
83
+ declare function TimelineHour({ time, role, isLive, liveLabel, countLabel, note, layout, children, overflow, className, }: TimelineHourProps): react_jsx_runtime.JSX.Element;
84
+ type TimelineHourSkeletonProps = {
85
+ /** Cards to reserve space for. */
86
+ cardCount?: number;
87
+ className?: string;
88
+ };
89
+ /**
90
+ * Mirrors `TimelineHour` at both widths — the phone's gutter chip and
91
+ * hairline, the desktop's grid of poster placeholders — so nothing jumps when
92
+ * data lands.
93
+ */
94
+ declare function TimelineHourSkeleton({ cardCount, className }: TimelineHourSkeletonProps): react_jsx_runtime.JSX.Element;
95
+
96
+ export { TimelineHour, type TimelineHourProps, type TimelineHourRole, TimelineHourSkeleton, type TimelineHourSkeletonProps };
@@ -1,4 +1,5 @@
1
- export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineBand, TimelineBandSkeleton, TimelineHourPuck } from '../chunk-NSNEANOQ.js';
1
+ export { TimelineHour, TimelineHourSkeleton } from '../chunk-LYREUZH6.js';
2
+ import '../chunk-PW247TG2.js';
2
3
  import '../chunk-NWZGSLPU.js';
3
4
  import '../chunk-XBBEX4SF.js';
4
5
  import '../chunk-FEK5XGZW.js';
@@ -1,6 +1,4 @@
1
- import { TIMELINE_MOBILE_GUTTER_CLASS } from '../chunk-NSNEANOQ.js';
2
- import '../chunk-NWZGSLPU.js';
3
- import '../chunk-XBBEX4SF.js';
1
+ import { TIMELINE_MOBILE_GUTTER_CLASS } from '../chunk-PW247TG2.js';
4
2
  import { cn } from '../chunk-FEK5XGZW.js';
5
3
  import '../chunk-MMUBH76A.js';
6
4
  import { jsxs, jsx } from 'react/jsx-runtime';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.8.17",
3
+ "version": "0.8.18",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {