@medalsocial/meda 2.8.1 → 3.2.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 +130 -1
- package/dist/booking/add-child-sheet.d.ts +38 -0
- package/dist/booking/add-child-sheet.js +88 -0
- package/dist/booking/booking-skeleton.d.ts +24 -0
- package/dist/booking/booking-skeleton.js +17 -0
- package/dist/booking/confirmation.d.ts +85 -0
- package/dist/booking/confirmation.js +102 -0
- package/dist/booking/details-screen.d.ts +118 -0
- package/dist/booking/details-screen.js +314 -0
- package/dist/booking/format.d.ts +64 -0
- package/dist/booking/format.js +1 -0
- package/dist/booking/index.d.ts +35 -0
- package/dist/booking/index.js +40 -0
- package/dist/booking/internal/label-parts.d.ts +8 -0
- package/dist/booking/internal/label-parts.js +19 -0
- package/dist/booking/internal/ui.d.ts +33 -0
- package/dist/booking/internal/ui.js +35 -0
- package/dist/booking/label-keys.d.ts +34 -0
- package/dist/booking/label-keys.js +56 -0
- package/dist/booking/labels.d.ts +15 -0
- package/dist/booking/labels.js +17 -0
- package/dist/booking/live-status.d.ts +14 -0
- package/dist/booking/live-status.js +17 -0
- package/dist/booking/login-panel.d.ts +108 -0
- package/dist/booking/login-panel.js +239 -0
- package/dist/booking/login-sheet.d.ts +41 -0
- package/dist/booking/login-sheet.js +49 -0
- package/dist/booking/manage-screen.d.ts +113 -0
- package/dist/booking/manage-screen.js +369 -0
- package/dist/booking/otp-slots.d.ts +42 -0
- package/dist/booking/otp-slots.js +109 -0
- package/dist/booking/portal/account-card.d.ts +24 -0
- package/dist/booking/portal/account-card.js +18 -0
- package/dist/booking/portal/action-result.d.ts +30 -0
- package/dist/booking/portal/action-result.js +18 -0
- package/dist/booking/portal/age-confirm-card.d.ts +39 -0
- package/dist/booking/portal/age-confirm-card.js +42 -0
- package/dist/booking/portal/child-cards.d.ts +65 -0
- package/dist/booking/portal/child-cards.js +59 -0
- package/dist/booking/portal/data-controls.d.ts +48 -0
- package/dist/booking/portal/data-controls.js +103 -0
- package/dist/booking/portal/family-editor.d.ts +141 -0
- package/dist/booking/portal/family-editor.js +422 -0
- package/dist/booking/portal/logout-button.d.ts +21 -0
- package/dist/booking/portal/logout-button.js +27 -0
- package/dist/booking/portal/portal-shell.d.ts +71 -0
- package/dist/booking/portal/portal-shell.js +38 -0
- package/dist/booking/portal/portal-unreachable.d.ts +31 -0
- package/dist/booking/portal/portal-unreachable.js +19 -0
- package/dist/booking/portal/profile-form.d.ts +75 -0
- package/dist/booking/portal/profile-form.js +144 -0
- package/dist/booking/portal/rebook-cards.d.ts +44 -0
- package/dist/booking/portal/rebook-cards.js +35 -0
- package/dist/booking/portal/upcoming-bookings.d.ts +65 -0
- package/dist/booking/portal/upcoming-bookings.js +88 -0
- package/dist/booking/portal/vipps-link-row.d.ts +54 -0
- package/dist/booking/portal/vipps-link-row.js +80 -0
- package/dist/booking/portal/visit-history.d.ts +63 -0
- package/dist/booking/portal/visit-history.js +73 -0
- package/dist/booking/service-screen.d.ts +137 -0
- package/dist/booking/service-screen.js +233 -0
- package/dist/booking/slots.d.ts +12 -0
- package/dist/booking/slots.js +5 -0
- package/dist/booking/stylist-screen.d.ts +117 -0
- package/dist/booking/stylist-screen.js +226 -0
- package/dist/booking/summary-bar.d.ts +26 -0
- package/dist/booking/summary-bar.js +32 -0
- package/dist/booking/time-screen.d.ts +191 -0
- package/dist/booking/time-screen.js +418 -0
- package/dist/booking/types.d.ts +284 -0
- package/dist/booking/types.js +15 -0
- package/dist/booking/vipps-button.d.ts +49 -0
- package/dist/booking/vipps-button.js +33 -0
- package/dist/booking/who-screen.d.ts +121 -0
- package/dist/booking/who-screen.js +164 -0
- package/dist/primitives/avatar.d.ts +24 -0
- package/dist/primitives/avatar.js +44 -0
- package/dist/primitives/button.d.ts +21 -0
- package/dist/primitives/button.js +51 -0
- package/dist/primitives/checkbox.d.ts +23 -0
- package/dist/primitives/checkbox.js +20 -0
- package/dist/primitives/field.d.ts +47 -0
- package/dist/primitives/field.js +48 -0
- package/dist/primitives/index.d.ts +16 -0
- package/dist/primitives/index.js +8 -0
- package/dist/primitives/input.d.ts +10 -0
- package/dist/primitives/input.js +9 -0
- package/dist/primitives/sheet.d.ts +68 -0
- package/dist/primitives/sheet.js +303 -0
- package/dist/primitives/textarea.d.ts +14 -0
- package/dist/primitives/textarea.js +13 -0
- package/dist/primitives/toggle-group.d.ts +46 -0
- package/dist/primitives/toggle-group.js +148 -0
- package/dist/styles/base.css +40 -0
- package/dist/styles/booking.css +10 -0
- package/dist/styles/bridge.css +159 -0
- package/dist/styles/calendar.css +8 -0
- package/dist/styles/primitives.css +10 -0
- package/dist/styles/theme.css +12 -162
- package/dist/styles/vendor/xyflow.css +626 -0
- package/dist/styles/workflow-builder.css +17 -0
- package/package.json +45 -9
- package/skills/components/SKILL.md +53 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ComponentProps } from 'react';
|
|
2
|
+
export type AvatarSize = 'sm' | 'md' | 'lg';
|
|
3
|
+
export interface AvatarProps extends Omit<ComponentProps<'span'>, 'children'> {
|
|
4
|
+
/** Person or entity name. Drives the accessible name and the initials. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Image URL. Falls back to initials when missing or when it fails to load. */
|
|
7
|
+
src?: string | null;
|
|
8
|
+
/** Override the computed initials (e.g. a single brand letter). */
|
|
9
|
+
initials?: string;
|
|
10
|
+
size?: AvatarSize;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Up to two initials from a name, letters only: the first letter of the first
|
|
14
|
+
* and of the last word. Digits, punctuation and emoji are ignored, so
|
|
15
|
+
* "Ali (Medal) 2" gives "AM" and " " gives "". Any script is supported via
|
|
16
|
+
* Unicode letter classes ("Åse Øien" -> "ÅØ").
|
|
17
|
+
*/
|
|
18
|
+
export declare function getInitials(name: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Image avatar with an initials fallback. The wrapper carries `role="img"` +
|
|
21
|
+
* `aria-label={name}`, so the name is announced once whether the photo or the
|
|
22
|
+
* initials are showing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function Avatar({ name, src, initials, size, className, ...props }: AvatarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
4
|
+
import { cn } from '../lib/utils.js';
|
|
5
|
+
const SIZE_CLASSES = {
|
|
6
|
+
sm: 'size-8 text-xs',
|
|
7
|
+
md: 'size-10 text-sm',
|
|
8
|
+
lg: 'size-12 text-base',
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Up to two initials from a name, letters only: the first letter of the first
|
|
12
|
+
* and of the last word. Digits, punctuation and emoji are ignored, so
|
|
13
|
+
* "Ali (Medal) 2" gives "AM" and " " gives "". Any script is supported via
|
|
14
|
+
* Unicode letter classes ("Åse Øien" -> "ÅØ").
|
|
15
|
+
*/
|
|
16
|
+
export function getInitials(name) {
|
|
17
|
+
const words = name
|
|
18
|
+
.split(/\s+/)
|
|
19
|
+
.map((word) => word.replace(/[^\p{L}]/gu, ''))
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
if (words.length === 0)
|
|
22
|
+
return '';
|
|
23
|
+
const first = words[0] ?? '';
|
|
24
|
+
const last = words.length > 1 ? (words[words.length - 1] ?? '') : '';
|
|
25
|
+
return (Array.from(first)[0] ?? '').concat(Array.from(last)[0] ?? '').toLocaleUpperCase();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Image avatar with an initials fallback. The wrapper carries `role="img"` +
|
|
29
|
+
* `aria-label={name}`, so the name is announced once whether the photo or the
|
|
30
|
+
* initials are showing.
|
|
31
|
+
*/
|
|
32
|
+
export function Avatar({ name, src, initials, size = 'md', className, ...props }) {
|
|
33
|
+
// Keyed by URL, so a new `src` automatically gets a fresh chance.
|
|
34
|
+
const [failedSrc, setFailedSrc] = useState(null);
|
|
35
|
+
const showImage = Boolean(src) && failedSrc !== src;
|
|
36
|
+
const text = initials ?? getInitials(name);
|
|
37
|
+
// An SSR-rendered <img> can fail before React hydrates and attaches
|
|
38
|
+
// onError; catch that case by inspecting the element once it mounts.
|
|
39
|
+
const imageRef = useCallback((image) => {
|
|
40
|
+
if (image?.complete && image.naturalWidth === 0 && src)
|
|
41
|
+
setFailedSrc(src);
|
|
42
|
+
}, [src]);
|
|
43
|
+
return (_jsx("span", { role: "img", "aria-label": name, "data-slot": "avatar", "data-size": size, "data-state": showImage ? 'image' : 'fallback', className: cn('relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-muted font-medium text-muted-foreground', SIZE_CLASSES[size], className), ...props, children: showImage ? (_jsx("img", { ref: imageRef, src: src ?? undefined, alt: "", decoding: "async", loading: "lazy", onError: () => setFailedSrc(src ?? null), className: "size-full object-cover" })) : (_jsx("span", { "aria-hidden": "true", "data-slot": "avatar-fallback", children: text })) }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ComponentProps, ReactNode } from 'react';
|
|
2
|
+
import { type RenderElement } from '../lib/render-element.js';
|
|
3
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'outline';
|
|
4
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
export interface ButtonProps extends ComponentProps<'button'> {
|
|
6
|
+
variant?: ButtonVariant;
|
|
7
|
+
size?: ButtonSize;
|
|
8
|
+
/**
|
|
9
|
+
* Shows a spinner, sets `aria-busy`, and swallows clicks while keeping the
|
|
10
|
+
* button focusable (so focus is not lost mid-submit) and its width stable.
|
|
11
|
+
*/
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
/** Screen-reader text announced while `loading`. Defaults to "Loading". */
|
|
14
|
+
loadingLabel?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Render as another element (e.g. a router `<Link>` or `<a>`), merging
|
|
17
|
+
* meda's props and classes onto it — same pattern as `AuthProviderButton`.
|
|
18
|
+
*/
|
|
19
|
+
render?: RenderElement<ButtonHTMLAttributes<HTMLElement>>;
|
|
20
|
+
}
|
|
21
|
+
export declare function Button({ variant, size, loading, loadingLabel, render, type, disabled, className, children, onClick, ...props }: ButtonProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { LoaderCircle } from 'lucide-react';
|
|
4
|
+
import { renderElement } from '../lib/render-element.js';
|
|
5
|
+
import { cn } from '../lib/utils.js';
|
|
6
|
+
// Semantic tokens only, so a consumer's own `--primary` / `--secondary` /
|
|
7
|
+
// `--accent` re-theme the button without touching meda.
|
|
8
|
+
const VARIANT_CLASSES = {
|
|
9
|
+
primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
10
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
11
|
+
ghost: 'text-foreground hover:bg-accent hover:text-accent-foreground',
|
|
12
|
+
outline: 'border border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground',
|
|
13
|
+
};
|
|
14
|
+
const SIZE_CLASSES = {
|
|
15
|
+
sm: 'h-8 gap-1.5 px-3 text-xs [&_svg]:size-3.5',
|
|
16
|
+
md: 'h-10 gap-2 px-4 text-sm [&_svg]:size-4',
|
|
17
|
+
lg: 'h-11 gap-2 px-5 text-base [&_svg]:size-4',
|
|
18
|
+
};
|
|
19
|
+
export function Button({ variant = 'primary', size = 'md', loading = false, loadingLabel = 'Loading', render, type = 'button', disabled, className, children, onClick, ...props }) {
|
|
20
|
+
// A rendered link can't be natively disabled, so swallow its clicks too.
|
|
21
|
+
const inert = loading || (render !== undefined && Boolean(disabled));
|
|
22
|
+
const handleClick = (event) => {
|
|
23
|
+
if (inert) {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
onClick?.(event);
|
|
28
|
+
};
|
|
29
|
+
const content = (_jsxs(_Fragment, { children: [loading ? (_jsxs(_Fragment, { children: [_jsx(LoaderCircle, { "data-slot": "button-spinner", "aria-hidden": "true", className: "animate-spin motion-reduce:animate-none" }), _jsx("span", { className: "sr-only", children: loadingLabel })] })) : null, children] }));
|
|
30
|
+
const buttonProps = {
|
|
31
|
+
...props,
|
|
32
|
+
'data-slot': 'button',
|
|
33
|
+
'data-variant': variant,
|
|
34
|
+
'data-size': size,
|
|
35
|
+
'data-loading': loading || undefined,
|
|
36
|
+
'aria-busy': loading || undefined,
|
|
37
|
+
'aria-disabled': loading || undefined,
|
|
38
|
+
className: cn('relative inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 data-loading:cursor-progress [&_svg]:pointer-events-none [&_svg]:shrink-0', VARIANT_CLASSES[variant], SIZE_CLASSES[size], className),
|
|
39
|
+
onClick: handleClick,
|
|
40
|
+
children: content,
|
|
41
|
+
};
|
|
42
|
+
if (render) {
|
|
43
|
+
// Non-button hosts (links) have no `disabled`; expose it via ARIA instead.
|
|
44
|
+
return renderElement(render, {
|
|
45
|
+
...buttonProps,
|
|
46
|
+
'aria-disabled': inert || undefined,
|
|
47
|
+
'data-disabled': disabled || undefined,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return _jsx("button", { type: type, disabled: disabled, ...buttonProps });
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export interface CheckboxProps extends Omit<ComponentProps<'input'>, 'type' | 'size'> {
|
|
3
|
+
/**
|
|
4
|
+
* Called with the new checked state. Fires alongside `onChange`, so a form
|
|
5
|
+
* can use whichever it prefers.
|
|
6
|
+
*/
|
|
7
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Marks the box invalid: sets `aria-invalid="true"` and the destructive
|
|
10
|
+
* border. An explicit `aria-invalid` prop still wins.
|
|
11
|
+
*/
|
|
12
|
+
invalid?: boolean;
|
|
13
|
+
/** Class for the wrapping `<span>` (the input itself takes `className`). */
|
|
14
|
+
rootClassName?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A native `<input type="checkbox">` drawn as a 16 px box with a check mark.
|
|
18
|
+
* Native on purpose: it submits with a form, works with `<label htmlFor>`,
|
|
19
|
+
* answers Space without JavaScript, and costs no dialog/portal machinery.
|
|
20
|
+
* The input carries `data-slot="checkbox"` and a `data-invalid` hook; style
|
|
21
|
+
* the checked state with `checked:` / `peer-checked:`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Checkbox({ className, rootClassName, invalid, onCheckedChange, onChange, checked, defaultChecked, 'aria-invalid': ariaInvalid, ...props }: CheckboxProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check } from 'lucide-react';
|
|
4
|
+
import { cn } from '../lib/utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* A native `<input type="checkbox">` drawn as a 16 px box with a check mark.
|
|
7
|
+
* Native on purpose: it submits with a form, works with `<label htmlFor>`,
|
|
8
|
+
* answers Space without JavaScript, and costs no dialog/portal machinery.
|
|
9
|
+
* The input carries `data-slot="checkbox"` and a `data-invalid` hook; style
|
|
10
|
+
* the checked state with `checked:` / `peer-checked:`.
|
|
11
|
+
*/
|
|
12
|
+
export function Checkbox({ className, rootClassName, invalid = false, onCheckedChange, onChange, checked, defaultChecked, 'aria-invalid': ariaInvalid, ...props }) {
|
|
13
|
+
const resolvedInvalid = ariaInvalid ?? (invalid || undefined);
|
|
14
|
+
return (_jsxs("span", { "data-slot": "checkbox-root", className: cn('relative inline-flex size-4 shrink-0', rootClassName), children: [_jsx("input", { type: "checkbox", "data-slot": "checkbox", "data-invalid": resolvedInvalid === true || resolvedInvalid === 'true' ? '' : undefined, "aria-invalid": resolvedInvalid, checked: checked, defaultChecked: defaultChecked, onChange: (event) => {
|
|
15
|
+
onChange?.(event);
|
|
16
|
+
onCheckedChange?.(event.currentTarget.checked);
|
|
17
|
+
}, className: cn(
|
|
18
|
+
// Same focus / invalid treatment as Input, so the two sit together.
|
|
19
|
+
'peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-colors outline-none checked:border-primary checked:bg-primary focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/30 disabled:cursor-not-allowed disabled:opacity-50', className), ...props }), _jsx(Check, { "aria-hidden": "true", "data-slot": "checkbox-indicator", className: "pointer-events-none absolute inset-0 m-auto size-3.5 text-primary-foreground opacity-0 peer-checked:opacity-100" })] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export type FieldOrientation = 'vertical' | 'horizontal';
|
|
3
|
+
export interface FieldProps extends ComponentProps<'div'> {
|
|
4
|
+
/**
|
|
5
|
+
* `vertical` (default) stacks label, control, description and error.
|
|
6
|
+
* `horizontal` puts a checkbox/switch beside its label.
|
|
7
|
+
*/
|
|
8
|
+
orientation?: FieldOrientation;
|
|
9
|
+
/**
|
|
10
|
+
* Dims the label. A disabled control inside the field does this on its
|
|
11
|
+
* own (`:has(:disabled)`); the prop is for fields whose control cannot be.
|
|
12
|
+
*/
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type FieldLabelProps = ComponentProps<'label'>;
|
|
16
|
+
export type FieldDescriptionProps = ComponentProps<'p'>;
|
|
17
|
+
export type FieldSetProps = ComponentProps<'fieldset'>;
|
|
18
|
+
export type FieldLegendProps = ComponentProps<'legend'>;
|
|
19
|
+
export interface FieldErrorProps extends ComponentProps<'p'> {
|
|
20
|
+
/**
|
|
21
|
+
* Several messages at once (e.g. from a schema validator). Each renders as
|
|
22
|
+
* its own `<p data-slot="field-error">` inside one wrapper that takes `id`
|
|
23
|
+
* and the other props; duplicates are shown once.
|
|
24
|
+
*/
|
|
25
|
+
errors?: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
declare function FieldRoot({ orientation, disabled, className, ...props }: FieldProps): import("react").JSX.Element;
|
|
28
|
+
declare function FieldLabel({ className, ...props }: FieldLabelProps): import("react").JSX.Element;
|
|
29
|
+
declare function FieldDescription({ className, ...props }: FieldDescriptionProps): import("react").JSX.Element;
|
|
30
|
+
declare function FieldError({ className, children, errors, ...props }: FieldErrorProps): import("react").JSX.Element | null;
|
|
31
|
+
declare function FieldSet({ className, ...props }: FieldSetProps): import("react").JSX.Element;
|
|
32
|
+
declare function FieldLegend({ className, ...props }: FieldLegendProps): import("react").JSX.Element;
|
|
33
|
+
/**
|
|
34
|
+
* Form field layout: `Field` groups a control with `Field.Label`,
|
|
35
|
+
* `Field.Description` and `Field.Error`. It owns no state and no ids — wire
|
|
36
|
+
* `htmlFor` / `aria-describedby` yourself, so the markup stays obvious and
|
|
37
|
+
* works with any form library. `Field.Set` + `Field.Legend` group related
|
|
38
|
+
* fields (radio sets, consent boxes).
|
|
39
|
+
*/
|
|
40
|
+
export declare const Field: typeof FieldRoot & {
|
|
41
|
+
Label: typeof FieldLabel;
|
|
42
|
+
Description: typeof FieldDescription;
|
|
43
|
+
Error: typeof FieldError;
|
|
44
|
+
Set: typeof FieldSet;
|
|
45
|
+
Legend: typeof FieldLegend;
|
|
46
|
+
};
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../lib/utils.js';
|
|
4
|
+
const ORIENTATION_CLASSES = {
|
|
5
|
+
vertical: '',
|
|
6
|
+
horizontal: 'grid-cols-[auto_1fr] items-center',
|
|
7
|
+
};
|
|
8
|
+
function FieldRoot({ orientation = 'vertical', disabled, className, ...props }) {
|
|
9
|
+
return (_jsx("div", { "data-slot": "field", "data-orientation": orientation, "data-disabled": disabled ? 'true' : undefined, className: cn('group/field grid gap-2', ORIENTATION_CLASSES[orientation], className), ...props }));
|
|
10
|
+
}
|
|
11
|
+
function FieldLabel({ className, ...props }) {
|
|
12
|
+
return (_jsx("label", { "data-slot": "field-label", className: cn('flex select-none items-center gap-2 text-sm font-medium leading-none group-has-disabled/field:cursor-not-allowed group-has-disabled/field:opacity-50 group-data-[disabled=true]/field:pointer-events-none group-data-[disabled=true]/field:opacity-50', className), ...props }));
|
|
13
|
+
}
|
|
14
|
+
function FieldDescription({ className, ...props }) {
|
|
15
|
+
return (_jsx("p", { "data-slot": "field-description", className: cn('text-sm text-muted-foreground', className), ...props }));
|
|
16
|
+
}
|
|
17
|
+
function FieldError({ className, children, errors, ...props }) {
|
|
18
|
+
const unique = errors ? Array.from(new Set(errors)) : [];
|
|
19
|
+
if (unique.length > 0) {
|
|
20
|
+
// One element owns the id (and every other prop), so `aria-describedby`
|
|
21
|
+
// pointing at it reads out all of the messages.
|
|
22
|
+
const { id, ...rest } = props;
|
|
23
|
+
return (_jsx("div", { id: id, "data-slot": "field-errors", className: "flex flex-col gap-1", ...rest, children: unique.map((error) => (_jsx("p", { "data-slot": "field-error", className: cn('text-sm text-destructive', className), children: error }, error))) }));
|
|
24
|
+
}
|
|
25
|
+
if (children == null || children === false)
|
|
26
|
+
return null;
|
|
27
|
+
return (_jsx("p", { "data-slot": "field-error", className: cn('text-sm text-destructive', className), ...props, children: children }));
|
|
28
|
+
}
|
|
29
|
+
function FieldSet({ className, ...props }) {
|
|
30
|
+
return _jsx("fieldset", { "data-slot": "field-set", className: cn('grid gap-6', className), ...props });
|
|
31
|
+
}
|
|
32
|
+
function FieldLegend({ className, ...props }) {
|
|
33
|
+
return (_jsx("legend", { "data-slot": "field-legend", className: cn('text-base font-medium leading-none text-foreground', className), ...props }));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Form field layout: `Field` groups a control with `Field.Label`,
|
|
37
|
+
* `Field.Description` and `Field.Error`. It owns no state and no ids — wire
|
|
38
|
+
* `htmlFor` / `aria-describedby` yourself, so the markup stays obvious and
|
|
39
|
+
* works with any form library. `Field.Set` + `Field.Legend` group related
|
|
40
|
+
* fields (radio sets, consent boxes).
|
|
41
|
+
*/
|
|
42
|
+
export const Field = Object.assign(FieldRoot, {
|
|
43
|
+
Label: FieldLabel,
|
|
44
|
+
Description: FieldDescription,
|
|
45
|
+
Error: FieldError,
|
|
46
|
+
Set: FieldSet,
|
|
47
|
+
Legend: FieldLegend,
|
|
48
|
+
});
|
|
@@ -1,10 +1,26 @@
|
|
|
1
|
+
export type { AvatarProps, AvatarSize } from './avatar.js';
|
|
2
|
+
export { Avatar, getInitials } from './avatar.js';
|
|
3
|
+
export type { ButtonProps, ButtonSize, ButtonVariant } from './button.js';
|
|
4
|
+
export { Button } from './button.js';
|
|
1
5
|
export type { CardBodyProps, CardFooterProps, CardHeaderProps, CardProps, } from './card.js';
|
|
2
6
|
export { Card } from './card.js';
|
|
7
|
+
export type { CheckboxProps } from './checkbox.js';
|
|
8
|
+
export { Checkbox } from './checkbox.js';
|
|
3
9
|
export type { EmptyStateProps, EmptyStateVariant } from './empty-state.js';
|
|
4
10
|
export { EmptyState } from './empty-state.js';
|
|
11
|
+
export type { FieldDescriptionProps, FieldErrorProps, FieldLabelProps, FieldLegendProps, FieldOrientation, FieldProps, FieldSetProps, } from './field.js';
|
|
12
|
+
export { Field } from './field.js';
|
|
5
13
|
export type { FilterRailGroupProps, FilterRailProps } from './filter-rail.js';
|
|
6
14
|
export { FilterRail } from './filter-rail.js';
|
|
15
|
+
export type { InputProps } from './input.js';
|
|
16
|
+
export { Input } from './input.js';
|
|
17
|
+
export type { SheetCloseProps, SheetContentProps, SheetProps, SheetSide, SheetTriggerProps, } from './sheet.js';
|
|
18
|
+
export { Sheet } from './sheet.js';
|
|
7
19
|
export type { SkeletonProps } from './skeleton.js';
|
|
8
20
|
export { Skeleton } from './skeleton.js';
|
|
9
21
|
export type { StatusPillProps, StatusPillSize, StatusPillTone } from './status-pill.js';
|
|
10
22
|
export { StatusPill } from './status-pill.js';
|
|
23
|
+
export type { TextareaProps } from './textarea.js';
|
|
24
|
+
export { Textarea } from './textarea.js';
|
|
25
|
+
export type { ToggleGroupItemProps, ToggleGroupMultipleProps, ToggleGroupOrientation, ToggleGroupProps, ToggleGroupSingleProps, ToggleGroupSize, } from './toggle-group.js';
|
|
26
|
+
export { ToggleGroup } from './toggle-group.js';
|
package/dist/primitives/index.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
export { Avatar, getInitials } from './avatar.js';
|
|
2
|
+
export { Button } from './button.js';
|
|
1
3
|
export { Card } from './card.js';
|
|
4
|
+
export { Checkbox } from './checkbox.js';
|
|
2
5
|
export { EmptyState } from './empty-state.js';
|
|
6
|
+
export { Field } from './field.js';
|
|
3
7
|
export { FilterRail } from './filter-rail.js';
|
|
8
|
+
export { Input } from './input.js';
|
|
9
|
+
export { Sheet } from './sheet.js';
|
|
4
10
|
export { Skeleton } from './skeleton.js';
|
|
5
11
|
export { StatusPill } from './status-pill.js';
|
|
12
|
+
export { Textarea } from './textarea.js';
|
|
13
|
+
export { ToggleGroup } from './toggle-group.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export interface InputProps extends ComponentProps<'input'> {
|
|
3
|
+
/**
|
|
4
|
+
* Marks the field invalid: sets `aria-invalid="true"` and the destructive
|
|
5
|
+
* border/ring. Pair it with `aria-describedby` pointing at the error text.
|
|
6
|
+
* An explicit `aria-invalid` prop still wins.
|
|
7
|
+
*/
|
|
8
|
+
invalid?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function Input({ invalid, type, className, 'aria-invalid': ariaInvalid, ...props }: InputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../lib/utils.js';
|
|
4
|
+
export function Input({ invalid = false, type = 'text', className, 'aria-invalid': ariaInvalid, ...props }) {
|
|
5
|
+
const resolvedInvalid = ariaInvalid ?? (invalid || undefined);
|
|
6
|
+
return (_jsx("input", { type: type, "data-slot": "input", "data-invalid": resolvedInvalid === true || resolvedInvalid === 'true' ? '' : undefined, "aria-invalid": resolvedInvalid, className: cn(
|
|
7
|
+
// text-base below `sm` keeps iOS Safari from zooming on focus.
|
|
8
|
+
'flex h-10 w-full min-w-0 rounded-md border border-input bg-background px-3 py-2 text-base text-foreground transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/30 file:border-0 file:bg-transparent file:text-sm file:font-medium sm:text-sm', className), ...props }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
import { type RenderElement } from '../lib/render-element.js';
|
|
3
|
+
export type SheetSide = 'responsive' | 'bottom';
|
|
4
|
+
export interface SheetProps {
|
|
5
|
+
/** Controlled open state. */
|
|
6
|
+
open?: boolean;
|
|
7
|
+
/** Initial open state when uncontrolled. */
|
|
8
|
+
defaultOpen?: boolean;
|
|
9
|
+
/** Called with the requested state (trigger, close button, Escape, backdrop). */
|
|
10
|
+
onOpenChange?: (open: boolean) => void;
|
|
11
|
+
/**
|
|
12
|
+
* `false` ignores every close request (Escape, backdrop, `Sheet.Close`,
|
|
13
|
+
* a native `dialog.close()`) — use it while a submit is in flight so the
|
|
14
|
+
* sheet cannot vanish under the user. Default `true`.
|
|
15
|
+
*/
|
|
16
|
+
dismissible?: boolean;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
export interface SheetTriggerProps extends ComponentProps<'button'> {
|
|
20
|
+
/** Render as another element, merging the trigger's props onto it. */
|
|
21
|
+
render?: RenderElement<ButtonHTMLAttributes<HTMLElement>>;
|
|
22
|
+
}
|
|
23
|
+
export interface SheetContentProps extends Omit<ComponentProps<'dialog'>, 'open'> {
|
|
24
|
+
/**
|
|
25
|
+
* `responsive` (default): a bottom sheet on phones, a centred dialog from
|
|
26
|
+
* `md` up. `bottom` keeps the bottom sheet at every width.
|
|
27
|
+
*/
|
|
28
|
+
side?: SheetSide;
|
|
29
|
+
/** Class for the padded inner wrapper that holds the children. */
|
|
30
|
+
bodyClassName?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SheetCloseProps extends ComponentProps<'button'> {
|
|
33
|
+
/** Render as another element (e.g. a meda `Button`), merging close behaviour onto it. */
|
|
34
|
+
render?: RenderElement<ButtonHTMLAttributes<HTMLElement>>;
|
|
35
|
+
}
|
|
36
|
+
declare function SheetRoot({ open: openProp, defaultOpen, onOpenChange, dismissible, children, }: SheetProps): import("react").JSX.Element;
|
|
37
|
+
declare function SheetTrigger({ render, onClick, className, children, ...props }: SheetTriggerProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
38
|
+
declare function SheetContent({ side, className, bodyClassName, children, onCancel, onClick, onClose, onKeyDown, onPointerDown, ...props }: SheetContentProps): import("react").JSX.Element;
|
|
39
|
+
declare function SheetTitle({ className, id, ...props }: ComponentProps<'h2'>): import("react").JSX.Element;
|
|
40
|
+
declare function SheetDescription({ className, id, ...props }: ComponentProps<'p'>): import("react").JSX.Element;
|
|
41
|
+
declare function SheetClose({ render, onClick, children, ...props }: SheetCloseProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* A modal sheet on the native `<dialog>` element: a bottom sheet on phones and
|
|
44
|
+
* a centred dialog from `md` up. `showModal()` gives the top layer, an inert
|
|
45
|
+
* background and a Tab trap for free; the component adds controlled state,
|
|
46
|
+
* Escape / backdrop dismissal, scroll lock and focus return to the trigger.
|
|
47
|
+
* The entry fade is skipped under `prefers-reduced-motion`.
|
|
48
|
+
*
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <Sheet>
|
|
51
|
+
* <Sheet.Trigger>Add child</Sheet.Trigger>
|
|
52
|
+
* <Sheet.Content>
|
|
53
|
+
* <Sheet.Title>Add child</Sheet.Title>
|
|
54
|
+
* <Sheet.Description>Only for this booking.</Sheet.Description>
|
|
55
|
+
* …
|
|
56
|
+
* <Sheet.Close aria-label="Close">×</Sheet.Close>
|
|
57
|
+
* </Sheet.Content>
|
|
58
|
+
* </Sheet>
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare const Sheet: typeof SheetRoot & {
|
|
62
|
+
Trigger: typeof SheetTrigger;
|
|
63
|
+
Content: typeof SheetContent;
|
|
64
|
+
Title: typeof SheetTitle;
|
|
65
|
+
Description: typeof SheetDescription;
|
|
66
|
+
Close: typeof SheetClose;
|
|
67
|
+
};
|
|
68
|
+
export {};
|