@medalsocial/meda 3.14.0 → 3.15.0
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/README.md
CHANGED
|
@@ -66,7 +66,8 @@ per-feature stylesheets never reach an optional peer (see
|
|
|
66
66
|
|
|
67
67
|
Unbranded, presentational screens for a customer booking flow: the wizard steps
|
|
68
68
|
(`WhoScreen`, `ServiceScreen`, `StylistScreen`, `TimeScreen`, `DetailsScreen`,
|
|
69
|
-
`SummaryBar`, `Confirmation`, `BookingSkeleton`, `AddChildSheet`),
|
|
69
|
+
`SummaryBar`, `Confirmation`, `BookingSkeleton`, `AddChildSheet`), the desktop's
|
|
70
|
+
sticky side panel (`BookingPanel`), `ManageScreen`,
|
|
70
71
|
login (`LoginSheet`, `LoginPanel`, `OtpSlots`, `VippsButton`) and the customer
|
|
71
72
|
portal (`PortalShell`, `UpcomingBookings`, `VisitHistory`, `ChildCards`,
|
|
72
73
|
`RebookCards`, `FamilyEditor`, `ProfileForm`, `DataControls`, `AccountCard`,
|
|
@@ -30,9 +30,10 @@ export declare function PersonTabs({ people, active, done, onSelect, labels, lab
|
|
|
30
30
|
* missing, and «Next». The same bar as `SummaryBar`, so the step reads as one
|
|
31
31
|
* flow; a caller that also renders `SummaryBar` hides it on this step.
|
|
32
32
|
*
|
|
33
|
-
* «Next» is
|
|
34
|
-
* by a screen reader's tab order; this one stays, the line says
|
|
35
|
-
* missing, and the caller decides what pressing it does.
|
|
33
|
+
* «Next» is not disabled by default. A disabled button explains nothing and
|
|
34
|
+
* is skipped by a screen reader's tab order; this one stays, the line says
|
|
35
|
+
* what is missing, and the caller decides what pressing it does. A site that
|
|
36
|
+
* wants it held back anyway opts in with `selection.disableUntilReady`.
|
|
36
37
|
*/
|
|
37
38
|
export declare function TotalBar({ selection, format, classNames, }: {
|
|
38
39
|
selection: ServiceSelection;
|
|
@@ -53,16 +53,20 @@ export function PersonTabs({ people, active, done, onSelect, labels, labelledBy,
|
|
|
53
53
|
* missing, and «Next». The same bar as `SummaryBar`, so the step reads as one
|
|
54
54
|
* flow; a caller that also renders `SummaryBar` hides it on this step.
|
|
55
55
|
*
|
|
56
|
-
* «Next» is
|
|
57
|
-
* by a screen reader's tab order; this one stays, the line says
|
|
58
|
-
* missing, and the caller decides what pressing it does.
|
|
56
|
+
* «Next» is not disabled by default. A disabled button explains nothing and
|
|
57
|
+
* is skipped by a screen reader's tab order; this one stays, the line says
|
|
58
|
+
* what is missing, and the caller decides what pressing it does. A site that
|
|
59
|
+
* wants it held back anyway opts in with `selection.disableUntilReady`.
|
|
59
60
|
*/
|
|
60
61
|
export function TotalBar({ selection, format, classNames, }) {
|
|
61
62
|
const { total, canContinue, labels } = selection;
|
|
63
|
+
const held = selection.disableUntilReady === true && !canContinue;
|
|
62
64
|
return (_jsxs("div", { className: slotClass(classNames, 'bar', 'sticky bottom-0 z-40 border-t border-border bg-background/95 backdrop-blur-md'), children: [_jsx(LiveStatus, { text: selection.notice ?? null, className: "px-5 text-sm font-medium text-foreground [&:not(:empty)]:pt-3" }), _jsxs("div", { className: "flex h-16 items-center justify-between gap-4 px-5", children: [_jsxs("div", { "aria-live": "polite", "aria-atomic": "true", className: "min-w-0 text-sm tabular-nums", children: [total !== null && (_jsx("p", { className: "truncate font-semibold", children: renderLabel(labels['service.total'], {
|
|
63
65
|
minutes: total.minutes,
|
|
64
66
|
price: format.price(total.priceOre),
|
|
65
|
-
}) })), !canContinue && (_jsx("p", { className: "truncate text-muted-foreground", children: renderLabel(labels['service.chooseFirst']) }))] }), _jsx(BookingButton, { size: "lg", onClick: selection.onContinue,
|
|
67
|
+
}) })), !canContinue && (_jsx("p", { className: "truncate text-muted-foreground", children: renderLabel(labels['service.chooseFirst']) }))] }), _jsx(BookingButton, { size: "lg", onClick: selection.onContinue, disabled: held, "aria-disabled": held || undefined, "data-disabled": held ? '' : undefined, className: cn(
|
|
68
|
+
// Material 3's disabled button: 38 % content on a 12 % container.
|
|
69
|
+
held && 'disabled:bg-foreground/12 disabled:text-foreground/38 disabled:opacity-100', classNames?.next), children: renderLabel(labels['service.continue']) })] })] }));
|
|
66
70
|
}
|
|
67
71
|
/**
|
|
68
72
|
* A service card on the multi-select step: a real checkbox inside its label.
|
|
@@ -11,18 +11,26 @@ import type { ComponentProps } from 'react';
|
|
|
11
11
|
*/
|
|
12
12
|
export type BookingButtonVariant = 'default' | 'outline' | 'secondary' | 'ghost' | 'destructive' | 'link';
|
|
13
13
|
export type BookingButtonSize = 'default' | 'sm' | 'lg' | 'icon' | 'icon-sm';
|
|
14
|
+
/**
|
|
15
|
+
* The corners: `default` is booking's near-square look (each size's own);
|
|
16
|
+
* `full` a pill, for a site whose buttons are round.
|
|
17
|
+
*/
|
|
18
|
+
export type BookingButtonRadius = 'default' | 'full';
|
|
14
19
|
/** Classes for a booking button — also for a link that should look like one. */
|
|
15
|
-
export declare function bookingButtonClass({ variant, size, className, }?: {
|
|
20
|
+
export declare function bookingButtonClass({ variant, size, radius, className, }?: {
|
|
16
21
|
variant?: BookingButtonVariant;
|
|
17
22
|
size?: BookingButtonSize;
|
|
23
|
+
radius?: BookingButtonRadius;
|
|
18
24
|
className?: string;
|
|
19
25
|
}): string;
|
|
20
26
|
export interface BookingButtonProps extends ComponentProps<'button'> {
|
|
21
27
|
variant?: BookingButtonVariant;
|
|
22
28
|
size?: BookingButtonSize;
|
|
29
|
+
/** `full` for pill-shaped corners. Default: booking's near-square ones. */
|
|
30
|
+
radius?: BookingButtonRadius;
|
|
23
31
|
}
|
|
24
32
|
/** The booking screens' button. A native `<button type="button">` unless told otherwise. */
|
|
25
|
-
export declare function BookingButton({ variant, size, className, type, ...props }: BookingButtonProps): import("react").JSX.Element;
|
|
33
|
+
export declare function BookingButton({ variant, size, radius, className, type, ...props }: BookingButtonProps): import("react").JSX.Element;
|
|
26
34
|
/** Override for meda `Input` in booking forms. */
|
|
27
35
|
export declare const BOOKING_INPUT_CLASS = "h-12 rounded-[4px] border-[1.5px] border-input bg-background px-4 py-3 text-base transition-[color,border-color] focus-visible:border-primary focus-visible:ring-0 aria-invalid:border-destructive sm:text-base md:text-base dark:aria-invalid:border-destructive/50";
|
|
28
36
|
/** Override for meda `Textarea` in booking forms. */
|
|
@@ -18,12 +18,12 @@ const BUTTON_SIZES = {
|
|
|
18
18
|
'icon-sm': 'size-8 rounded-[min(var(--radius-md),10px)]',
|
|
19
19
|
};
|
|
20
20
|
/** Classes for a booking button — also for a link that should look like one. */
|
|
21
|
-
export function bookingButtonClass({ variant = 'default', size = 'default', className, } = {}) {
|
|
22
|
-
return cn(BUTTON_BASE, BUTTON_VARIANTS[variant], BUTTON_SIZES[size], className);
|
|
21
|
+
export function bookingButtonClass({ variant = 'default', size = 'default', radius = 'default', className, } = {}) {
|
|
22
|
+
return cn(BUTTON_BASE, BUTTON_VARIANTS[variant], BUTTON_SIZES[size], radius === 'full' && 'rounded-full', className);
|
|
23
23
|
}
|
|
24
24
|
/** The booking screens' button. A native `<button type="button">` unless told otherwise. */
|
|
25
|
-
export function BookingButton({ variant, size, className, type = 'button', ...props }) {
|
|
26
|
-
return (_jsx("button", { type: type, "data-slot": "button", className: bookingButtonClass({ variant, size, className }), ...props }));
|
|
25
|
+
export function BookingButton({ variant, size, radius, className, type = 'button', ...props }) {
|
|
26
|
+
return (_jsx("button", { type: type, "data-slot": "button", className: bookingButtonClass({ variant, size, radius, className }), ...props }));
|
|
27
27
|
}
|
|
28
28
|
/** Override for meda `Input` in booking forms. */
|
|
29
29
|
export const BOOKING_INPUT_CLASS = 'h-12 rounded-[4px] border-[1.5px] border-input bg-background px-4 py-3 text-base transition-[color,border-color] focus-visible:border-primary focus-visible:ring-0 aria-invalid:border-destructive sm:text-base md:text-base dark:aria-invalid:border-destructive/50';
|
|
@@ -51,8 +51,10 @@ export type ServiceScreenLabels = Record<(typeof SERVICE_SCREEN_LABEL_KEYS)[numb
|
|
|
51
51
|
* - `price` — a card's price (passed to `ServiceCard`, and the «same as last time» card's)
|
|
52
52
|
* - `tab` / `tabSelected` — a person's tab in a multi-select family / the open one
|
|
53
53
|
* - `bar` — the multi-select step's sticky total bar
|
|
54
|
+
* - `next` — the bar's «Next» button (`data-disabled` while
|
|
55
|
+
* `selection.disableUntilReady` holds it back, for a site's own disabled look)
|
|
54
56
|
*/
|
|
55
|
-
export type ServiceScreenSlot = 'tab' | 'tabSelected' | 'bar' | 'root' | 'heading' | 'pill' | 'pillSelected' | 'groupHeading' | 'card' | 'cardSelected' | 'price';
|
|
57
|
+
export type ServiceScreenSlot = 'tab' | 'tabSelected' | 'bar' | 'next' | 'root' | 'heading' | 'pill' | 'pillSelected' | 'groupHeading' | 'card' | 'cardSelected' | 'price';
|
|
56
58
|
/** One service group, in display order. */
|
|
57
59
|
export interface ServiceScreenCategory {
|
|
58
60
|
key: string;
|
|
@@ -127,10 +129,18 @@ export interface ServiceSelection {
|
|
|
127
129
|
} | null;
|
|
128
130
|
/**
|
|
129
131
|
* Whether every person has at least one service. «Next» stays enabled
|
|
130
|
-
* either way (a disabled button explains nothing)
|
|
131
|
-
*
|
|
132
|
+
* either way (a disabled button explains nothing) unless
|
|
133
|
+
* `disableUntilReady`; while `false` the bar shows `service.chooseFirst`
|
|
134
|
+
* under the total.
|
|
132
135
|
*/
|
|
133
136
|
canContinue: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Opt-in: hold «Next» back — `disabled`, `aria-disabled` and
|
|
139
|
+
* `data-disabled` — while `canContinue` is `false`, drawn in the Material 3
|
|
140
|
+
* disabled look (38 % content on a 12 % container). The line under the
|
|
141
|
+
* total still says what is missing. Default: off, «Next» always enabled.
|
|
142
|
+
*/
|
|
143
|
+
disableUntilReady?: boolean;
|
|
134
144
|
/** A refusal to say politely (e.g. too many services), announced as a live region. */
|
|
135
145
|
notice?: string | null;
|
|
136
146
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medalsocial/meda",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "Shared Meda UI shell and runtime package.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -220,12 +220,12 @@
|
|
|
220
220
|
"@fontsource-variable/geist-mono": "5.3.0",
|
|
221
221
|
"@react-three/fiber": "9.8.1",
|
|
222
222
|
"@size-limit/preset-small-lib": "13.1.1",
|
|
223
|
-
"@storybook/addon-a11y": "10.6.
|
|
224
|
-
"@storybook/addon-docs": "10.6.
|
|
225
|
-
"@storybook/addon-mcp": "10.6.
|
|
226
|
-
"@storybook/addon-themes": "10.6.
|
|
227
|
-
"@storybook/addon-vitest": "10.6.
|
|
228
|
-
"@storybook/react-vite": "10.6.
|
|
223
|
+
"@storybook/addon-a11y": "10.6.2",
|
|
224
|
+
"@storybook/addon-docs": "10.6.2",
|
|
225
|
+
"@storybook/addon-mcp": "10.6.2",
|
|
226
|
+
"@storybook/addon-themes": "10.6.2",
|
|
227
|
+
"@storybook/addon-vitest": "10.6.2",
|
|
228
|
+
"@storybook/react-vite": "10.6.2",
|
|
229
229
|
"@tailwindcss/cli": "4.3.3",
|
|
230
230
|
"@tailwindcss/vite": "4.3.3",
|
|
231
231
|
"@tanstack/intent": "0.5.5",
|
|
@@ -235,31 +235,31 @@
|
|
|
235
235
|
"@types/react-dom": "19.3.0",
|
|
236
236
|
"@types/three": "0.185.4",
|
|
237
237
|
"@vitejs/plugin-react": "6.1.2",
|
|
238
|
-
"@vitest/browser-playwright": "4.1.
|
|
239
|
-
"@vitest/coverage-istanbul": "4.1.
|
|
240
|
-
"@vitest/coverage-v8": "4.1.
|
|
238
|
+
"@vitest/browser-playwright": "4.1.12",
|
|
239
|
+
"@vitest/coverage-istanbul": "4.1.12",
|
|
240
|
+
"@vitest/coverage-v8": "4.1.12",
|
|
241
241
|
"@xyflow/react": "12.12.0",
|
|
242
242
|
"chromatic": "18.12.0",
|
|
243
243
|
"esbuild": "0.28.2",
|
|
244
244
|
"fast-check": "4.10.2",
|
|
245
245
|
"husky": "9.1.7",
|
|
246
|
-
"jsdom": "30.1.
|
|
246
|
+
"jsdom": "30.1.3",
|
|
247
247
|
"next-themes": "0.4.6",
|
|
248
|
-
"playwright": "1.64.
|
|
249
|
-
"react": "19.2.
|
|
250
|
-
"react-dom": "19.2.
|
|
248
|
+
"playwright": "1.64.1",
|
|
249
|
+
"react": "19.2.9",
|
|
250
|
+
"react-dom": "19.2.9",
|
|
251
251
|
"react-markdown": "10.1.0",
|
|
252
252
|
"rehype-highlight": "7.0.2",
|
|
253
253
|
"remark-gfm": "4.0.1",
|
|
254
254
|
"size-limit": "13.1.1",
|
|
255
|
-
"storybook": "10.6.
|
|
255
|
+
"storybook": "10.6.2",
|
|
256
256
|
"tailwindcss": "4.3.3",
|
|
257
257
|
"three": "0.186.1",
|
|
258
258
|
"typescript": "~6.0.3",
|
|
259
259
|
"vite": "8.3.4",
|
|
260
|
-
"vitest": "4.1.
|
|
260
|
+
"vitest": "4.1.12",
|
|
261
261
|
"vitest-axe": "0.1.0",
|
|
262
|
-
"wrangler": "4.
|
|
262
|
+
"wrangler": "4.151.0"
|
|
263
263
|
},
|
|
264
264
|
"keywords": [
|
|
265
265
|
"tanstack-intent"
|