@medalsocial/meda 3.9.0 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { type ComponentType } from 'react';
1
+ import { type ComponentType, type ReactNode } from 'react';
2
2
  import type { BookingDaypart, BookingFormat } from './format.js';
3
3
  import { type BookingLabel } from './labels.js';
4
4
  import { type SlotClassNames } from './slots.js';
@@ -70,8 +70,23 @@ export declare function dayFreeMarks(free: number): 0 | 1 | 2 | 3;
70
70
  export declare function DefaultDayChip({ label, selected, className, onSelect, free, freeLabel, }: DayChipProps): import("react").JSX.Element;
71
71
  export interface TimeChipProps {
72
72
  startTs: number;
73
- /** «14:30», a party's «15:00 → 16:00», or the current hour's «15:00 · Current time». */
73
+ /**
74
+ * «14:30», a party's «15:00 → 16:00», a simultaneous party's
75
+ * «15:00 (Mia with Ada · Leo with Bo)», or the current hour's
76
+ * «15:00 · Current time». Always the chip's whole sentence.
77
+ */
74
78
  label: string;
79
+ /**
80
+ * `label` as elements, in the label pack's own order, with a simultaneous
81
+ * party's seating in a span that may wrap — so a long seating breaks onto
82
+ * the next line instead of widening the chip past the screen.
83
+ */
84
+ richLabel?: ReactNode;
85
+ /**
86
+ * What a week-grid column shows: too narrow for the seating, so only the
87
+ * hour, with `label` as the accessible name and the tooltip.
88
+ */
89
+ compactLabel?: string;
75
90
  /** One of the nearest alternatives to a slot that was just taken. */
76
91
  nearest: boolean;
77
92
  /** The screen-reader text that says so. */
@@ -83,7 +98,7 @@ export interface TimeChipProps {
83
98
  onPick: () => void;
84
99
  }
85
100
  /** One time chip. */
86
- export declare function DefaultTimeChip({ label, nearest, nearestLabel, current, className, onPick, }: TimeChipProps): import("react").JSX.Element;
101
+ export declare function DefaultTimeChip({ label, richLabel, compactLabel, nearest, nearestLabel, current, className, onPick, }: TimeChipProps): import("react").JSX.Element;
87
102
  export interface TimeScreenComponents {
88
103
  DayChip?: ComponentType<DayChipProps>;
89
104
  TimeChip?: ComponentType<TimeChipProps>;
@@ -3,7 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, X } from 'lucide-react';
4
4
  import { useEffect, useId, useRef, useState } from 'react';
5
5
  import { cn } from '../lib/utils.js';
6
- import { renderLabel } from './internal/label-parts.js';
6
+ import { labelParts, renderLabel } from './internal/label-parts.js';
7
7
  import { BookingButton, bookingButtonClass } from './internal/ui.js';
8
8
  import { visitName } from './internal/visit.js';
9
9
  import { fillLabel, labelText } from './labels.js';
@@ -123,11 +123,13 @@ export function DefaultDayChip({ label, selected, className, onSelect, free, fre
123
123
  return (_jsxs(BookingButton, { variant: "outline", size: "sm", "aria-pressed": selected, onClick: onSelect, className: cn('h-auto min-h-11 flex-col gap-1 py-1.5', free === 0 && !selected && 'text-muted-foreground', className), children: [_jsx("span", { children: label }), free === 0 && freeLabel ? (_jsx("span", { className: "text-[11px] font-medium leading-none", children: freeLabel })) : (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", "data-testid": "day-free-marks", className: "flex gap-0.5", children: [1, 2, 3].map((mark) => (_jsx("span", { "data-filled": mark <= marks ? '' : undefined, className: cn('block h-[3px] w-2 rounded-full bg-current', mark > marks && 'opacity-25') }, mark))) }), freeLabel && _jsx("span", { className: "sr-only", children: freeLabel })] }))] }));
124
124
  }
125
125
  /** One time chip. */
126
- export function DefaultTimeChip({ label, nearest, nearestLabel, current, className, onPick, }) {
126
+ export function DefaultTimeChip({ label, richLabel, compactLabel, nearest, nearestLabel, current, className, onPick, }) {
127
127
  if (current) {
128
128
  return (_jsx(BookingButton, { variant: "outline", size: "lg", disabled: true, "aria-current": "true", className: className, children: label }));
129
129
  }
130
- return (_jsxs(BookingButton, { variant: "outline", size: "lg", onClick: onPick, className: className, children: [label, nearest && (_jsxs("span", { className: "sr-only", children: [" ", nearestLabel] }))] }));
130
+ // A compact chip draws less than its sentence, so the sentence becomes its name.
131
+ const compact = compactLabel !== undefined;
132
+ return (_jsxs(BookingButton, { variant: "outline", size: "lg", onClick: onPick, "aria-label": compact ? `${label}${nearest ? ` ${nearestLabel}` : ''}` : undefined, title: compact ? label : undefined, className: cn(!compact && richLabel !== undefined && 'h-auto max-w-full py-2 whitespace-normal', className), children: [compact && compactLabel, !compact && richLabel !== undefined && (_jsx("span", { className: "min-w-0 text-center", children: richLabel })), !compact && richLabel === undefined && label, nearest && !compact && (_jsxs("span", { className: "sr-only", children: [" ", nearestLabel] }))] }));
131
133
  }
132
134
  // ---------------------------------------------------------------------------
133
135
  // View logic
@@ -174,16 +176,19 @@ function buildDayStrip(clock, slots, days, takenSlotTs, currentSlotTs, openDays,
174
176
  /**
175
177
  * The one chip a family taps: `15:00 → 16:00`, or the parallel form
176
178
  * `15:00 (Name with Stylist · Name with Stylist)`. The parallel chip names the
177
- * people where the visitor has given a name and the service otherwise.
179
+ * people where the visitor has given a name and the service otherwise; its
180
+ * seating is drawn so it can wrap, and a week column shows the hour alone.
178
181
  */
179
- function partyChipLabel(labels, clock, slot, party) {
182
+ function partyChipText(labels, clock, slot, party) {
180
183
  if (slot.mode === 'sequential') {
181
- return fillLabel(labels['time.party.sequential'], {
182
- start: clock.formatTime(slot.startTs),
183
- end: clock.formatTime(party.chipEndTs(slot)),
184
- });
184
+ return {
185
+ label: fillLabel(labels['time.party.sequential'], {
186
+ start: clock.formatTime(slot.startTs),
187
+ end: clock.formatTime(party.chipEndTs(slot)),
188
+ }),
189
+ };
185
190
  }
186
- const seats = slot.seats.map((seat, index) => {
191
+ const names = slot.seats.map((seat, index) => {
187
192
  const item = party.items[index];
188
193
  const who = item?.bookedForName ?? (item ? visitName(item) : '');
189
194
  const stylist = party.resolveStylistName(seat.resourceId);
@@ -191,10 +196,16 @@ function partyChipLabel(labels, clock, slot, party) {
191
196
  // is a reload of the resource list, not a reason to hide the slot.
192
197
  return stylist === null ? who : fillLabel(labels['time.party.seat'], { who, stylist });
193
198
  });
194
- return fillLabel(labels['time.party.parallel'], {
195
- time: clock.formatTime(slot.startTs),
196
- seats: seats.join(labelText(labels['time.party.seatSeparator'])),
197
- });
199
+ const time = clock.formatTime(slot.startTs);
200
+ const seats = names.join(labelText(labels['time.party.seatSeparator']));
201
+ return {
202
+ label: fillLabel(labels['time.party.parallel'], { time, seats }),
203
+ richLabel: labelParts(labels['time.party.parallel'], {
204
+ time,
205
+ seats: _jsx("span", { className: "font-normal text-muted-foreground", children: seats }),
206
+ }),
207
+ compactLabel: time,
208
+ };
198
209
  }
199
210
  /**
200
211
  * The instants on offer — ONE per instant. Availability is per (start,
@@ -426,7 +437,7 @@ function WeekGrid({ labels, words, format, days, offered, currentSlotTs, nearest
426
437
  const partySlot = partyByStart.get(slot.startTs);
427
438
  const current = slot.startTs === currentSlotTs;
428
439
  const isNearest = !current && nearest.has(slot.startTs);
429
- return (_jsx("li", { children: _jsx(TimeChip, { startTs: slot.startTs, label: chipLabel(labels, clock, slot, current, partySlot, party), nearest: isNearest, nearestLabel: labelText(labels['time.nearest']), current: current, onPick: () => (partySlot && party ? party.onPick(partySlot) : onPick(slot)), className: cn('h-auto min-h-11 w-full min-w-0 whitespace-normal px-1 tabular-nums', isNearest ? 'border-primary ring-2 ring-primary' : '', classNames?.chip, isNearest ? classNames?.chipNearest : undefined, current ? classNames?.chipCurrent : undefined) }) }, slot.startTs));
440
+ return (_jsx("li", { children: _jsx(TimeChip, { startTs: slot.startTs, ...chipText(labels, clock, slot, current, partySlot, party), nearest: isNearest, nearestLabel: labelText(labels['time.nearest']), current: current, onPick: () => (partySlot && party ? party.onPick(partySlot) : onPick(slot)), className: cn('h-auto min-h-11 w-full min-w-0 whitespace-normal px-1 tabular-nums', isNearest ? 'border-primary ring-2 ring-primary' : '', classNames?.chip, isNearest ? classNames?.chipNearest : undefined, current ? classNames?.chipCurrent : undefined) }) }, slot.startTs));
430
441
  }) })), more > 0 && (_jsx(BookingButton, { variant: "link", size: "sm", onClick: () => setUnfolded(key), className: "w-full", children: renderLabel(words.more, { count: more }) })), bookable === 0 &&
431
442
  (alternative && party ? (_jsx(BookingButton, { size: "sm", onClick: () => party.onPick(alternative), className: "h-auto min-h-11 w-full whitespace-normal tabular-nums", children: renderLabel(labels['time.parallel.accept'], {
432
443
  time: clock.formatTime(alternative.startTs),
@@ -435,13 +446,13 @@ function WeekGrid({ labels, words, format, days, offered, currentSlotTs, nearest
435
446
  return (_jsxs("div", { "data-testid": "time-week", className: cn('space-y-4', className), children: [_jsx("ol", { className: "grid grid-cols-7 gap-2", children: columns }), !anyBookable && empty, next, todayOpen && (_jsx(ShortNoticeLine, { labels: labels, format: format, phone: phone, className: classNames?.shortNotice }))] }));
436
447
  }
437
448
  /** A time chip's words: the hour, the held hour marked, or a family's whole visit. */
438
- function chipLabel(labels, clock, slot, current, partySlot, party) {
449
+ function chipText(labels, clock, slot, current, partySlot, party) {
439
450
  const time = clock.formatTime(slot.startTs);
440
451
  if (current)
441
- return fillLabel(labels['time.current'], { time });
452
+ return { label: fillLabel(labels['time.current'], { time }) };
442
453
  if (partySlot && party)
443
- return partyChipLabel(labels, clock, partySlot, party);
444
- return time;
454
+ return partyChipText(labels, clock, partySlot, party);
455
+ return { label: time };
445
456
  }
446
457
  /** The weekend note as one plain sentence, for a card that books without the notice on screen. */
447
458
  function weekendSentenceText(labels, format, note) {
@@ -594,7 +605,7 @@ function DaypartSections({ labels, clock, dayparts, daySlots, nearest, currentSl
594
605
  const partySlot = partyByStart.get(slot.startTs);
595
606
  const current = slot.startTs === currentSlotTs;
596
607
  const isNearest = !current && nearest.has(slot.startTs);
597
- return (_jsx(TimeChip, { startTs: slot.startTs, label: chipLabel(labels, clock, slot, current, partySlot, party), nearest: isNearest, nearestLabel: labelText(labels['time.nearest']), current: current, onPick: () => (partySlot && party ? party.onPick(partySlot) : onPick(slot)), className: cn('min-w-20 tabular-nums', isNearest ? 'border-primary ring-2 ring-primary' : '', classNames?.chip, isNearest ? classNames?.chipNearest : undefined, current ? classNames?.chipCurrent : undefined) }, slot.startTs));
608
+ return (_jsx(TimeChip, { startTs: slot.startTs, ...chipText(labels, clock, slot, current, partySlot, party), compactLabel: undefined, nearest: isNearest, nearestLabel: labelText(labels['time.nearest']), current: current, onPick: () => (partySlot && party ? party.onPick(partySlot) : onPick(slot)), className: cn('min-w-20 tabular-nums', isNearest ? 'border-primary ring-2 ring-primary' : '', classNames?.chip, isNearest ? classNames?.chipNearest : undefined, current ? classNames?.chipCurrent : undefined) }, slot.startTs));
598
609
  }) })] }, daypart.key));
599
610
  });
600
611
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medalsocial/meda",
3
- "version": "3.9.0",
3
+ "version": "3.9.1",
4
4
  "description": "Shared Meda UI shell and runtime package.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {