@bearmenu/ui 0.8.55 → 0.8.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CZ2HC7WY.js → chunk-EGN5DOLQ.js} +2 -2
- package/dist/{chunk-YE4ZQUTI.js → chunk-QOXXIYDG.js} +14 -2
- package/dist/{chunk-I5V446BX.js → chunk-TDTLEDX6.js} +1 -1
- package/dist/{chunk-CBBECMBD.js → chunk-WBTSOARU.js} +55 -2
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/banner.d.ts +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/card.d.ts +2 -2
- package/dist/components/command.d.ts +15 -15
- package/dist/components/concept-compare.d.ts +1 -1
- package/dist/components/day-rail-chip.d.ts +8 -1
- package/dist/components/day-rail-chip.js +5 -2
- package/dist/components/doorway-tiles.d.ts +1 -1
- package/dist/components/empty-state.d.ts +2 -2
- package/dist/components/event-detail-panel.d.ts +1 -1
- package/dist/components/event-detail-panel.js +2 -2
- package/dist/components/grade-atom.d.ts +1 -1
- package/dist/components/grade-atom.js +2 -2
- package/dist/components/kitchen-dossier.d.ts +1 -1
- package/dist/components/menu-dead-end.d.ts +1 -1
- package/dist/components/menu-family-block.d.ts +1 -1
- package/dist/components/menu-family-block.js +3 -3
- package/dist/components/menu-family-card.d.ts +1 -1
- package/dist/components/menu-filter-chips.d.ts +1 -1
- package/dist/components/menu-group-rows.d.ts +1 -1
- package/dist/components/menu-index-header.d.ts +1 -1
- package/dist/components/menu-index-list.d.ts +1 -1
- package/dist/components/menu-intent-hero.d.ts +1 -1
- package/dist/components/menu-item.d.ts +34 -1
- package/dist/components/menu-item.js +3 -1
- package/dist/components/menu-kitchen-list.d.ts +1 -1
- package/dist/components/menu-kitchen-list.js +3 -3
- package/dist/components/menu-mocks.d.ts +1 -1
- package/dist/components/menu-types.d.ts +1 -1
- package/dist/components/open-now-list.d.ts +1 -1
- package/dist/components/place-about-tab.d.ts +1 -1
- package/dist/components/place-card.d.ts +1 -1
- package/dist/components/place-card.js +2 -2
- package/dist/components/place-details-mobile.d.ts +1 -1
- package/dist/components/place-schedule-week.d.ts +1 -1
- package/dist/components/place-types.d.ts +1 -1
- package/dist/components/qa-block.d.ts +1 -1
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/text.d.ts +2 -2
- package/dist/components/top-comparison.d.ts +1 -1
- package/dist/components/top-cross-link.d.ts +1 -1
- package/dist/components/top-ranking-entry.d.ts +1 -1
- package/dist/components/top-ranking-entry.js +3 -1
- package/dist/components/top-ranking-hero.d.ts +1 -1
- package/dist/components/top-types.d.ts +1 -1
- package/dist/components/venue-grade-dimensions.d.ts +1 -1
- package/dist/components/venue-quotes.d.ts +1 -1
- package/dist/components/venue-wall.d.ts +1 -1
- package/dist/components/weekday-chip.d.ts +13 -2
- package/dist/components/weekday-chip.js +14 -2
- package/dist/lib/grade.d.ts +1 -1
- package/dist/lib/grade.js +1 -1
- package/dist/{place-types-Dzy6FH67.d.ts → place-types-CjB4JlH0.d.ts} +2 -2
- package/package.json +1 -1
- package/src/components/booking-primitives.test.tsx +7 -0
- package/src/components/day-rail-chip.test.tsx +58 -0
- package/src/components/day-rail-chip.tsx +17 -1
- package/src/components/grade-atom.test.tsx +15 -0
- package/src/components/grade-atom.tsx +15 -3
- package/src/components/menu-item.stories.tsx +108 -0
- package/src/components/menu-item.test.tsx +100 -0
- package/src/components/menu-item.tsx +89 -6
- package/src/components/weekday-chip.stories.tsx +4 -0
- package/src/components/weekday-chip.tsx +25 -2
- package/src/lib/grade.ts +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { MenuVenuePreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
3
3
|
import 'react';
|
|
4
|
-
import '../place-types-
|
|
4
|
+
import '../place-types-CjB4JlH0.js';
|
|
5
5
|
import '../lib/grade.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { P as PlacePreview, c as PlaceAboutTabLabels, d as PlaceLinkKind } from '../place-types-
|
|
2
|
+
import { P as PlacePreview, c as PlaceAboutTabLabels, d as PlaceLinkKind } from '../place-types-CjB4JlH0.js';
|
|
3
3
|
import '../lib/grade.js';
|
|
4
4
|
|
|
5
5
|
interface PlaceAboutTabProps {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React$1 from 'react';
|
|
3
3
|
import { ReactNode, MouseEvent } from 'react';
|
|
4
|
-
import { P as PlacePreview, e as PlaceCardLabels } from '../place-types-
|
|
4
|
+
import { P as PlacePreview, e as PlaceCardLabels } from '../place-types-CjB4JlH0.js';
|
|
5
5
|
import '../lib/grade.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { HOVER_IMAGE_MOTION, HOVER_IMAGE_WRAPPER, HOVER_MOTION, HOVER_LIFT_GRID_GROUP } from '../chunk-XCB3F6SF.js';
|
|
2
2
|
import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
|
|
3
3
|
import { OVERLAY_PILL } from '../chunk-EZG2NELX.js';
|
|
4
|
-
import { GradeAtom } from '../chunk-
|
|
5
|
-
import { showsGradeAtom } from '../chunk-
|
|
4
|
+
import { GradeAtom } from '../chunk-QOXXIYDG.js';
|
|
5
|
+
import { showsGradeAtom } from '../chunk-TDTLEDX6.js';
|
|
6
6
|
import { Separator } from '../chunk-NWZGSLPU.js';
|
|
7
7
|
import { springSnappy } from '../chunk-OIV7B44M.js';
|
|
8
8
|
import { Skeleton } from '../chunk-XBBEX4SF.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { P as PlacePreview, f as PlaceDetailsMobileLabels } from '../place-types-
|
|
2
|
+
import { P as PlacePreview, f as PlaceDetailsMobileLabels } from '../place-types-CjB4JlH0.js';
|
|
3
3
|
import '../lib/grade.js';
|
|
4
4
|
|
|
5
5
|
interface PlaceDetailsMobileProps {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { g as PlaceSchedule, h as PlaceScheduleWeekLabels } from '../place-types-
|
|
2
|
+
import { g as PlaceSchedule, h as PlaceScheduleWeekLabels } from '../place-types-CjB4JlH0.js';
|
|
3
3
|
import '../lib/grade.js';
|
|
4
4
|
|
|
5
5
|
interface PlaceScheduleWeekProps {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { C as CitedText, l as CitedValue, M as MenuSizeAndPrice, c as PlaceAboutTabLabels, e as PlaceCardLabels, f as PlaceDetailsMobileLabels, a as PlaceGrade, b as PlaceGradeDimensions, m as PlaceHourBlock, d as PlaceLinkKind, n as PlaceMustTry, P as PlacePreview, o as PlacePriceRange, p as PlaceReviewsDigest, g as PlaceSchedule, h as PlaceScheduleWeekLabels, W as WeekdayKey } from '../place-types-
|
|
1
|
+
export { C as CitedText, l as CitedValue, M as MenuSizeAndPrice, c as PlaceAboutTabLabels, e as PlaceCardLabels, f as PlaceDetailsMobileLabels, a as PlaceGrade, b as PlaceGradeDimensions, m as PlaceHourBlock, d as PlaceLinkKind, n as PlaceMustTry, P as PlacePreview, o as PlacePriceRange, p as PlaceReviewsDigest, g as PlaceSchedule, h as PlaceScheduleWeekLabels, W as WeekdayKey } from '../place-types-CjB4JlH0.js';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import '../lib/grade.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?: "
|
|
7
|
+
tone?: "primary" | "inherit" | "default" | "destructive" | 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?: "
|
|
11
|
+
tone?: "primary" | "inherit" | "default" | "destructive" | 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;
|
package/dist/components/tag.d.ts
CHANGED
|
@@ -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?: "
|
|
6
|
+
variant?: "primary" | "success" | "warning" | "default" | "destructive" | "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?: "
|
|
10
|
+
variant?: "primary" | "success" | "warning" | "default" | "destructive" | "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,8 +22,8 @@ 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?: "
|
|
26
|
-
color?: "
|
|
25
|
+
variant?: "small" | "body" | "display" | "h2" | "h3" | "label" | "caption" | "h1" | "h4" | "body-lg" | "body-sm" | "overline" | null | undefined;
|
|
26
|
+
color?: "primary" | "inherit" | "default" | "destructive" | "muted" | "primary-foreground" | null | undefined;
|
|
27
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;
|
|
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import { MenuLinkComponent } from './menu-types.js';
|
|
3
3
|
import { TopComparisonRow } from './top-types.js';
|
|
4
4
|
import 'react';
|
|
5
|
-
import '../place-types-
|
|
5
|
+
import '../place-types-CjB4JlH0.js';
|
|
6
6
|
import '../lib/grade.js';
|
|
7
7
|
|
|
8
8
|
type TopComparisonLabels = {
|
|
@@ -3,7 +3,7 @@ import { MenuItemLabels } from './menu-item.js';
|
|
|
3
3
|
import { MenuItemPreview, MenuLinkComponent } from './menu-types.js';
|
|
4
4
|
import { TopRankEntry } from './top-types.js';
|
|
5
5
|
import 'react';
|
|
6
|
-
import '../place-types-
|
|
6
|
+
import '../place-types-CjB4JlH0.js';
|
|
7
7
|
import '../lib/grade.js';
|
|
8
8
|
|
|
9
9
|
type TopRankingEntryLabels = {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { MenuItem } from '../chunk-
|
|
1
|
+
import { MenuItem } from '../chunk-WBTSOARU.js';
|
|
2
2
|
import '../chunk-6PQINSAA.js';
|
|
3
|
+
import '../chunk-QOXXIYDG.js';
|
|
4
|
+
import '../chunk-TDTLEDX6.js';
|
|
3
5
|
import { Separator } from '../chunk-NWZGSLPU.js';
|
|
4
6
|
import { Skeleton } from '../chunk-XBBEX4SF.js';
|
|
5
7
|
import '../chunk-RYPPN2GZ.js';
|
|
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import { MenuLinkComponent } from './menu-types.js';
|
|
3
3
|
import { TopExit } from './top-types.js';
|
|
4
4
|
import 'react';
|
|
5
|
-
import '../place-types-
|
|
5
|
+
import '../place-types-CjB4JlH0.js';
|
|
6
6
|
import '../lib/grade.js';
|
|
7
7
|
|
|
8
8
|
type TopRankingHeroProps = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
3
|
-
import { P as PlacePreview } from '../place-types-
|
|
3
|
+
import { P as PlacePreview } from '../place-types-CjB4JlH0.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import '../lib/grade.js';
|
|
6
6
|
|
|
@@ -9,17 +9,28 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
9
9
|
* over the date, no count — and the rail shares the width equally.
|
|
10
10
|
*
|
|
11
11
|
* Measured from the booking design (`.dchip`): flexes to an equal share, 1px
|
|
12
|
-
* hairline, 11px radius, 7px vertical inset, centred. The weekday at
|
|
12
|
+
* hairline, 11px radius, 7px vertical inset, centred. The weekday at 11px
|
|
13
13
|
* uppercase, 0.05em tracking, muted; the date at 15px bold, tabular, 2px
|
|
14
14
|
* below. Selected fills with `--gradient-primary`, white type, the weekday at
|
|
15
15
|
* 75%. A day that cannot be booked (closed, past the window) fades to 40% and
|
|
16
16
|
* is disabled.
|
|
17
|
+
*
|
|
18
|
+
* The weekday was 10px, under the HIG's 11pt floor for iPhone text; measured
|
|
19
|
+
* when the events map took this chip for its week picker (bearmenu-fe,
|
|
20
|
+
* 2026-11-10). 11px still holds a three-letter weekday in a 46px seventh of a
|
|
21
|
+
* phone's width.
|
|
22
|
+
*
|
|
23
|
+
* `detail` is an optional third line under the date: the events map prints
|
|
24
|
+
* that day's event count there. A chip without it keeps its booking height.
|
|
17
25
|
*/
|
|
18
26
|
type WeekdayChipProps = {
|
|
19
27
|
/** "Mon", "Tue" — already localised; rendered uppercase. */
|
|
20
28
|
weekday: string;
|
|
21
29
|
/** Day of month as displayed — "14". */
|
|
22
30
|
day: string;
|
|
31
|
+
/** A short line under the date — "12", that day's events. Give the button
|
|
32
|
+
* an `aria-label` that says it with its unit; a bare number is unclear. */
|
|
33
|
+
detail?: string;
|
|
23
34
|
selected?: boolean;
|
|
24
35
|
/** Closed that day or outside the booking window. */
|
|
25
36
|
unavailable?: boolean;
|
|
@@ -28,6 +39,6 @@ type WeekdayChipProps = {
|
|
|
28
39
|
"aria-label"?: string;
|
|
29
40
|
className?: string;
|
|
30
41
|
};
|
|
31
|
-
declare function WeekdayChip({ weekday, day, selected, unavailable, onSelect, "aria-label": ariaLabel, className, }: WeekdayChipProps): react_jsx_runtime.JSX.Element;
|
|
42
|
+
declare function WeekdayChip({ weekday, day, detail, selected, unavailable, onSelect, "aria-label": ariaLabel, className, }: WeekdayChipProps): react_jsx_runtime.JSX.Element;
|
|
32
43
|
|
|
33
44
|
export { WeekdayChip, type WeekdayChipProps };
|
|
@@ -5,6 +5,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
5
5
|
function WeekdayChip({
|
|
6
6
|
weekday,
|
|
7
7
|
day,
|
|
8
|
+
detail,
|
|
8
9
|
selected,
|
|
9
10
|
unavailable,
|
|
10
11
|
onSelect,
|
|
@@ -33,13 +34,24 @@ function WeekdayChip({
|
|
|
33
34
|
"span",
|
|
34
35
|
{
|
|
35
36
|
className: cn(
|
|
36
|
-
"block text-[
|
|
37
|
+
"block text-[11px] tracking-[0.05em] uppercase",
|
|
37
38
|
selected ? "text-white/75" : "text-muted-foreground"
|
|
38
39
|
),
|
|
39
40
|
children: weekday
|
|
40
41
|
}
|
|
41
42
|
),
|
|
42
|
-
/* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[15px] font-bold tabular-nums", children: day })
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[15px] font-bold tabular-nums", children: day }),
|
|
44
|
+
detail !== void 0 && /* @__PURE__ */ jsx(
|
|
45
|
+
"span",
|
|
46
|
+
{
|
|
47
|
+
"data-slot": "weekday-chip-detail",
|
|
48
|
+
className: cn(
|
|
49
|
+
"mt-0.5 block font-mono text-[11px] tabular-nums",
|
|
50
|
+
selected ? "text-white/85" : "text-muted-foreground"
|
|
51
|
+
),
|
|
52
|
+
children: detail
|
|
53
|
+
}
|
|
54
|
+
)
|
|
43
55
|
]
|
|
44
56
|
}
|
|
45
57
|
);
|
package/dist/lib/grade.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ declare function shouldShowGradeNumber(grade: {
|
|
|
44
44
|
declare function showsGradeAtom<T extends {
|
|
45
45
|
overall?: number | null;
|
|
46
46
|
show_number?: boolean;
|
|
47
|
-
}>(grade: T | null | undefined, size: "xs" | "sm" | "md" | "lg"): grade is T;
|
|
47
|
+
}>(grade: T | null | undefined, size: "2xs" | "xs" | "sm" | "md" | "lg"): grade is T;
|
|
48
48
|
/** Tone for a single 0–100 dimension score, driving the page dimension-bar
|
|
49
49
|
* colour: strong (≥75) = accent, adequate (50–74) = muted, low (<50) = amber
|
|
50
50
|
* (a soft "fair warning", never error-red). */
|
package/dist/lib/grade.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { formatGrade, resolveDimensionTone, resolveGradeBand, shouldShowGradeNumber, showsGradeAtom } from '../chunk-
|
|
1
|
+
export { formatGrade, resolveDimensionTone, resolveGradeBand, shouldShowGradeNumber, showsGradeAtom } from '../chunk-TDTLEDX6.js';
|
|
2
2
|
import '../chunk-MMUBH76A.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { GradeBand } from './lib/grade.js';
|
|
3
3
|
|
|
4
|
-
type GradeAtomSize = "xs" | "sm" | "md" | "lg";
|
|
4
|
+
type GradeAtomSize = "2xs" | "xs" | "sm" | "md" | "lg";
|
|
5
5
|
/** Labels passed to `<GradeAtom>`. All translation happens in the caller. */
|
|
6
6
|
type GradeAtomLabels = {
|
|
7
7
|
/** The `show_number=false` state, e.g. "Worth exploring". */
|
|
@@ -23,7 +23,7 @@ type GradeAtomLabels = {
|
|
|
23
23
|
type GradeAtomProps = {
|
|
24
24
|
grade: PlaceGrade;
|
|
25
25
|
labels: GradeAtomLabels;
|
|
26
|
-
/** xs = card chip · sm = chat/compact · md = drawer · lg = page hero. */
|
|
26
|
+
/** 2xs = inline in a line of text · xs = card chip · sm = chat/compact · md = drawer · lg = page hero. */
|
|
27
27
|
size?: GradeAtomSize;
|
|
28
28
|
/** Force-hide the descriptor even at sizes that would show it (tight rows). */
|
|
29
29
|
hideDescriptor?: boolean;
|
package/package.json
CHANGED
|
@@ -82,6 +82,13 @@ describe("WeekdayChip", () => {
|
|
|
82
82
|
render(<WeekdayChip weekday="Tue" day="11" unavailable />);
|
|
83
83
|
expect(screen.getByRole("button")).toBeDisabled();
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
it("prints a detail line under the date only when given one", () => {
|
|
87
|
+
const { rerender, container } = render(<WeekdayChip weekday="Fri" day="13" detail="12" />);
|
|
88
|
+
expect(container.querySelector("[data-slot='weekday-chip-detail']")).toHaveTextContent("12");
|
|
89
|
+
rerender(<WeekdayChip weekday="Fri" day="13" />);
|
|
90
|
+
expect(container.querySelector("[data-slot='weekday-chip-detail']")).toBeNull();
|
|
91
|
+
});
|
|
85
92
|
});
|
|
86
93
|
|
|
87
94
|
describe("HoldCountdown", () => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { DayRailChip } from "./day-rail-chip";
|
|
4
|
+
|
|
5
|
+
const labels = { noEvents: "no events" };
|
|
6
|
+
|
|
7
|
+
describe("DayRailChip", () => {
|
|
8
|
+
// Without a label a screen reader read "Vin 1312": the date and the count
|
|
9
|
+
// as one number (bearmenu-fe HIG review, 2026-11-10).
|
|
10
|
+
it("speaks its aria-label instead of its text run together", () => {
|
|
11
|
+
render(
|
|
12
|
+
<DayRailChip
|
|
13
|
+
label="Vin"
|
|
14
|
+
dayNumber="13"
|
|
15
|
+
count={12}
|
|
16
|
+
labels={labels}
|
|
17
|
+
onClick={() => {}}
|
|
18
|
+
aria-label="Vineri 13 noiembrie, 12 evenimente"
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
expect(
|
|
22
|
+
screen.getByRole("button", { name: "Vineri 13 noiembrie, 12 evenimente" })
|
|
23
|
+
).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("names a link chip and an inert one the same way", () => {
|
|
27
|
+
const { rerender } = render(
|
|
28
|
+
<DayRailChip
|
|
29
|
+
label="Sâm"
|
|
30
|
+
dayNumber="14"
|
|
31
|
+
count={3}
|
|
32
|
+
labels={labels}
|
|
33
|
+
href="/ro/cluj-napoca/events?day=2026-11-14"
|
|
34
|
+
aria-label="Sâmbătă 14 noiembrie, 3 evenimente"
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
expect(
|
|
38
|
+
screen.getByRole("link", { name: "Sâmbătă 14 noiembrie, 3 evenimente" })
|
|
39
|
+
).toBeInTheDocument();
|
|
40
|
+
rerender(
|
|
41
|
+
<DayRailChip
|
|
42
|
+
label="Lun"
|
|
43
|
+
dayNumber="16"
|
|
44
|
+
count={0}
|
|
45
|
+
labels={labels}
|
|
46
|
+
aria-label="Luni 16 noiembrie, niciun eveniment"
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByRole("group", { name: "Luni 16 noiembrie, niciun eveniment" })
|
|
51
|
+
).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps its text as the name when no label is given", () => {
|
|
55
|
+
render(<DayRailChip label="Azi" dayNumber="10" count={7} labels={labels} onClick={() => {}} />);
|
|
56
|
+
expect(screen.getByRole("button")).toHaveTextContent("Azi 107");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -53,6 +53,7 @@ export type DayRailChipLinkProps = {
|
|
|
53
53
|
className?: string;
|
|
54
54
|
children: React.ReactNode;
|
|
55
55
|
"aria-current"?: "page";
|
|
56
|
+
"aria-label"?: string;
|
|
56
57
|
onClick?: () => void;
|
|
57
58
|
};
|
|
58
59
|
|
|
@@ -75,6 +76,12 @@ export type DayRailChipProps = {
|
|
|
75
76
|
linkComponent?: React.ComponentType<DayRailChipLinkProps>;
|
|
76
77
|
onClick?: () => void;
|
|
77
78
|
className?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The day for assistive tech — "Vineri 13 noiembrie, 12 evenimente". Without
|
|
81
|
+
* it a screen reader reads the chip's text run together: "Vin 1312", the
|
|
82
|
+
* date and the count as one number (bearmenu-fe HIG review, 2026-11-10).
|
|
83
|
+
*/
|
|
84
|
+
"aria-label"?: string;
|
|
78
85
|
};
|
|
79
86
|
|
|
80
87
|
export function DayRailChip({
|
|
@@ -88,6 +95,7 @@ export function DayRailChip({
|
|
|
88
95
|
linkComponent: LinkComponent = DefaultLink,
|
|
89
96
|
onClick,
|
|
90
97
|
className,
|
|
98
|
+
"aria-label": ariaLabel,
|
|
91
99
|
}: DayRailChipProps) {
|
|
92
100
|
const hasEvents = count > 0;
|
|
93
101
|
|
|
@@ -160,6 +168,7 @@ export function DayRailChip({
|
|
|
160
168
|
href={href}
|
|
161
169
|
className={rootClasses}
|
|
162
170
|
aria-current={isActive ? "page" : undefined}
|
|
171
|
+
aria-label={ariaLabel}
|
|
163
172
|
onClick={onClick}
|
|
164
173
|
>
|
|
165
174
|
{body}
|
|
@@ -176,6 +185,7 @@ export function DayRailChip({
|
|
|
176
185
|
// toggle semantic was right; it now navigates, and this is the current
|
|
177
186
|
// day in a set of days.
|
|
178
187
|
aria-current={isActive ? "true" : undefined}
|
|
188
|
+
aria-label={ariaLabel}
|
|
179
189
|
onClick={onClick}
|
|
180
190
|
>
|
|
181
191
|
{body}
|
|
@@ -183,7 +193,13 @@ export function DayRailChip({
|
|
|
183
193
|
);
|
|
184
194
|
}
|
|
185
195
|
|
|
186
|
-
|
|
196
|
+
// An inert day has no role to carry a label; `role="group"` gives the name
|
|
197
|
+
// somewhere to live without inventing an interaction.
|
|
198
|
+
return (
|
|
199
|
+
<div className={rootClasses} role={ariaLabel ? "group" : undefined} aria-label={ariaLabel}>
|
|
200
|
+
{body}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
export type DayRailChipSkeletonProps = {
|
|
@@ -23,6 +23,21 @@ describe("GradeAtom", () => {
|
|
|
23
23
|
expect(screen.getByLabelText("Nota 8,7 din 10")).toHaveTextContent("8,7");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
// A pill that sits inside a line of text (a menu row's name or venue line)
|
|
27
|
+
// must not make the line taller: at `xs` (24px) it pushed rows apart
|
|
28
|
+
// (Adrian, 9 Nov). `2xs` is 18px with an 11px number, and like `xs` it has
|
|
29
|
+
// no room for the soft "worth exploring" sentence.
|
|
30
|
+
it("has an inline size that fits a line of text, number only", () => {
|
|
31
|
+
render(<GradeAtom grade={{ overall: 8.7 }} labels={labels} size="2xs" />);
|
|
32
|
+
const pill = screen.getByLabelText("Grade 8.7 out of 10");
|
|
33
|
+
expect(pill.className).toContain("h-[18px]");
|
|
34
|
+
expect(pill.innerHTML).toContain("text-2xs");
|
|
35
|
+
const { container } = render(
|
|
36
|
+
<GradeAtom grade={{ overall: 8.7, show_number: false }} labels={labels} size="2xs" />
|
|
37
|
+
);
|
|
38
|
+
expect(container.textContent).toBe("");
|
|
39
|
+
});
|
|
40
|
+
|
|
26
41
|
it("hands the formatter the rounded, clamped grade", () => {
|
|
27
42
|
const seen: number[] = [];
|
|
28
43
|
const spy: GradeAtomLabels = { ...labels, value: (n) => (seen.push(n), String(n)) };
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "../lib/grade";
|
|
30
30
|
import type { PlaceGrade } from "./place-types";
|
|
31
31
|
|
|
32
|
-
export type GradeAtomSize = "xs" | "sm" | "md" | "lg";
|
|
32
|
+
export type GradeAtomSize = "2xs" | "xs" | "sm" | "md" | "lg";
|
|
33
33
|
|
|
34
34
|
/** Labels passed to `<GradeAtom>`. All translation happens in the caller. */
|
|
35
35
|
export type GradeAtomLabels = {
|
|
@@ -53,7 +53,7 @@ export type GradeAtomLabels = {
|
|
|
53
53
|
export type GradeAtomProps = {
|
|
54
54
|
grade: PlaceGrade;
|
|
55
55
|
labels: GradeAtomLabels;
|
|
56
|
-
/** xs = card chip · sm = chat/compact · md = drawer · lg = page hero. */
|
|
56
|
+
/** 2xs = inline in a line of text · xs = card chip · sm = chat/compact · md = drawer · lg = page hero. */
|
|
57
57
|
size?: GradeAtomSize;
|
|
58
58
|
/** Force-hide the descriptor even at sizes that would show it (tight rows). */
|
|
59
59
|
hideDescriptor?: boolean;
|
|
@@ -136,6 +136,18 @@ const SIZE: Record<
|
|
|
136
136
|
showDesc: boolean;
|
|
137
137
|
}
|
|
138
138
|
> = {
|
|
139
|
+
// Inline in a line of text (a menu row's name or venue line): 18px fits a
|
|
140
|
+
// 15px name and a 12px venue line without making either taller, which the
|
|
141
|
+
// 24px xs did (Adrian, 2026-11-09). Number only, like xs.
|
|
142
|
+
"2xs": {
|
|
143
|
+
box: "h-[18px] px-1 rounded",
|
|
144
|
+
minW: "min-w-7",
|
|
145
|
+
num: "text-2xs",
|
|
146
|
+
desc: "text-2xs",
|
|
147
|
+
gap: "gap-0.5",
|
|
148
|
+
shield: "h-2.5 w-2.5",
|
|
149
|
+
showDesc: false,
|
|
150
|
+
},
|
|
139
151
|
xs: {
|
|
140
152
|
box: "h-6 px-1.5 rounded-md",
|
|
141
153
|
minW: "min-w-9",
|
|
@@ -208,7 +220,7 @@ export function GradeAtom({
|
|
|
208
220
|
// caller falls back to its Google star instead (gate on `showsGradeAtom`, and
|
|
209
221
|
// see its note for why callers can't test `overall != null` any more).
|
|
210
222
|
if (!shouldShowGradeNumber(grade)) {
|
|
211
|
-
if (size === "xs") return null;
|
|
223
|
+
if (size === "xs" || size === "2xs") return null;
|
|
212
224
|
return (
|
|
213
225
|
<span
|
|
214
226
|
className={cn(
|
|
@@ -80,3 +80,111 @@ export const CountKinds: Story = {
|
|
|
80
80
|
</div>
|
|
81
81
|
),
|
|
82
82
|
};
|
|
83
|
+
|
|
84
|
+
const brisket: MenuItemPreview = {
|
|
85
|
+
id: "i2",
|
|
86
|
+
slug: "brisket",
|
|
87
|
+
name: "Brisket",
|
|
88
|
+
kind: "dish",
|
|
89
|
+
category_slug: "meat",
|
|
90
|
+
mentions: 60,
|
|
91
|
+
sizes_and_prices: [{ size: 0, price: 375 }],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const gradeLabels = {
|
|
95
|
+
soft: "Worth exploring",
|
|
96
|
+
bands: {
|
|
97
|
+
exceptional: "Exceptional",
|
|
98
|
+
great: "Great",
|
|
99
|
+
good: "Good",
|
|
100
|
+
decent: "Decent",
|
|
101
|
+
weak: "Weak",
|
|
102
|
+
},
|
|
103
|
+
aria: (value: string) => `Grade ${value} out of 10`,
|
|
104
|
+
capped: "Capped for low confidence",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The venue line on a cross-venue list (the map's Dishes and Drinks): the place
|
|
109
|
+
* in full ink with its grade, then its neighbourhood and whether it is open,
|
|
110
|
+
* muted. With `emphasis="praise"` the price steps back and the praise leads.
|
|
111
|
+
*/
|
|
112
|
+
export const VenueLine: Story = {
|
|
113
|
+
render: (args) => (
|
|
114
|
+
<div className="divide-y divide-border">
|
|
115
|
+
<MenuItem
|
|
116
|
+
{...args}
|
|
117
|
+
item={brisket}
|
|
118
|
+
countKind="praise"
|
|
119
|
+
labels={praiseLabels}
|
|
120
|
+
emphasis="praise"
|
|
121
|
+
subtitle="“Carnea a fost foarte fragedă și avea un gust intens de vită.”"
|
|
122
|
+
venueName="Texas Grill"
|
|
123
|
+
venueGrade={{
|
|
124
|
+
grade: { overall: 8.7, show_number: true, capped: false, confidence: "high" },
|
|
125
|
+
labels: gradeLabels,
|
|
126
|
+
}}
|
|
127
|
+
venueDetail="Centru"
|
|
128
|
+
venueStatus={{ tone: "open", label: "Închide la 23:00" }}
|
|
129
|
+
/>
|
|
130
|
+
<MenuItem
|
|
131
|
+
{...args}
|
|
132
|
+
item={{ ...brisket, id: "i3", name: "Ramen Tori Paitan", mentions: 58 }}
|
|
133
|
+
countKind="praise"
|
|
134
|
+
labels={praiseLabels}
|
|
135
|
+
emphasis="praise"
|
|
136
|
+
venueName="YUSO"
|
|
137
|
+
venueGrade={{
|
|
138
|
+
grade: { overall: 9.2, show_number: true, capped: false, confidence: "high" },
|
|
139
|
+
labels: gradeLabels,
|
|
140
|
+
}}
|
|
141
|
+
venueDetail="Mărăști"
|
|
142
|
+
venueStatus={{ tone: "closed", label: "Deschide la 12:00" }}
|
|
143
|
+
/>
|
|
144
|
+
<MenuItem
|
|
145
|
+
{...args}
|
|
146
|
+
item={{ ...brisket, id: "i4", name: "Cannoli Siciliani", mentions: 12 }}
|
|
147
|
+
countKind="praise"
|
|
148
|
+
labels={praiseLabels}
|
|
149
|
+
emphasis="praise"
|
|
150
|
+
venueName="Cannoleria Cluj-Napoca Restaurant & Pasticceria"
|
|
151
|
+
venueGrade={{
|
|
152
|
+
grade: { overall: 7.4, show_number: true, capped: false, confidence: "medium" },
|
|
153
|
+
labels: gradeLabels,
|
|
154
|
+
}}
|
|
155
|
+
venueDetail="Zorilor"
|
|
156
|
+
venueStatus={{ tone: "open", label: "Închide la 22:00" }}
|
|
157
|
+
/>
|
|
158
|
+
{/* A row whose name is the place (the map's Drinks list): the grade goes
|
|
159
|
+
with the name, and the venue line says what the place is praised for. */}
|
|
160
|
+
<MenuItem
|
|
161
|
+
{...args}
|
|
162
|
+
item={{ id: "f1", slug: "the-soviet", name: "The Soviet", kind: "drink", mentions: 271 }}
|
|
163
|
+
countKind="praise"
|
|
164
|
+
labels={praiseLabels}
|
|
165
|
+
emphasis="praise"
|
|
166
|
+
subtitle="“A very nice place to drink very good cocktails with some unusual names.”"
|
|
167
|
+
nameGrade={{
|
|
168
|
+
grade: { overall: 9.2, show_number: true, capped: false, confidence: "high" },
|
|
169
|
+
labels: gradeLabels,
|
|
170
|
+
}}
|
|
171
|
+
venueName="Praised for cocktails"
|
|
172
|
+
venueDetail="Centru"
|
|
173
|
+
venueStatus={{ tone: "open", label: "Închide la 02:00" }}
|
|
174
|
+
/>
|
|
175
|
+
<MenuItem
|
|
176
|
+
{...args}
|
|
177
|
+
item={{ ...brisket, id: "i5", name: "Papanași Cremoși", mentions: 9 }}
|
|
178
|
+
countKind="praise"
|
|
179
|
+
labels={praiseLabels}
|
|
180
|
+
emphasis="praise"
|
|
181
|
+
venueName="Madrugada"
|
|
182
|
+
venueGrade={{
|
|
183
|
+
grade: { overall: 8.1, show_number: false, capped: false, confidence: "low" },
|
|
184
|
+
labels: gradeLabels,
|
|
185
|
+
}}
|
|
186
|
+
venueDetail="Centru"
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
),
|
|
190
|
+
};
|