@bearmenu/ui 0.8.18 → 0.8.19

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 (32) hide show
  1. package/dist/{chunk-PW247TG2.js → chunk-BRXI6EDO.js} +5 -2
  2. package/dist/{chunk-LYREUZH6.js → chunk-QRSIJC4X.js} +1 -1
  3. package/dist/components/alert.d.ts +2 -2
  4. package/dist/components/badge.d.ts +1 -1
  5. package/dist/components/button.d.ts +1 -1
  6. package/dist/components/card.d.ts +2 -2
  7. package/dist/components/command.d.ts +15 -15
  8. package/dist/components/event-hub-hero.d.ts +5 -3
  9. package/dist/components/event-hub-hero.js +11 -11
  10. package/dist/components/event-tonight-strip.js +2 -2
  11. package/dist/components/item.d.ts +2 -2
  12. package/dist/components/menu-family-block.js +4 -3
  13. package/dist/components/place-card.js +1 -1
  14. package/dist/components/poster-stack.js +70 -62
  15. package/dist/components/spinner.d.ts +2 -2
  16. package/dist/components/tag.d.ts +2 -2
  17. package/dist/components/text.d.ts +3 -3
  18. package/dist/components/timeline-day.js +2 -2
  19. package/dist/components/timeline-hour-puck.js +1 -1
  20. package/dist/components/timeline-hour.js +2 -2
  21. package/dist/components/timeline-playhead.js +1 -1
  22. package/package.json +1 -1
  23. package/src/components/event-hub-hero.tsx +18 -14
  24. package/src/components/event-tonight-strip.test.tsx +0 -10
  25. package/src/components/event-tonight-strip.tsx +2 -2
  26. package/src/components/menu-family-block.tsx +13 -6
  27. package/src/components/poster-stack.tsx +11 -5
  28. package/src/components/timeline-hour-puck.tsx +5 -2
  29. package/dist/components/event-door.d.ts +0 -41
  30. package/dist/components/event-door.js +0 -65
  31. package/src/components/event-door.stories.tsx +0 -88
  32. package/src/components/event-door.tsx +0 -97
@@ -53,8 +53,11 @@ function TimelineHourPuck({
53
53
  variant === "responsive" && cn(
54
54
  chipClasses,
55
55
  // The station look from `lg`: bordered, background-filled,
56
- // no fixed width. Every chip utility above has an `lg:` twin.
57
- "lg:h-auto lg:min-w-0 lg:gap-0 lg:border-border lg:bg-background lg:px-[7px] lg:py-[3px] lg:text-[12.5px] lg:text-foreground",
56
+ // content-width. Every chip utility above has an `lg:` twin.
57
+ // `min-w-max`, NOT `min-w-0`: the host centres the puck inside
58
+ // a zero-width flex box, and a flex item allowed to shrink to 0
59
+ // collapses to a 24px square behind its own text.
60
+ "lg:h-auto lg:min-w-max lg:shrink-0 lg:gap-0 lg:border-border lg:bg-background lg:px-[7px] lg:py-[3px] lg:text-[12.5px] lg:text-foreground",
58
61
  isLive && "lg:border-success/40 lg:bg-background lg:text-success"
59
62
  )
60
63
  ),
@@ -1,4 +1,4 @@
1
- import { TimelineHourPuck, TIMELINE_MOBILE_GUTTER_CLASS } from './chunk-PW247TG2.js';
1
+ import { TimelineHourPuck, TIMELINE_MOBILE_GUTTER_CLASS } from './chunk-BRXI6EDO.js';
2
2
  import { Separator } from './chunk-NWZGSLPU.js';
3
3
  import { Skeleton } from './chunk-XBBEX4SF.js';
4
4
  import { cn } from './chunk-FEK5XGZW.js';
@@ -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?: "success" | "warning" | "default" | "destructive" | null | undefined;
6
+ variant?: "default" | "destructive" | "warning" | "success" | 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?: "success" | "warning" | "default" | "destructive" | null | undefined;
9
+ variant?: "default" | "destructive" | "warning" | "success" | 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?: "success" | "warning" | "default" | "destructive" | "outline" | "secondary" | "glass" | "overlay" | "brown" | "onPrimary" | null | undefined;
7
+ variant?: "default" | "destructive" | "warning" | "success" | "outline" | "secondary" | "glass" | "overlay" | "brown" | "onPrimary" | 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?: "link" | "warning" | "default" | "destructive" | "outline" | "secondary" | "ghost" | "glass" | "overlay" | "ink" | "brown" | null | undefined;
6
+ variant?: "default" | "destructive" | "warning" | "link" | "outline" | "secondary" | "ghost" | "glass" | "overlay" | "ink" | "brown" | null | undefined;
7
7
  size?: "default" | "xs" | "sm" | "lg" | "pill" | "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?: "outline" | "glass" | "muted" | "surface" | "elevated" | "elevatedPanel" | null | undefined;
6
+ variant?: "muted" | "outline" | "glass" | "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?: "outline" | "glass" | "muted" | "surface" | "elevated" | "elevatedPanel" | null | undefined;
11
+ variant?: "muted" | "outline" | "glass" | "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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
7
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
8
8
  ref?: React.Ref<HTMLDivElement>;
9
9
  } & {
10
10
  asChild?: boolean;
11
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
11
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
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
- }, "key" | "asChild" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "onChange" | "type" | "value"> & {
31
+ }, "asChild" | "key" | 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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
37
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
38
38
  ref?: React.Ref<HTMLDivElement>;
39
39
  } & {
40
40
  asChild?: boolean;
41
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
41
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
46
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
47
47
  ref?: React.Ref<HTMLDivElement>;
48
48
  } & {
49
49
  asChild?: boolean;
50
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
50
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & 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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
53
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
54
54
  ref?: React.Ref<HTMLDivElement>;
55
55
  } & {
56
56
  asChild?: boolean;
57
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
57
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
63
+ } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
64
64
  ref?: React.Ref<HTMLDivElement>;
65
65
  } & {
66
66
  asChild?: boolean;
67
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "heading" | "value"> & {
67
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key">, "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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
72
+ declare const CommandSeparator: React.ForwardRefExoticComponent<Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
73
73
  ref?: React.Ref<HTMLDivElement>;
74
74
  } & {
75
75
  asChild?: boolean;
76
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
76
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key"> & {
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>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
81
+ } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.HTMLAttributes<HTMLDivElement> | "key"> & {
82
82
  ref?: React.Ref<HTMLDivElement>;
83
83
  } & {
84
84
  asChild?: boolean;
85
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "onSelect" | "disabled" | "value"> & {
85
+ }, keyof React.HTMLAttributes<HTMLDivElement> | "asChild" | "key">, "onSelect" | "disabled" | "value"> & {
86
86
  disabled?: boolean;
87
87
  onSelect?: (value: string) => void;
88
88
  value?: string;
@@ -43,9 +43,11 @@ type EventHubHeroProps = {
43
43
  /** The right-column slot — the poster stack in the source design. */
44
44
  aside?: React.ReactNode;
45
45
  /**
46
- * The text column's last row, under the figures the hub's tonight state
47
- * strip. The figures say scale; this says what is on right now, and it is
48
- * the one thing on the first screen a reader can tap into the timeline.
46
+ * The hub's tonight state strip. The figures say scale; this says what is
47
+ * on right now, and it is the one thing on the first screen a reader can
48
+ * tap into the timeline. On a phone it follows the aside — title, figures,
49
+ * posters, then the one line that says what is on — and from `lg` it is a
50
+ * content-width row under both columns.
49
51
  */
50
52
  footer?: React.ReactNode;
51
53
  className?: string;
@@ -17,10 +17,10 @@ function EventHubHero({
17
17
  "div",
18
18
  {
19
19
  className: cn(
20
- // 16px between the text column and the aside on a phone: with a
21
- // tappable poster stack the aside is content, not decoration, and
22
- // gets the within-section rhythm rather than a section gap.
23
- "grid grid-cols-1 gap-4 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-10",
20
+ // 12px between the phone's blocks the page's rhythm, the same as
21
+ // between the sections below: with a tappable poster stack the aside
22
+ // is content, not decoration, and gets no section gap of its own.
23
+ "grid grid-cols-1 gap-3 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-x-10 lg:gap-y-6",
24
24
  className
25
25
  ),
26
26
  children: [
@@ -86,10 +86,10 @@ function EventHubHero({
86
86
  );
87
87
  })
88
88
  }
89
- ),
90
- footer && /* @__PURE__ */ jsx("div", { className: "order-4 pt-1.5 lg:order-none lg:pt-2", children: footer })
89
+ )
91
90
  ] }),
92
- aside && /* @__PURE__ */ jsx("div", { className: "min-w-0", children: aside })
91
+ aside && /* @__PURE__ */ jsx("div", { className: "min-w-0", children: aside }),
92
+ footer && /* @__PURE__ */ jsx("div", { className: "lg:col-span-2 lg:w-fit lg:min-w-[480px]", children: footer })
93
93
  ]
94
94
  }
95
95
  );
@@ -100,7 +100,7 @@ function EventHubHeroSkeleton({ aside, footer, className }) {
100
100
  {
101
101
  "aria-hidden": "true",
102
102
  className: cn(
103
- "grid grid-cols-1 gap-4 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-10",
103
+ "grid grid-cols-1 gap-3 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-x-10 lg:gap-y-6",
104
104
  className
105
105
  ),
106
106
  children: [
@@ -121,10 +121,10 @@ function EventHubHeroSkeleton({ aside, footer, className }) {
121
121
  ]
122
122
  },
123
123
  index
124
- )) }),
125
- footer && /* @__PURE__ */ jsx("div", { className: "order-4 pt-1.5 lg:order-none lg:pt-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-[52px] w-full rounded-[14px]" }) })
124
+ )) })
126
125
  ] }),
127
- aside && /* @__PURE__ */ jsx("div", { className: "min-w-0", children: aside })
126
+ aside && /* @__PURE__ */ jsx("div", { className: "min-w-0", children: aside }),
127
+ footer && /* @__PURE__ */ jsx("div", { className: "lg:col-span-2 lg:w-fit lg:min-w-[480px]", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-[52px] w-full rounded-[14px]" }) })
128
128
  ]
129
129
  }
130
130
  );
@@ -2,7 +2,7 @@ import { Skeleton } from '../chunk-XBBEX4SF.js';
2
2
  import { cn } from '../chunk-FEK5XGZW.js';
3
3
  import { __objRest, __spreadValues } from '../chunk-MMUBH76A.js';
4
4
  import * as React from 'react';
5
- import { Clock, CalendarDays, ArrowRight } from 'lucide-react';
5
+ import { Clock, CalendarDays, ChevronRight } from 'lucide-react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
 
8
8
  function DefaultLink(_a) {
@@ -44,7 +44,7 @@ function EventTonightStrip({
44
44
  actionLabel
45
45
  ] })
46
46
  ] }),
47
- interactive && /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true })
47
+ interactive && /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true })
48
48
  ] });
49
49
  const rootClasses = cn(
50
50
  "flex min-h-[52px] w-full items-center gap-2.5 rounded-[14px] border bg-card px-3.5 py-2 text-left",
@@ -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" | "outline" | "muted" | null | undefined;
6
+ variant?: "default" | "muted" | "outline" | 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" | "outline" | "muted" | null | undefined;
11
+ variant?: "default" | "muted" | "outline" | 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> & {
@@ -17,7 +17,7 @@ function DefaultLink(_a) {
17
17
  }
18
18
  var GROUND = PHOTO_GROUND;
19
19
  var INK_PHONE = "bg-[linear-gradient(to_bottom,oklch(0.16_0.012_55_/_0.12)_0%,oklch(0.16_0.012_55_/_0.55)_38%,oklch(0.16_0.012_55_/_0.92)_62%,oklch(0.16_0.012_55_/_0.96)_100%)]";
20
- var INK_WIDE = "lg:bg-[linear-gradient(to_right,oklch(0.16_0.012_55_/_0.32)_0%,oklch(0.16_0.012_55_/_0.62)_34%,oklch(0.16_0.012_55_/_0.9)_52%,oklch(0.16_0.012_55_/_0.96)_100%)]";
20
+ var INK_WIDE = "lg:bg-[linear-gradient(to_right,oklch(0.16_0.012_55_/_0.5)_0%,oklch(0.16_0.012_55_/_0.66)_34%,oklch(0.16_0.012_55_/_0.9)_52%,oklch(0.16_0.012_55_/_0.96)_100%)]";
21
21
  var TEXT_SCRIM = (
22
22
  /* `isolate` + `before:-z-10`: a positioned pseudo-element paints ABOVE the
23
23
  column's in-flow text unless the column is its own stacking context and
@@ -70,8 +70,9 @@ function MenuFamilyBlock({
70
70
  {
71
71
  className: cn(
72
72
  "relative flex min-h-[220px] flex-col justify-end p-3.5",
73
- wide && "lg:min-h-[320px] lg:p-5",
74
- TEXT_SCRIM
73
+ TEXT_SCRIM,
74
+ // The two-column card has ONE gradient from `lg` — see `INK_WIDE`.
75
+ wide && "lg:min-h-[320px] lg:p-5 lg:before:hidden"
75
76
  ),
76
77
  children: [
77
78
  pill && /* @__PURE__ */ jsx(
@@ -1,6 +1,6 @@
1
- import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
2
1
  import { OVERLAY_PILL } from '../chunk-EZG2NELX.js';
3
2
  import { HOVER_IMAGE_MOTION, HOVER_IMAGE_WRAPPER, HOVER_MOTION, HOVER_LIFT_GRID_GROUP } from '../chunk-XCB3F6SF.js';
3
+ import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
4
4
  import { GradeAtom } from '../chunk-DJD3EMPK.js';
5
5
  import { showsGradeAtom } from '../chunk-I5V446BX.js';
6
6
  import { Separator } from '../chunk-NWZGSLPU.js';
@@ -17,6 +17,7 @@ var POSTER_STACK_ANIMATION_STYLE = `
17
17
  [data-ds-poster-stack-item] { animation: none !important; }
18
18
  }
19
19
  `;
20
+ var PHONE_STAGE_CLASS = "mx-auto w-[336px] max-w-full";
20
21
  var POSTER_SLOTS = [
21
22
  {
22
23
  positionClassName: "left-0 top-[28px] w-[112px] md:left-0 md:top-[52px] md:w-[196px]",
@@ -65,71 +66,78 @@ function PosterStack({
65
66
  return () => observer.disconnect();
66
67
  }, []);
67
68
  const visiblePosters = posters.slice(0, POSTER_SLOTS.length);
68
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative h-[212px] md:h-[400px]", className), children: [
69
- /* @__PURE__ */ jsx("style", { children: POSTER_STACK_ANIMATION_STYLE }),
70
- visiblePosters.map((poster, index) => {
71
- var _a;
72
- const slot = POSTER_SLOTS[index];
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",
69
+ return /* @__PURE__ */ jsxs(
70
+ "div",
71
+ {
72
+ ref: containerRef,
73
+ className: cn("relative h-[196px] md:h-[400px] md:w-auto md:mx-0", PHONE_STAGE_CLASS, className),
74
+ children: [
75
+ /* @__PURE__ */ jsx("style", { children: POSTER_STACK_ANIMATION_STYLE }),
76
+ visiblePosters.map((poster, index) => {
77
+ var _a;
78
+ const slot = POSTER_SLOTS[index];
79
+ const showPoster = !!poster.src && !failedIndices.has(index);
80
+ const face = /* @__PURE__ */ jsxs(Fragment, { children: [
81
+ showPoster ? (
82
+ // eslint-disable-next-line @next/next/no-img-element
83
+ /* @__PURE__ */ jsx(
84
+ "img",
85
+ {
86
+ src: poster.src,
87
+ alt: poster.caption ? "" : poster.alt,
88
+ loading: "lazy",
89
+ decoding: "async",
90
+ className: cn(
91
+ "h-full w-full object-cover",
92
+ poster.href && "transition-transform duration-300 group-hover/poster:scale-[1.03] motion-reduce:transition-none"
93
+ ),
94
+ onError: () => setFailedIndices((current) => new Set(current).add(index))
95
+ }
96
+ )
97
+ ) : (_a = poster.fallback) != null ? _a : /* @__PURE__ */ jsx(EventPosterFallback, {}),
98
+ 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 }) })
99
+ ] });
100
+ return /* @__PURE__ */ jsx(
101
+ "div",
79
102
  {
80
- src: poster.src,
81
- alt: poster.caption ? "" : poster.alt,
82
- loading: "lazy",
83
- decoding: "async",
103
+ ref: (node) => {
104
+ posterRefs.current[index] = node;
105
+ },
106
+ "data-ds-poster-stack-item": true,
84
107
  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"
108
+ "absolute aspect-[3/4] overflow-hidden rounded-xl bg-muted",
109
+ slot.positionClassName,
110
+ slot.zIndexClassName,
111
+ poster.href && "hover:z-[4] focus-within:z-[4]"
87
112
  ),
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
- ] });
94
- return /* @__PURE__ */ jsx(
95
- "div",
96
- {
97
- ref: (node) => {
98
- posterRefs.current[index] = node;
99
- },
100
- "data-ds-poster-stack-item": true,
101
- className: cn(
102
- "absolute aspect-[3/4] overflow-hidden rounded-xl bg-muted",
103
- slot.positionClassName,
104
- slot.zIndexClassName,
105
- poster.href && "hover:z-[4] focus-within:z-[4]"
106
- ),
107
- style: {
108
- transform: slot.restTransform,
109
- boxShadow: POSTER_SHADOW,
110
- animationName: slot.animationName,
111
- animationDuration: `${slot.animationDurationMs}ms`,
112
- animationTimingFunction: "ease-in-out",
113
- animationIterationCount: "infinite",
114
- animationDirection: "alternate"
115
- },
116
- children: poster.href ? /* @__PURE__ */ jsx(
117
- LinkComponent,
118
- {
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
126
- }
127
- ) : /* @__PURE__ */ jsx("div", { className: "relative h-full w-full", children: face })
128
- },
129
- poster.alt || index
130
- );
131
- })
132
- ] });
113
+ style: {
114
+ transform: slot.restTransform,
115
+ boxShadow: POSTER_SHADOW,
116
+ animationName: slot.animationName,
117
+ animationDuration: `${slot.animationDurationMs}ms`,
118
+ animationTimingFunction: "ease-in-out",
119
+ animationIterationCount: "infinite",
120
+ animationDirection: "alternate"
121
+ },
122
+ children: poster.href ? /* @__PURE__ */ jsx(
123
+ LinkComponent,
124
+ {
125
+ href: poster.href,
126
+ className: cn(
127
+ "group/poster relative block h-full w-full overflow-hidden rounded-xl",
128
+ "transition-transform duration-150 active:scale-[0.98] motion-reduce:transition-none",
129
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
130
+ ),
131
+ children: face
132
+ }
133
+ ) : /* @__PURE__ */ jsx("div", { className: "relative h-full w-full", children: face })
134
+ },
135
+ poster.alt || index
136
+ );
137
+ })
138
+ ]
139
+ }
140
+ );
133
141
  }
134
142
 
135
143
  export { PosterStack };
@@ -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?: "inherit" | "default" | "destructive" | "primary" | null | undefined;
7
+ tone?: "default" | "destructive" | "inherit" | "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?: "inherit" | "default" | "destructive" | "primary" | null | undefined;
11
+ tone?: "default" | "destructive" | "inherit" | "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?: "success" | "warning" | "default" | "destructive" | "outline" | "primary" | "quiet" | null | undefined;
6
+ variant?: "default" | "destructive" | "warning" | "success" | "primary" | "outline" | "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?: "success" | "warning" | "default" | "destructive" | "outline" | "primary" | "quiet" | null | undefined;
10
+ variant?: "default" | "destructive" | "warning" | "success" | "primary" | "outline" | "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?: "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;
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;
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;
@@ -1,5 +1,5 @@
1
- import { TimelineHourSkeleton } from '../chunk-LYREUZH6.js';
2
- import '../chunk-PW247TG2.js';
1
+ import { TimelineHourSkeleton } from '../chunk-QRSIJC4X.js';
2
+ import '../chunk-BRXI6EDO.js';
3
3
  import { Separator } from '../chunk-NWZGSLPU.js';
4
4
  import { Skeleton } from '../chunk-XBBEX4SF.js';
5
5
  import { cn } from '../chunk-FEK5XGZW.js';
@@ -1,3 +1,3 @@
1
- export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineHourPuck } from '../chunk-PW247TG2.js';
1
+ export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineHourPuck } from '../chunk-BRXI6EDO.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-MMUBH76A.js';
@@ -1,5 +1,5 @@
1
- export { TimelineHour, TimelineHourSkeleton } from '../chunk-LYREUZH6.js';
2
- import '../chunk-PW247TG2.js';
1
+ export { TimelineHour, TimelineHourSkeleton } from '../chunk-QRSIJC4X.js';
2
+ import '../chunk-BRXI6EDO.js';
3
3
  import '../chunk-NWZGSLPU.js';
4
4
  import '../chunk-XBBEX4SF.js';
5
5
  import '../chunk-FEK5XGZW.js';
@@ -1,4 +1,4 @@
1
- import { TIMELINE_MOBILE_GUTTER_CLASS } from '../chunk-PW247TG2.js';
1
+ import { TIMELINE_MOBILE_GUTTER_CLASS } from '../chunk-BRXI6EDO.js';
2
2
  import { cn } from '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-MMUBH76A.js';
4
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.18",
3
+ "version": "0.8.19",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -48,9 +48,11 @@ export type EventHubHeroProps = {
48
48
  /** The right-column slot — the poster stack in the source design. */
49
49
  aside?: React.ReactNode;
50
50
  /**
51
- * The text column's last row, under the figures the hub's tonight state
52
- * strip. The figures say scale; this says what is on right now, and it is
53
- * the one thing on the first screen a reader can tap into the timeline.
51
+ * The hub's tonight state strip. The figures say scale; this says what is
52
+ * on right now, and it is the one thing on the first screen a reader can
53
+ * tap into the timeline. On a phone it follows the aside — title, figures,
54
+ * posters, then the one line that says what is on — and from `lg` it is a
55
+ * content-width row under both columns.
54
56
  */
55
57
  footer?: React.ReactNode;
56
58
  className?: string;
@@ -69,10 +71,10 @@ export function EventHubHero({
69
71
  return (
70
72
  <div
71
73
  className={cn(
72
- // 16px between the text column and the aside on a phone: with a
73
- // tappable poster stack the aside is content, not decoration, and
74
- // gets the within-section rhythm rather than a section gap.
75
- "grid grid-cols-1 gap-4 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-10",
74
+ // 12px between the phone's blocks the page's rhythm, the same as
75
+ // between the sections below: with a tappable poster stack the aside
76
+ // is content, not decoration, and gets no section gap of its own.
77
+ "grid grid-cols-1 gap-3 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-x-10 lg:gap-y-6",
76
78
  className
77
79
  )}
78
80
  >
@@ -141,10 +143,11 @@ export function EventHubHero({
141
143
  );
142
144
  })}
143
145
  </div>
144
- {footer && <div className="order-4 pt-1.5 lg:order-none lg:pt-2">{footer}</div>}
145
146
  </div>
146
147
 
147
148
  {aside && <div className="min-w-0">{aside}</div>}
149
+
150
+ {footer && <div className="lg:col-span-2 lg:w-fit lg:min-w-[480px]">{footer}</div>}
148
151
  </div>
149
152
  );
150
153
  }
@@ -162,7 +165,7 @@ export function EventHubHeroSkeleton({ aside, footer, className }: EventHubHeroS
162
165
  <div
163
166
  aria-hidden="true"
164
167
  className={cn(
165
- "grid grid-cols-1 gap-4 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-10",
168
+ "grid grid-cols-1 gap-3 lg:grid-cols-[1fr_620px] lg:items-center lg:gap-x-10 lg:gap-y-6",
166
169
  className
167
170
  )}
168
171
  >
@@ -187,14 +190,15 @@ export function EventHubHeroSkeleton({ aside, footer, className }: EventHubHeroS
187
190
  </div>
188
191
  ))}
189
192
  </div>
190
- {footer && (
191
- <div className="order-4 pt-1.5 lg:order-none lg:pt-2">
192
- <Skeleton className="h-[52px] w-full rounded-[14px]" />
193
- </div>
194
- )}
195
193
  </div>
196
194
 
197
195
  {aside && <div className="min-w-0">{aside}</div>}
196
+
197
+ {footer && (
198
+ <div className="lg:col-span-2 lg:w-fit lg:min-w-[480px]">
199
+ <Skeleton className="h-[52px] w-full rounded-[14px]" />
200
+ </div>
201
+ )}
198
202
  </div>
199
203
  );
200
204
  }
@@ -1,7 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { render, screen } from "@testing-library/react";
3
3
  import { EventTonightStrip } from "./event-tonight-strip";
4
- import { EventDoor } from "./event-door";
5
4
  import { PosterStack } from "./poster-stack";
6
5
 
7
6
  describe("EventTonightStrip", () => {
@@ -29,15 +28,6 @@ describe("EventTonightStrip", () => {
29
28
  });
30
29
  });
31
30
 
32
- describe("EventDoor", () => {
33
- it("is an anchor named by the category, with the image decorative", () => {
34
- render(<EventDoor name="Concerts" href="/events/concerts" imageSrc="/stage.webp" />);
35
- const link = screen.getByRole("link", { name: "Concerts" });
36
- expect(link).toHaveAttribute("href", "/events/concerts");
37
- expect(screen.getByRole("presentation", { hidden: true })).toHaveAttribute("alt", "");
38
- });
39
- });
40
-
41
31
  describe("PosterStack", () => {
42
32
  it("makes a captioned poster an anchor named by its caption", () => {
43
33
  // The float's reduced-motion check reads matchMedia, which jsdom lacks.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { ArrowRight, CalendarDays, Clock } from "lucide-react";
4
+ import { CalendarDays, ChevronRight, Clock } from "lucide-react";
5
5
  import { cn } from "../lib/utils";
6
6
  import { Skeleton } from "./skeleton";
7
7
 
@@ -98,7 +98,7 @@ export function EventTonightStrip({
98
98
  {interactive && actionLabel && <span className="sr-only">. {actionLabel}</span>}
99
99
  </span>
100
100
  {interactive && (
101
- <ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden />
101
+ <ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden />
102
102
  )}
103
103
  </>
104
104
  );
@@ -123,12 +123,18 @@ const GROUND = PHOTO_GROUND;
123
123
  const INK_PHONE =
124
124
  "bg-[linear-gradient(to_bottom,oklch(0.16_0.012_55_/_0.12)_0%,oklch(0.16_0.012_55_/_0.55)_38%,oklch(0.16_0.012_55_/_0.92)_62%,oklch(0.16_0.012_55_/_0.96)_100%)]";
125
125
  /**
126
- * From `lg` the ramp runs to the right: clear over the photo third, dark
127
- * under the rows — the rows sit on the ink itself, no opaque paper, so the
128
- * photograph still reads through the ramp's shoulder.
126
+ * From `lg` the ramp runs to the right: over the photo third, dark under the
127
+ * rows — the rows sit on the ink itself, no opaque paper, so the photograph
128
+ * still reads through the ramp's shoulder.
129
+ *
130
+ * THE ONLY GRADIENT FROM `lg`. The phone's local text scrim (below) is off
131
+ * at this width: a vertical scrim rising from the photo column's foot met
132
+ * this horizontal ramp at the column's bottom-right corner and the two read
133
+ * as a seam. Its contrast job moved here — the ramp opens at 0.5 rather
134
+ * than 0.32, which is what the name and count needed on a bright photograph.
129
135
  */
130
136
  const INK_WIDE =
131
- "lg:bg-[linear-gradient(to_right,oklch(0.16_0.012_55_/_0.32)_0%,oklch(0.16_0.012_55_/_0.62)_34%,oklch(0.16_0.012_55_/_0.9)_52%,oklch(0.16_0.012_55_/_0.96)_100%)]";
137
+ "lg:bg-[linear-gradient(to_right,oklch(0.16_0.012_55_/_0.5)_0%,oklch(0.16_0.012_55_/_0.66)_34%,oklch(0.16_0.012_55_/_0.9)_52%,oklch(0.16_0.012_55_/_0.96)_100%)]";
132
138
 
133
139
  /**
134
140
  * A second, LOCAL scrim under the name, count and action — the photo column's
@@ -200,8 +206,9 @@ export function MenuFamilyBlock({
200
206
  <div
201
207
  className={cn(
202
208
  "relative flex min-h-[220px] flex-col justify-end p-3.5",
203
- wide && "lg:min-h-[320px] lg:p-5",
204
- TEXT_SCRIM
209
+ TEXT_SCRIM,
210
+ // The two-column card has ONE gradient from `lg` — see `INK_WIDE`.
211
+ wide && "lg:min-h-[320px] lg:p-5 lg:before:hidden"
205
212
  )}
206
213
  >
207
214
  {pill && (
@@ -90,11 +90,14 @@ type PosterSlotConfig = {
90
90
  };
91
91
 
92
92
  /**
93
- * Phone geometry fits a 343px column (375 minus the 16px gutters): the third
94
- * poster used to reach 358px and ran into the right gutter. 112 / 128 / 112
95
- * wide, 212px tall 38px shorter than before, which is what lets the door
96
- * row land in the first screen. Touch targets are 112×149 and 128×171.
93
+ * Phone geometry: the three slots span 336px (112 / 128 / 112 wide, the
94
+ * outer two overlapping the middle by 8px each side), inside a stage the
95
+ * component centres in whatever column it is given the stack used to hug
96
+ * the left gutter. 196px tall, the height the lowest slot actually reaches,
97
+ * so the block below sits at the page's rhythm rather than under 20px of
98
+ * empty stage. Touch targets are 112×149 and 128×171.
97
99
  */
100
+ const PHONE_STAGE_CLASS = "mx-auto w-[336px] max-w-full";
98
101
  const POSTER_SLOTS: PosterSlotConfig[] = [
99
102
  {
100
103
  positionClassName: "left-0 top-[28px] w-[112px] md:left-0 md:top-[52px] md:w-[196px]",
@@ -154,7 +157,10 @@ export function PosterStack({
154
157
  const visiblePosters = posters.slice(0, POSTER_SLOTS.length);
155
158
 
156
159
  return (
157
- <div ref={containerRef} className={cn("relative h-[212px] md:h-[400px]", className)}>
160
+ <div
161
+ ref={containerRef}
162
+ className={cn("relative h-[196px] md:h-[400px] md:w-auto md:mx-0", PHONE_STAGE_CLASS, className)}
163
+ >
158
164
  <style>{POSTER_STACK_ANIMATION_STYLE}</style>
159
165
 
160
166
  {visiblePosters.map((poster, index) => {
@@ -112,8 +112,11 @@ export function TimelineHourPuck({
112
112
  cn(
113
113
  chipClasses,
114
114
  // The station look from `lg`: bordered, background-filled,
115
- // no fixed width. Every chip utility above has an `lg:` twin.
116
- "lg:h-auto lg:min-w-0 lg:gap-0 lg:border-border lg:bg-background lg:px-[7px] lg:py-[3px] lg:text-[12.5px] lg:text-foreground",
115
+ // content-width. Every chip utility above has an `lg:` twin.
116
+ // `min-w-max`, NOT `min-w-0`: the host centres the puck inside
117
+ // a zero-width flex box, and a flex item allowed to shrink to 0
118
+ // collapses to a 24px square behind its own text.
119
+ "lg:h-auto lg:min-w-max lg:shrink-0 lg:gap-0 lg:border-border lg:bg-background lg:px-[7px] lg:py-[3px] lg:text-[12.5px] lg:text-foreground",
117
120
  isLive && "lg:border-success/40 lg:bg-background lg:text-success"
118
121
  )
119
122
  )}
@@ -1,41 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as React from 'react';
3
-
4
- /**
5
- * EventDoor — a square tile that is a category's entry point: a photograph of
6
- * the KIND of night (a stage, a dance floor, a terrace), a scrim, the name.
7
- *
8
- * A door and an event row must not look alike. `CategoryDeck` fans four event
9
- * posters and prints a count, which reads as "these four events"; a card with
10
- * a single event's poster reads as "tap = that event" and changes daily. The
11
- * 1:1 shape with the name INSIDE the image is what says "door, not event", and
12
- * it carries no count — a count is a claim about scale the hub's figures
13
- * already make, and on a door it competes with the name.
14
- *
15
- * Always an anchor: a door with no destination is not a door, so `href` is
16
- * required. The scrim is a gradient over a photograph, which is mode-
17
- * independent — the same measured treatment `DoorwayTiles` uses.
18
- */
19
- type EventDoorLinkProps = {
20
- href: string;
21
- className?: string;
22
- children: React.ReactNode;
23
- onClick?: (mouseEvent: React.MouseEvent) => void;
24
- };
25
- type EventDoorProps = {
26
- /** The category's name — the anchor's accessible name and the tile's label. */
27
- name: string;
28
- href: string;
29
- imageSrc?: string;
30
- /** Rendered when `imageSrc` is absent or fails. Defaults to `<EventPosterFallback />`. */
31
- fallback?: React.ReactNode;
32
- linkComponent?: React.ComponentType<EventDoorLinkProps>;
33
- onClick?: (mouseEvent: React.MouseEvent) => void;
34
- className?: string;
35
- };
36
- declare function EventDoor({ name, href, imageSrc, fallback, linkComponent: LinkComponent, onClick, className, }: EventDoorProps): react_jsx_runtime.JSX.Element;
37
- declare function EventDoorSkeleton({ className }: {
38
- className?: string;
39
- }): react_jsx_runtime.JSX.Element;
40
-
41
- export { EventDoor, type EventDoorLinkProps, type EventDoorProps, EventDoorSkeleton };
@@ -1,65 +0,0 @@
1
- import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
2
- import { Skeleton } from '../chunk-XBBEX4SF.js';
3
- import { cn } from '../chunk-FEK5XGZW.js';
4
- import { __objRest, __spreadValues } from '../chunk-MMUBH76A.js';
5
- import { useState } from 'react';
6
- import { jsxs, jsx } from 'react/jsx-runtime';
7
-
8
- function DefaultLink(_a) {
9
- var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
10
- return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
11
- }
12
- function EventDoor({
13
- name,
14
- href,
15
- imageSrc,
16
- fallback = /* @__PURE__ */ jsx(EventPosterFallback, {}),
17
- linkComponent: LinkComponent = DefaultLink,
18
- onClick,
19
- className
20
- }) {
21
- const [failed, setFailed] = useState(false);
22
- const showImage = !!imageSrc && !failed;
23
- return /* @__PURE__ */ jsxs(
24
- LinkComponent,
25
- {
26
- href,
27
- onClick,
28
- className: cn(
29
- "group relative block aspect-square overflow-hidden rounded-xl bg-muted",
30
- "transition-transform duration-150 active:scale-[0.98] motion-reduce:transition-none",
31
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
32
- className
33
- ),
34
- children: [
35
- showImage ? (
36
- // eslint-disable-next-line @next/next/no-img-element
37
- /* @__PURE__ */ jsx(
38
- "img",
39
- {
40
- src: imageSrc,
41
- alt: "",
42
- loading: "lazy",
43
- decoding: "async",
44
- className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.04] motion-reduce:transition-none",
45
- onError: () => setFailed(true)
46
- }
47
- )
48
- ) : fallback,
49
- /* @__PURE__ */ jsx(
50
- "span",
51
- {
52
- "aria-hidden": true,
53
- className: "absolute inset-x-0 bottom-0 h-3/4 bg-gradient-to-t from-black/65 via-black/45 to-transparent"
54
- }
55
- ),
56
- /* @__PURE__ */ jsx("span", { className: "absolute inset-x-2 bottom-2 line-clamp-2 text-[12.5px] font-bold leading-[1.2] tracking-[-0.01em] text-white", children: name })
57
- ]
58
- }
59
- );
60
- }
61
- function EventDoorSkeleton({ className }) {
62
- return /* @__PURE__ */ jsx(Skeleton, { className: cn("aspect-square w-full rounded-xl", className) });
63
- }
64
-
65
- export { EventDoor, EventDoorSkeleton };
@@ -1,88 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import { EventDoor, EventDoorSkeleton } from "./event-door";
3
- import { SnapRail } from "./snap-rail";
4
-
5
- function photo(label: string, fill: string) {
6
- return (
7
- "data:image/svg+xml;utf8," +
8
- encodeURIComponent(
9
- `<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320"><rect width="320" height="320" fill="${fill}"/><text x="50%" y="50%" font-family="monospace" font-size="22" fill="#fff" text-anchor="middle">${label}</text></svg>`
10
- )
11
- );
12
- }
13
-
14
- const DOORS = [
15
- { name: "Concerts", fill: "#7a5c46" },
16
- { name: "Parties", fill: "#4a6a5c" },
17
- { name: "Live music", fill: "#6a5c8a" },
18
- { name: "Stand-up", fill: "#8a6a4a" },
19
- { name: "Theatre", fill: "#4a5a7a" },
20
- { name: "Workshops", fill: "#5a7a4a" },
21
- { name: "Markets", fill: "#7a4a5a" },
22
- { name: "Film", fill: "#4a4a4a" },
23
- ];
24
-
25
- const meta: Meta<typeof EventDoor> = {
26
- title: "Events/EventDoor",
27
- component: EventDoor,
28
- tags: ["autodocs"],
29
- args: {
30
- name: "Concerts",
31
- href: "#concerts",
32
- imageSrc: photo("stage", "#7a5c46"),
33
- },
34
- decorators: [
35
- (Story) => (
36
- <div className="w-[104px]">
37
- <Story />
38
- </div>
39
- ),
40
- ],
41
- parameters: {
42
- docs: {
43
- description: {
44
- component:
45
- "A category's entry point: a square photograph of the kind of night, a scrim, the name inside the image, no count. The 1:1 shape with the name inside is what says door rather than event. Always an anchor.",
46
- },
47
- },
48
- },
49
- };
50
-
51
- export default meta;
52
- type Story = StoryObj<typeof EventDoor>;
53
-
54
- export const Default: Story = {};
55
-
56
- /** No image — the poster fallback carries the tile. */
57
- export const NoImage: Story = {
58
- args: { imageSrc: undefined },
59
- };
60
-
61
- /** The hub's door row on a phone: a snap rail of squares, three and a third visible. */
62
- export const Row: Story = {
63
- decorators: [
64
- (Story) => (
65
- <div className="w-[375px] px-4" style={{ "--page-gutter": "1rem" } as React.CSSProperties}>
66
- <Story />
67
- </div>
68
- ),
69
- ],
70
- render: () => (
71
- <nav aria-label="Browse by kind">
72
- <SnapRail itemWidth="xs" gap="sm" bleed="page">
73
- {DOORS.map((door) => (
74
- <EventDoor
75
- key={door.name}
76
- name={door.name}
77
- href={`#${door.name}`}
78
- imageSrc={photo(door.name.toLowerCase(), door.fill)}
79
- />
80
- ))}
81
- </SnapRail>
82
- </nav>
83
- ),
84
- };
85
-
86
- export const Skeleton: Story = {
87
- render: () => <EventDoorSkeleton />,
88
- };
@@ -1,97 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { useState } from "react";
5
- import { cn } from "../lib/utils";
6
- import { EventPosterFallback } from "./event-poster-fallback";
7
- import { Skeleton } from "./skeleton";
8
-
9
- /**
10
- * EventDoor — a square tile that is a category's entry point: a photograph of
11
- * the KIND of night (a stage, a dance floor, a terrace), a scrim, the name.
12
- *
13
- * A door and an event row must not look alike. `CategoryDeck` fans four event
14
- * posters and prints a count, which reads as "these four events"; a card with
15
- * a single event's poster reads as "tap = that event" and changes daily. The
16
- * 1:1 shape with the name INSIDE the image is what says "door, not event", and
17
- * it carries no count — a count is a claim about scale the hub's figures
18
- * already make, and on a door it competes with the name.
19
- *
20
- * Always an anchor: a door with no destination is not a door, so `href` is
21
- * required. The scrim is a gradient over a photograph, which is mode-
22
- * independent — the same measured treatment `DoorwayTiles` uses.
23
- */
24
-
25
- export type EventDoorLinkProps = {
26
- href: string;
27
- className?: string;
28
- children: React.ReactNode;
29
- onClick?: (mouseEvent: React.MouseEvent) => void;
30
- };
31
-
32
- function DefaultLink({ href, ...props }: EventDoorLinkProps) {
33
- return <a href={href} {...props} />;
34
- }
35
-
36
- export type EventDoorProps = {
37
- /** The category's name — the anchor's accessible name and the tile's label. */
38
- name: string;
39
- href: string;
40
- imageSrc?: string;
41
- /** Rendered when `imageSrc` is absent or fails. Defaults to `<EventPosterFallback />`. */
42
- fallback?: React.ReactNode;
43
- linkComponent?: React.ComponentType<EventDoorLinkProps>;
44
- onClick?: (mouseEvent: React.MouseEvent) => void;
45
- className?: string;
46
- };
47
-
48
- export function EventDoor({
49
- name,
50
- href,
51
- imageSrc,
52
- fallback = <EventPosterFallback />,
53
- linkComponent: LinkComponent = DefaultLink,
54
- onClick,
55
- className,
56
- }: EventDoorProps) {
57
- const [failed, setFailed] = useState(false);
58
- const showImage = !!imageSrc && !failed;
59
-
60
- return (
61
- <LinkComponent
62
- href={href}
63
- onClick={onClick}
64
- className={cn(
65
- "group relative block aspect-square overflow-hidden rounded-xl bg-muted",
66
- "transition-transform duration-150 active:scale-[0.98] motion-reduce:transition-none",
67
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
68
- className
69
- )}
70
- >
71
- {showImage ? (
72
- // eslint-disable-next-line @next/next/no-img-element
73
- <img
74
- src={imageSrc}
75
- alt=""
76
- loading="lazy"
77
- decoding="async"
78
- className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.04] motion-reduce:transition-none"
79
- onError={() => setFailed(true)}
80
- />
81
- ) : (
82
- fallback
83
- )}
84
- <span
85
- aria-hidden
86
- className="absolute inset-x-0 bottom-0 h-3/4 bg-gradient-to-t from-black/65 via-black/45 to-transparent"
87
- />
88
- <span className="absolute inset-x-2 bottom-2 line-clamp-2 text-[12.5px] font-bold leading-[1.2] tracking-[-0.01em] text-white">
89
- {name}
90
- </span>
91
- </LinkComponent>
92
- );
93
- }
94
-
95
- export function EventDoorSkeleton({ className }: { className?: string }) {
96
- return <Skeleton className={cn("aspect-square w-full rounded-xl", className)} />;
97
- }