@bmsuisse/ui 0.19.0 → 0.20.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/dist/index.js CHANGED
@@ -3471,11 +3471,717 @@ function Stepper({
3471
3471
  );
3472
3472
  }
3473
3473
 
3474
+ // src/patterns/empty-state/EmptyState.tsx
3475
+ import { FunnelIcon } from "@heroicons/react/24/outline";
3476
+ import { jsx as jsx49, jsxs as jsxs31 } from "react/jsx-runtime";
3477
+ var EmptyState = ({
3478
+ title,
3479
+ description,
3480
+ icon: Icon2,
3481
+ action,
3482
+ size = "md",
3483
+ fill = false,
3484
+ className,
3485
+ ...props
3486
+ }) => {
3487
+ const sm = size === "sm";
3488
+ return /* @__PURE__ */ jsxs31(
3489
+ "div",
3490
+ {
3491
+ "data-testid": "empty-state",
3492
+ className: cn(
3493
+ "flex flex-col items-center text-center",
3494
+ sm ? "gap-1 px-4 py-4" : "gap-1.5 px-6 py-8",
3495
+ fill && "min-h-0 flex-1 justify-center",
3496
+ className
3497
+ ),
3498
+ ...props,
3499
+ children: [
3500
+ Icon2 ? /* @__PURE__ */ jsx49(
3501
+ "div",
3502
+ {
3503
+ className: cn(
3504
+ "mb-1 flex items-center justify-center rounded-full bg-muted",
3505
+ sm ? "h-8 w-8" : "h-12 w-12"
3506
+ ),
3507
+ children: /* @__PURE__ */ jsx49(Icon2, { "aria-hidden": "true", className: cn("text-muted-foreground", sm ? "h-4 w-4" : "h-6 w-6") })
3508
+ }
3509
+ ) : null,
3510
+ /* @__PURE__ */ jsx49("p", { className: cn("font-semibold text-foreground", sm ? "text-xs" : "text-sm"), children: title }),
3511
+ description ? /* @__PURE__ */ jsx49("p", { className: cn("max-w-prose text-muted-foreground", sm ? "text-xs" : "text-sm"), children: description }) : null,
3512
+ action ? /* @__PURE__ */ jsx49("div", { className: "mt-2 flex justify-center gap-2", children: action }) : null
3513
+ ]
3514
+ }
3515
+ );
3516
+ };
3517
+ var defaultFilteredEmptyStateLabels = {
3518
+ title: "No results",
3519
+ description: "Nothing matches the current search or filters.",
3520
+ reset: "Reset filters"
3521
+ };
3522
+ var FilteredEmptyState = ({
3523
+ onReset,
3524
+ labels,
3525
+ icon = FunnelIcon,
3526
+ size,
3527
+ ...props
3528
+ }) => {
3529
+ const l = { ...defaultFilteredEmptyStateLabels, ...labels };
3530
+ const rootTestId = props["data-testid"];
3531
+ return /* @__PURE__ */ jsx49(
3532
+ EmptyState,
3533
+ {
3534
+ icon,
3535
+ size,
3536
+ title: l.title,
3537
+ description: l.description,
3538
+ action: /* @__PURE__ */ jsx49(
3539
+ Button,
3540
+ {
3541
+ type: "button",
3542
+ variant: "outline",
3543
+ size: size === "sm" ? "xs" : "sm",
3544
+ onClick: onReset,
3545
+ "data-testid": typeof rootTestId === "string" ? `${rootTestId}-reset` : void 0,
3546
+ children: l.reset
3547
+ }
3548
+ ),
3549
+ ...props
3550
+ }
3551
+ );
3552
+ };
3553
+
3554
+ // src/patterns/toast/toastStore.ts
3555
+ var DEFAULT_TOAST_DURATION = { info: 4e3, success: 4e3, warning: 4e3, error: 8e3 };
3556
+ var MAX_TOASTS = 3;
3557
+ var TOAST_EXIT_MS = 150;
3558
+ var toasts = [];
3559
+ var nextId = 1;
3560
+ var listeners = /* @__PURE__ */ new Set();
3561
+ var emit = (next) => {
3562
+ toasts = next;
3563
+ for (const l of listeners) l();
3564
+ };
3565
+ var subscribeToasts = (listener) => {
3566
+ listeners.add(listener);
3567
+ return () => listeners.delete(listener);
3568
+ };
3569
+ var getToasts = () => toasts;
3570
+ var EMPTY = [];
3571
+ var getServerToasts = () => EMPTY;
3572
+ var remove = (id) => emit(toasts.filter((t) => t.id !== id));
3573
+ function dismissToast(id) {
3574
+ const targets = toasts.filter((t) => !t.leaving && (id === void 0 || t.id === id));
3575
+ if (targets.length === 0) return;
3576
+ const ids = new Set(targets.map((t) => t.id));
3577
+ emit(toasts.map((t) => ids.has(t.id) ? { ...t, leaving: true } : t));
3578
+ setTimeout(() => {
3579
+ for (const i of ids) remove(i);
3580
+ }, TOAST_EXIT_MS);
3581
+ }
3582
+ function show(message, options = {}) {
3583
+ const id = nextId++;
3584
+ const tone = options.tone ?? "info";
3585
+ const item = {
3586
+ id,
3587
+ message,
3588
+ description: options.description,
3589
+ tone,
3590
+ action: options.action,
3591
+ duration: options.duration,
3592
+ leaving: false
3593
+ };
3594
+ const active = toasts.filter((t) => !t.leaving);
3595
+ if (active.length >= MAX_TOASTS) {
3596
+ const victim = active.find((t) => t.duration !== Infinity) ?? active[0];
3597
+ if (victim) dismissToast(victim.id);
3598
+ }
3599
+ emit([...toasts, item]);
3600
+ return id;
3601
+ }
3602
+ var toast = Object.assign(show, {
3603
+ info: (message, options) => show(message, { ...options, tone: "info" }),
3604
+ success: (message, options) => show(message, { ...options, tone: "success" }),
3605
+ warning: (message, options) => show(message, { ...options, tone: "warning" }),
3606
+ error: (message, options) => show(message, { ...options, tone: "error" }),
3607
+ dismiss: dismissToast
3608
+ });
3609
+ function resetToasts() {
3610
+ emit([]);
3611
+ }
3612
+
3613
+ // src/patterns/toast/Toaster.tsx
3614
+ import {
3615
+ CheckCircleIcon as CheckCircleIcon2,
3616
+ ExclamationTriangleIcon as ExclamationTriangleIcon6,
3617
+ InformationCircleIcon as InformationCircleIcon2,
3618
+ XCircleIcon,
3619
+ XMarkIcon as XMarkIcon6
3620
+ } from "@heroicons/react/24/outline";
3621
+ import { useEffect as useEffect9, useRef as useRef9, useState as useState16, useSyncExternalStore } from "react";
3622
+ import { jsx as jsx50, jsxs as jsxs32 } from "react/jsx-runtime";
3623
+ var defaultToasterLabels = {
3624
+ region: "Notifications",
3625
+ dismiss: "Dismiss"
3626
+ };
3627
+ var positionClass = {
3628
+ "bottom-center": "bottom-4 items-center pb-[env(safe-area-inset-bottom)]",
3629
+ "bottom-left": "bottom-4 items-start pb-[env(safe-area-inset-bottom)]",
3630
+ "bottom-right": "bottom-4 items-end pb-[env(safe-area-inset-bottom)]",
3631
+ "top-center": "top-4 items-center pt-[env(safe-area-inset-top)]",
3632
+ "top-left": "top-4 items-start pt-[env(safe-area-inset-top)]",
3633
+ "top-right": "top-4 items-end pt-[env(safe-area-inset-top)]"
3634
+ };
3635
+ var toneIcon = {
3636
+ info: InformationCircleIcon2,
3637
+ success: CheckCircleIcon2,
3638
+ warning: ExclamationTriangleIcon6,
3639
+ error: XCircleIcon
3640
+ };
3641
+ var toneIconClass = {
3642
+ info: "text-sky-600 dark:text-sky-400",
3643
+ success: "text-emerald-600 dark:text-emerald-400",
3644
+ warning: "text-amber-600 dark:text-amber-400",
3645
+ error: "text-destructive"
3646
+ };
3647
+ var Toaster = ({ position = "bottom-center", duration, labels, className }) => {
3648
+ const toasts2 = useSyncExternalStore(subscribeToasts, getToasts, getServerToasts);
3649
+ const l = { ...defaultToasterLabels, ...labels };
3650
+ const top = position.startsWith("top");
3651
+ return /* @__PURE__ */ jsx50(
3652
+ "section",
3653
+ {
3654
+ "aria-label": l.region,
3655
+ "aria-live": "polite",
3656
+ className: cn(
3657
+ "pointer-events-none fixed inset-x-0 z-[100] flex gap-2 px-4",
3658
+ top ? "flex-col-reverse" : "flex-col",
3659
+ positionClass[position],
3660
+ className
3661
+ ),
3662
+ children: toasts2.map((t) => /* @__PURE__ */ jsx50(ToastCard, { toast: t, duration: resolveDuration(t, duration), dismissLabel: l.dismiss }, t.id))
3663
+ }
3664
+ );
3665
+ };
3666
+ var resolveDuration = (t, fallback) => t.duration ?? (typeof fallback === "number" ? fallback : fallback?.[t.tone] ?? DEFAULT_TOAST_DURATION[t.tone]);
3667
+ var ToastCard = ({
3668
+ toast: toast2,
3669
+ duration,
3670
+ dismissLabel
3671
+ }) => {
3672
+ const [entered, setEntered] = useState16(false);
3673
+ const [hovered, setHovered] = useState16(false);
3674
+ const [focused, setFocused] = useState16(false);
3675
+ const paused = hovered || focused;
3676
+ const bar = useRef9(null);
3677
+ const Icon2 = toneIcon[toast2.tone];
3678
+ const timed = Number.isFinite(duration) && duration > 0;
3679
+ useEffect9(() => {
3680
+ const raf = requestAnimationFrame(() => setEntered(true));
3681
+ return () => cancelAnimationFrame(raf);
3682
+ }, []);
3683
+ const remaining = useRef9(duration);
3684
+ const animRef = useRef9(null);
3685
+ useEffect9(() => {
3686
+ if (!timed || toast2.leaving) return;
3687
+ const el = bar.current;
3688
+ if (el && typeof el.animate === "function") {
3689
+ const anim = el.animate([{ transform: "scaleX(1)" }, { transform: "scaleX(0)" }], {
3690
+ duration,
3691
+ fill: "forwards"
3692
+ });
3693
+ anim.onfinish = () => dismissToast(toast2.id);
3694
+ animRef.current = anim;
3695
+ return () => {
3696
+ anim.onfinish = null;
3697
+ anim.cancel();
3698
+ animRef.current = null;
3699
+ };
3700
+ }
3701
+ return void 0;
3702
+ }, [timed, duration, toast2.id, toast2.leaving]);
3703
+ useEffect9(() => {
3704
+ if (!timed || toast2.leaving) return;
3705
+ if (animRef.current) {
3706
+ if (paused) animRef.current.pause();
3707
+ else animRef.current.play();
3708
+ return;
3709
+ }
3710
+ if (paused) return;
3711
+ const started = Date.now();
3712
+ const handle = setTimeout(() => dismissToast(toast2.id), remaining.current);
3713
+ return () => {
3714
+ clearTimeout(handle);
3715
+ remaining.current -= Date.now() - started;
3716
+ };
3717
+ }, [paused, timed, toast2.id, toast2.leaving]);
3718
+ const shown = entered && !toast2.leaving;
3719
+ return /* @__PURE__ */ jsxs32(
3720
+ "div",
3721
+ {
3722
+ role: toast2.tone === "error" ? "alert" : void 0,
3723
+ "data-testid": "toast",
3724
+ "data-tone": toast2.tone,
3725
+ "data-state": toast2.leaving ? "closed" : "open",
3726
+ onPointerEnter: (e) => {
3727
+ if (e.pointerType === "mouse") setHovered(true);
3728
+ },
3729
+ onPointerLeave: () => setHovered(false),
3730
+ onFocus: () => setFocused(true),
3731
+ onBlur: (e) => {
3732
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocused(false);
3733
+ },
3734
+ className: cn(
3735
+ "pointer-events-auto relative flex w-full max-w-sm items-start gap-2 overflow-hidden rounded-lg border border-border bg-card px-3 py-2.5 text-sm text-card-foreground shadow-lg transition-[opacity,transform] duration-150 ease-out motion-reduce:transition-none",
3736
+ shown ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0",
3737
+ toast2.leaving && "pointer-events-none"
3738
+ ),
3739
+ children: [
3740
+ /* @__PURE__ */ jsx50(Icon2, { "aria-hidden": "true", className: cn("mt-0.5 h-4 w-4 shrink-0", toneIconClass[toast2.tone]) }),
3741
+ /* @__PURE__ */ jsxs32("div", { className: "min-w-0 flex-1", children: [
3742
+ /* @__PURE__ */ jsx50("p", { className: "font-medium", children: toast2.message }),
3743
+ toast2.description ? /* @__PURE__ */ jsx50("p", { className: "mt-0.5 text-xs text-muted-foreground", children: toast2.description }) : null
3744
+ ] }),
3745
+ toast2.action ? /* @__PURE__ */ jsx50(
3746
+ "button",
3747
+ {
3748
+ type: "button",
3749
+ "data-testid": "toast-action",
3750
+ onClick: () => {
3751
+ if (toast2.leaving) return;
3752
+ toast2.action?.onClick();
3753
+ dismissToast(toast2.id);
3754
+ },
3755
+ className: "shrink-0 rounded-sm px-1.5 py-0.5 font-semibold text-foreground underline-offset-2 hover:bg-accent hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3756
+ children: toast2.action.label
3757
+ }
3758
+ ) : null,
3759
+ /* @__PURE__ */ jsx50(
3760
+ "button",
3761
+ {
3762
+ type: "button",
3763
+ "aria-label": dismissLabel,
3764
+ onClick: () => dismissToast(toast2.id),
3765
+ className: "grid h-5 w-5 shrink-0 place-items-center rounded-sm text-muted-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3766
+ children: /* @__PURE__ */ jsx50(XMarkIcon6, { "aria-hidden": "true", className: "h-3.5 w-3.5" })
3767
+ }
3768
+ ),
3769
+ timed && !toast2.leaving ? /* @__PURE__ */ jsx50(
3770
+ "span",
3771
+ {
3772
+ ref: bar,
3773
+ "aria-hidden": "true",
3774
+ "data-testid": "toast-progress",
3775
+ className: "absolute inset-x-0 bottom-0 h-0.5 origin-left bg-foreground/25 motion-reduce:opacity-0"
3776
+ }
3777
+ ) : null
3778
+ ]
3779
+ }
3780
+ );
3781
+ };
3782
+
3783
+ // src/patterns/date-input/DateInput.tsx
3784
+ import { CalendarDaysIcon } from "@heroicons/react/24/outline";
3785
+ import { forwardRef as forwardRef20, useEffect as useEffect11, useId as useId3, useImperativeHandle, useRef as useRef11, useState as useState18 } from "react";
3786
+
3787
+ // src/lib/dateField.ts
3788
+ var layoutCache = /* @__PURE__ */ new Map();
3789
+ function layoutOf(locale) {
3790
+ const cached = layoutCache.get(locale);
3791
+ if (cached) return cached;
3792
+ const parts = new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric" }).formatToParts(
3793
+ new Date(2001, 1, 3)
3794
+ );
3795
+ const order = parts.filter((p) => p.type === "day" || p.type === "month" || p.type === "year").map((p) => p.type);
3796
+ const separator = parts.find((p) => p.type === "literal")?.value.trim() || ".";
3797
+ const layout = { order, separator };
3798
+ layoutCache.set(locale, layout);
3799
+ return layout;
3800
+ }
3801
+ var WORD = { day: "dd", month: "mm", year: "yyyy" };
3802
+ function datePlaceholder(locale) {
3803
+ const { order, separator } = layoutOf(locale);
3804
+ return order.map((part) => WORD[part]).join(separator);
3805
+ }
3806
+ var pad = (n, width) => String(n).padStart(width, "0");
3807
+ function formatIsoDay(iso, locale) {
3808
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:[T ].*)?$/.exec(iso);
3809
+ if (!match) return "";
3810
+ const { order, separator } = layoutOf(locale);
3811
+ const [, year = "", month = "", day = ""] = match;
3812
+ const value = { year, month, day };
3813
+ return order.map((part) => value[part]).join(separator);
3814
+ }
3815
+ var isRealDay = (year, month, day) => {
3816
+ const date = new Date(year, month - 1, day);
3817
+ return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
3818
+ };
3819
+ var TWO_DIGIT_YEAR_LOOKAHEAD = 20;
3820
+ function expandYear(raw) {
3821
+ const n = Number(raw);
3822
+ if (raw.length > 2) return n;
3823
+ const now = (/* @__PURE__ */ new Date()).getFullYear();
3824
+ const century = Math.floor(now / 100) * 100;
3825
+ return century + n > now + TWO_DIGIT_YEAR_LOOKAHEAD ? century - 100 + n : century + n;
3826
+ }
3827
+ function splitDigits(digits, order) {
3828
+ if (digits.length !== 6 && digits.length !== 8) return null;
3829
+ const width = { day: 2, month: 2, year: digits.length - 4 };
3830
+ const out = [];
3831
+ let at = 0;
3832
+ for (const part of order) {
3833
+ out.push(digits.slice(at, at + width[part]));
3834
+ at += width[part];
3835
+ }
3836
+ return out;
3837
+ }
3838
+ function parseIsoDay(text, locale) {
3839
+ const trimmed = text.trim().replace(/\.$/, "");
3840
+ const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T ].*)?$/.exec(trimmed);
3841
+ let year;
3842
+ let month;
3843
+ let day;
3844
+ if (iso) {
3845
+ [year, month, day] = [Number(iso[1]), Number(iso[2]), Number(iso[3])];
3846
+ } else {
3847
+ const { order } = layoutOf(locale);
3848
+ const typed = /^(\d{1,4})[./\-\s]+(\d{1,2})[./\-\s]+(\d{1,4})$/.exec(trimmed);
3849
+ const raw = typed ? [typed[1] ?? "", typed[2] ?? "", typed[3] ?? ""] : /^\d+$/.test(trimmed) ? splitDigits(trimmed, order) : null;
3850
+ if (!raw) return null;
3851
+ const at = (part) => raw[order.indexOf(part)] ?? "";
3852
+ year = expandYear(at("year"));
3853
+ month = Number(at("month"));
3854
+ day = Number(at("day"));
3855
+ }
3856
+ if (year < 1e3 || year > 9999 || !isRealDay(year, month, day)) return null;
3857
+ return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;
3858
+ }
3859
+ function toLocalIsoDay(d) {
3860
+ return `${pad(d.getFullYear(), 4)}-${pad(d.getMonth() + 1, 2)}-${pad(d.getDate(), 2)}`;
3861
+ }
3862
+ var daysInMonth = (year, month) => new Date(year, month, 0).getDate();
3863
+ function shiftIsoDay(iso, by) {
3864
+ const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
3865
+ if (!match) return iso;
3866
+ const year = Number(match[1]);
3867
+ const month = Number(match[2]);
3868
+ const day = Number(match[3]);
3869
+ const total = year * 12 + (month - 1) + (by.months ?? 0);
3870
+ const nextYear = Math.floor(total / 12);
3871
+ const nextMonth = total % 12 + 1;
3872
+ const moved = new Date(nextYear, nextMonth - 1, Math.min(day, daysInMonth(nextYear, nextMonth)) + (by.days ?? 0));
3873
+ const result = moved.getFullYear();
3874
+ return result < 1e3 || result > 9999 ? iso : toLocalIsoDay(moved);
3875
+ }
3876
+
3877
+ // src/patterns/date-input/NativeDateInput.tsx
3878
+ import { forwardRef as forwardRef19, useEffect as useEffect10, useId as useId2, useRef as useRef10, useState as useState17 } from "react";
3879
+
3880
+ // src/patterns/date-input/shared.tsx
3881
+ import { useSyncExternalStore as useSyncExternalStore2 } from "react";
3882
+ import { jsx as jsx51 } from "react/jsx-runtime";
3883
+ var defaultDateInputLabels = {
3884
+ invalid: "Enter a valid date",
3885
+ outOfRange: "Date is out of range",
3886
+ openCalendar: "Choose date"
3887
+ };
3888
+ var toDay = (iso) => iso?.slice(0, 10) ?? "";
3889
+ var COARSE = "(pointer: coarse)";
3890
+ var subscribeCoarse = (listener) => {
3891
+ const mq = typeof window !== "undefined" ? window.matchMedia?.(COARSE) : void 0;
3892
+ mq?.addEventListener("change", listener);
3893
+ return () => mq?.removeEventListener("change", listener);
3894
+ };
3895
+ var isCoarse = () => typeof window !== "undefined" && !!window.matchMedia?.(COARSE).matches;
3896
+ var useCoarsePointer = () => useSyncExternalStore2(subscribeCoarse, isCoarse, () => false);
3897
+ var errorMessage = (error, l) => error === "invalid" ? l.invalid : error === "range" ? l.outOfRange : "";
3898
+ var DateInputMessage = ({ id, message }) => /* @__PURE__ */ jsx51("p", { id, role: "alert", className: "mt-1 text-xs text-destructive", children: message });
3899
+ var invalidClass = "aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive";
3900
+
3901
+ // src/patterns/date-input/NativeDateInput.tsx
3902
+ import { jsx as jsx52, jsxs as jsxs33 } from "react/jsx-runtime";
3903
+ var NativeDateInput = forwardRef19(
3904
+ ({
3905
+ value,
3906
+ onValueChange,
3907
+ onInvalidChange,
3908
+ locale: _locale,
3909
+ native: _native,
3910
+ min,
3911
+ max,
3912
+ showError = true,
3913
+ labels,
3914
+ onBlur,
3915
+ placeholder: _placeholder,
3916
+ className,
3917
+ name,
3918
+ ...props
3919
+ }, ref) => {
3920
+ const l = { ...defaultDateInputLabels, ...labels };
3921
+ const minDay = toDay(min);
3922
+ const maxDay = toDay(max);
3923
+ const [raw, setRaw] = useState17(toDay(value));
3924
+ const [error, setErrorState] = useState17(null);
3925
+ const errorRef = useRef10(null);
3926
+ const messageId = useId2();
3927
+ const setError = (next) => {
3928
+ if (errorRef.current === next) return;
3929
+ errorRef.current = next;
3930
+ setErrorState(next);
3931
+ onInvalidChange?.(next != null, next ?? void 0);
3932
+ };
3933
+ useEffect10(() => {
3934
+ setRaw(toDay(value));
3935
+ setError(null);
3936
+ }, [value]);
3937
+ const message = errorMessage(error, l);
3938
+ const showMessage = showError && message !== "";
3939
+ const describedBy = [props["aria-describedby"], showMessage ? messageId : void 0].filter(Boolean).join(" ") || void 0;
3940
+ return /* @__PURE__ */ jsxs33("div", { className, children: [
3941
+ /* @__PURE__ */ jsx52(
3942
+ Input,
3943
+ {
3944
+ ref,
3945
+ ...props,
3946
+ className: invalidClass,
3947
+ type: "date",
3948
+ min: minDay || void 0,
3949
+ max: maxDay || void 0,
3950
+ value: raw,
3951
+ "aria-invalid": error != null || props["aria-invalid"] || void 0,
3952
+ "aria-describedby": describedBy,
3953
+ onChange: (e) => {
3954
+ const el = e.currentTarget;
3955
+ const next = el.value;
3956
+ setRaw(next);
3957
+ if (el.validity.badInput) {
3958
+ setError(null);
3959
+ return;
3960
+ }
3961
+ if (next && (minDay && next < minDay || maxDay && next > maxDay)) {
3962
+ setError("range");
3963
+ return;
3964
+ }
3965
+ setError(null);
3966
+ if (next !== toDay(value)) onValueChange(next);
3967
+ },
3968
+ onBlur: (e) => {
3969
+ if (e.currentTarget.validity.badInput) setError("invalid");
3970
+ onBlur?.(e);
3971
+ }
3972
+ }
3973
+ ),
3974
+ showMessage ? /* @__PURE__ */ jsx52(DateInputMessage, { id: messageId, message }) : null,
3975
+ name ? /* @__PURE__ */ jsx52("input", { type: "hidden", name, value: toDay(value) }) : null
3976
+ ] });
3977
+ }
3978
+ );
3979
+ NativeDateInput.displayName = "NativeDateInput";
3980
+
3981
+ // src/patterns/date-input/DateInput.tsx
3982
+ import { Fragment as Fragment6, jsx as jsx53, jsxs as jsxs34 } from "react/jsx-runtime";
3983
+ function check(text, locale, min, max) {
3984
+ if (!text.trim()) return null;
3985
+ const iso = parseIsoDay(text, locale);
3986
+ if (iso == null) return "invalid";
3987
+ if (min && iso < min || max && iso > max) return "range";
3988
+ return null;
3989
+ }
3990
+ var DateInput = forwardRef20((props, ref) => {
3991
+ const coarse = useCoarsePointer();
3992
+ const useNative = props.native === "always" || props.native !== "never" && coarse;
3993
+ return useNative ? /* @__PURE__ */ jsx53(NativeDateInput, { ref, ...props }) : /* @__PURE__ */ jsx53(TypedDateInput, { ref, ...props });
3994
+ });
3995
+ DateInput.displayName = "DateInput";
3996
+ var TypedDateInput = forwardRef20(
3997
+ ({
3998
+ value,
3999
+ onValueChange,
4000
+ onInvalidChange,
4001
+ locale = "de-CH",
4002
+ min,
4003
+ max,
4004
+ showError = true,
4005
+ labels,
4006
+ onBlur,
4007
+ onKeyDown,
4008
+ onFocus,
4009
+ placeholder,
4010
+ className,
4011
+ name,
4012
+ disabled,
4013
+ readOnly,
4014
+ native: _native,
4015
+ ...props
4016
+ }, ref) => {
4017
+ const l = { ...defaultDateInputLabels, ...labels };
4018
+ const minDay = toDay(min);
4019
+ const maxDay = toDay(max);
4020
+ const [text, setText] = useState18(() => formatIsoDay(value, locale));
4021
+ const [error, setErrorState] = useState18(null);
4022
+ const dirty = useRef11(false);
4023
+ const focused = useRef11(false);
4024
+ const inputRef = useRef11(null);
4025
+ const pickerRef = useRef11(null);
4026
+ const messageId = useId3();
4027
+ useImperativeHandle(ref, () => inputRef.current);
4028
+ const errorRef = useRef11(null);
4029
+ const setError = (next) => {
4030
+ if (errorRef.current === next) return;
4031
+ errorRef.current = next;
4032
+ setErrorState(next);
4033
+ onInvalidChange?.(next != null, next ?? void 0);
4034
+ };
4035
+ useEffect11(() => {
4036
+ if (focused.current && dirty.current) return;
4037
+ setText(formatIsoDay(value, locale));
4038
+ setError(null);
4039
+ }, [value, locale]);
4040
+ const live = check(text, locale, minDay, maxDay);
4041
+ const liveMessage = live === "invalid" ? l.invalid : live === "range" ? l.outOfRange : "";
4042
+ useEffect11(() => {
4043
+ inputRef.current?.setCustomValidity(liveMessage);
4044
+ }, [liveMessage]);
4045
+ const commitIso = (iso) => {
4046
+ setText(formatIsoDay(iso, locale));
4047
+ setError(null);
4048
+ if (iso !== value.slice(0, 10)) onValueChange(iso);
4049
+ };
4050
+ const commit = () => {
4051
+ if (!dirty.current) return;
4052
+ dirty.current = false;
4053
+ if (!text.trim()) {
4054
+ setError(null);
4055
+ if (value) onValueChange("");
4056
+ return;
4057
+ }
4058
+ const problem = check(text, locale, minDay, maxDay);
4059
+ setError(problem);
4060
+ if (problem) return;
4061
+ commitIso(parseIsoDay(text, locale));
4062
+ };
4063
+ const step = (key, shift) => {
4064
+ const by = key === "ArrowUp" ? { days: 1 } : key === "ArrowDown" ? { days: -1 } : key === "PageUp" ? { months: shift ? 12 : 1 } : key === "PageDown" ? { months: shift ? -12 : -1 } : null;
4065
+ if (!by) return null;
4066
+ const base = parseIsoDay(text, locale) ?? (toDay(value) || toLocalIsoDay(/* @__PURE__ */ new Date()));
4067
+ let next = text.trim() || value ? shiftIsoDay(base, by) : base;
4068
+ if (minDay && next < minDay) next = minDay;
4069
+ if (maxDay && next > maxDay) next = maxDay;
4070
+ return next;
4071
+ };
4072
+ const openPicker = () => {
4073
+ const el = pickerRef.current;
4074
+ if (!el) return;
4075
+ try {
4076
+ el.showPicker();
4077
+ } catch {
4078
+ el.click();
4079
+ }
4080
+ };
4081
+ const message = errorMessage(error, l);
4082
+ const showMessage = showError && message !== "";
4083
+ const pickable = !disabled && !readOnly;
4084
+ const describedBy = [props["aria-describedby"], showMessage ? messageId : void 0].filter(Boolean).join(" ") || void 0;
4085
+ return /* @__PURE__ */ jsxs34("div", { className, children: [
4086
+ /* @__PURE__ */ jsxs34("div", { className: "relative", children: [
4087
+ /* @__PURE__ */ jsx53(
4088
+ Input,
4089
+ {
4090
+ ref: inputRef,
4091
+ ...props,
4092
+ disabled,
4093
+ readOnly,
4094
+ className: cn(invalidClass, pickable && "pr-9"),
4095
+ type: "text",
4096
+ autoComplete: "off",
4097
+ value: text,
4098
+ placeholder: placeholder ?? datePlaceholder(locale),
4099
+ "aria-invalid": error != null || props["aria-invalid"] || void 0,
4100
+ "aria-describedby": describedBy,
4101
+ onChange: (e) => {
4102
+ dirty.current = true;
4103
+ setText(e.target.value);
4104
+ setError(null);
4105
+ },
4106
+ onFocus: (e) => {
4107
+ focused.current = true;
4108
+ onFocus?.(e);
4109
+ },
4110
+ onBlur: (e) => {
4111
+ focused.current = false;
4112
+ commit();
4113
+ onBlur?.(e);
4114
+ },
4115
+ onKeyDown: (e) => {
4116
+ onKeyDown?.(e);
4117
+ if (e.defaultPrevented) return;
4118
+ if (e.key === "Enter") commit();
4119
+ else if (!readOnly && !disabled) {
4120
+ if (e.key === "ArrowDown" && e.altKey) {
4121
+ e.preventDefault();
4122
+ openPicker();
4123
+ } else if (!e.altKey && !e.ctrlKey && !e.metaKey) {
4124
+ const next = step(e.key, e.shiftKey);
4125
+ if (next) {
4126
+ e.preventDefault();
4127
+ dirty.current = false;
4128
+ commitIso(next);
4129
+ }
4130
+ }
4131
+ }
4132
+ }
4133
+ }
4134
+ ),
4135
+ pickable ? /* @__PURE__ */ jsxs34(Fragment6, { children: [
4136
+ /* @__PURE__ */ jsx53(
4137
+ "button",
4138
+ {
4139
+ type: "button",
4140
+ "aria-label": l.openCalendar,
4141
+ onClick: openPicker,
4142
+ className: "absolute inset-y-0 right-0 grid w-9 place-items-center rounded-r-md text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4143
+ children: /* @__PURE__ */ jsx53(CalendarDaysIcon, { "aria-hidden": "true", className: "h-4 w-4" })
4144
+ }
4145
+ ),
4146
+ /* @__PURE__ */ jsx53(
4147
+ "input",
4148
+ {
4149
+ ref: pickerRef,
4150
+ type: "date",
4151
+ tabIndex: -1,
4152
+ "aria-hidden": "true",
4153
+ form: "bmsui-date-input-picker",
4154
+ "data-testid": "date-input-picker",
4155
+ min: minDay || void 0,
4156
+ max: maxDay || void 0,
4157
+ value: toDay(value),
4158
+ onChange: (e) => {
4159
+ const iso = e.target.value;
4160
+ dirty.current = false;
4161
+ if (iso) commitIso(iso);
4162
+ else {
4163
+ setText("");
4164
+ setError(null);
4165
+ if (value) onValueChange("");
4166
+ }
4167
+ },
4168
+ className: "pointer-events-none absolute inset-0 h-full w-full opacity-0"
4169
+ }
4170
+ )
4171
+ ] }) : null
4172
+ ] }),
4173
+ showMessage ? /* @__PURE__ */ jsx53(DateInputMessage, { id: messageId, message }) : null,
4174
+ name ? /* @__PURE__ */ jsx53("input", { type: "hidden", name, value: toDay(value) }) : null
4175
+ ] });
4176
+ }
4177
+ );
4178
+ TypedDateInput.displayName = "TypedDateInput";
4179
+
3474
4180
  // src/patterns/animated-brand/AnimatedBrand.tsx
3475
4181
  import {
3476
- useEffect as useEffect9,
3477
- useRef as useRef9,
3478
- useState as useState16
4182
+ useEffect as useEffect12,
4183
+ useRef as useRef12,
4184
+ useState as useState19
3479
4185
  } from "react";
3480
4186
 
3481
4187
  // src/patterns/animated-brand/brandMotion.ts
@@ -3869,7 +4575,7 @@ function playBrandMotion(p, name, onComplete) {
3869
4575
  }
3870
4576
 
3871
4577
  // src/patterns/animated-brand/AnimatedBrand.tsx
3872
- import { jsx as jsx49, jsxs as jsxs31 } from "react/jsx-runtime";
4578
+ import { jsx as jsx54, jsxs as jsxs35 } from "react/jsx-runtime";
3873
4579
  var absolute = {
3874
4580
  position: "absolute",
3875
4581
  borderRadius: "50%",
@@ -3900,17 +4606,17 @@ function AnimatedBrand({
3900
4606
  className,
3901
4607
  style
3902
4608
  }) {
3903
- const ref = useRef9(null);
3904
- const completion = useRef9(onComplete);
3905
- const [segmented, setSegmented] = useState16(null);
3906
- useEffect9(() => {
4609
+ const ref = useRef12(null);
4610
+ const completion = useRef12(onComplete);
4611
+ const [segmented, setSegmented] = useState19(null);
4612
+ useEffect12(() => {
3907
4613
  const letters2 = typeof Intl.Segmenter === "function" ? Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(product), ({ segment }) => segment) : product ? [product] : [];
3908
4614
  setSegmented({ product, letters: letters2 });
3909
4615
  }, [product]);
3910
- useEffect9(() => {
4616
+ useEffect12(() => {
3911
4617
  completion.current = onComplete;
3912
4618
  }, [onComplete]);
3913
- useEffect9(() => {
4619
+ useEffect12(() => {
3914
4620
  const element = ref.current;
3915
4621
  if (!element || !autoPlay || segmented?.product !== product) return;
3916
4622
  let disposed = false;
@@ -3957,13 +4663,13 @@ function AnimatedBrand({
3957
4663
  color: textColor,
3958
4664
  ...style
3959
4665
  };
3960
- return /* @__PURE__ */ jsx49(
4666
+ return /* @__PURE__ */ jsx54(
3961
4667
  "span",
3962
4668
  {
3963
4669
  ref,
3964
4670
  className,
3965
4671
  style: wrapperStyle,
3966
- children: /* @__PURE__ */ jsxs31(
4672
+ children: /* @__PURE__ */ jsxs35(
3967
4673
  "span",
3968
4674
  {
3969
4675
  "data-logo-part": "logo",
@@ -3981,7 +4687,7 @@ function AnimatedBrand({
3981
4687
  userSelect: "none"
3982
4688
  },
3983
4689
  children: [
3984
- /* @__PURE__ */ jsxs31(
4690
+ /* @__PURE__ */ jsxs35(
3985
4691
  "span",
3986
4692
  {
3987
4693
  "data-logo-part": "mark",
@@ -3995,7 +4701,7 @@ function AnimatedBrand({
3995
4701
  marginRight: tail ? ".04em" : 0
3996
4702
  },
3997
4703
  children: [
3998
- /* @__PURE__ */ jsx49(
4704
+ /* @__PURE__ */ jsx54(
3999
4705
  "span",
4000
4706
  {
4001
4707
  "data-logo-part": "ring",
@@ -4007,7 +4713,7 @@ function AnimatedBrand({
4007
4713
  }
4008
4714
  }
4009
4715
  ),
4010
- /* @__PURE__ */ jsx49(
4716
+ /* @__PURE__ */ jsx54(
4011
4717
  "span",
4012
4718
  {
4013
4719
  "data-logo-part": "core",
@@ -4020,7 +4726,7 @@ function AnimatedBrand({
4020
4726
  }
4021
4727
  }
4022
4728
  ),
4023
- /* @__PURE__ */ jsx49(
4729
+ /* @__PURE__ */ jsx54(
4024
4730
  "span",
4025
4731
  {
4026
4732
  "data-logo-part": "trail",
@@ -4038,7 +4744,7 @@ function AnimatedBrand({
4038
4744
  }
4039
4745
  }
4040
4746
  ),
4041
- [0, 1].map((id) => /* @__PURE__ */ jsx49(
4747
+ [0, 1].map((id) => /* @__PURE__ */ jsx54(
4042
4748
  "span",
4043
4749
  {
4044
4750
  "data-logo-part": "echo",
@@ -4051,7 +4757,7 @@ function AnimatedBrand({
4051
4757
  },
4052
4758
  id
4053
4759
  )),
4054
- Array.from({ length: 8 }, (_, id) => /* @__PURE__ */ jsx49(
4760
+ Array.from({ length: 8 }, (_, id) => /* @__PURE__ */ jsx54(
4055
4761
  "span",
4056
4762
  {
4057
4763
  "data-logo-part": "particle",
@@ -4066,7 +4772,7 @@ function AnimatedBrand({
4066
4772
  },
4067
4773
  id
4068
4774
  )),
4069
- /* @__PURE__ */ jsx49(
4775
+ /* @__PURE__ */ jsx54(
4070
4776
  "span",
4071
4777
  {
4072
4778
  "data-logo-part": "bead",
@@ -4076,7 +4782,7 @@ function AnimatedBrand({
4076
4782
  ]
4077
4783
  }
4078
4784
  ),
4079
- /* @__PURE__ */ jsxs31(
4785
+ /* @__PURE__ */ jsxs35(
4080
4786
  "span",
4081
4787
  {
4082
4788
  "data-logo-part": "tail",
@@ -4087,7 +4793,7 @@ function AnimatedBrand({
4087
4793
  whiteSpace: "pre"
4088
4794
  },
4089
4795
  children: [
4090
- letters.map((letter, index) => /* @__PURE__ */ jsx49(
4796
+ letters.map((letter, index) => /* @__PURE__ */ jsx54(
4091
4797
  "span",
4092
4798
  {
4093
4799
  "data-brand-letter": "",
@@ -4096,7 +4802,7 @@ function AnimatedBrand({
4096
4802
  },
4097
4803
  `${index}-${letter}`
4098
4804
  )),
4099
- /* @__PURE__ */ jsx49(
4805
+ /* @__PURE__ */ jsx54(
4100
4806
  "span",
4101
4807
  {
4102
4808
  "data-brand-baseline": "",
@@ -4136,6 +4842,7 @@ export {
4136
4842
  Checkbox,
4137
4843
  Combobox,
4138
4844
  ConfirmDialog,
4845
+ DateInput,
4139
4846
  Dialog,
4140
4847
  DialogActions,
4141
4848
  DialogBody,
@@ -4161,7 +4868,9 @@ export {
4161
4868
  DropdownMenuSubContent,
4162
4869
  DropdownMenuSubTrigger,
4163
4870
  DropdownMenuTrigger,
4871
+ EmptyState,
4164
4872
  ErrorState,
4873
+ FilteredEmptyState,
4165
4874
  FormField,
4166
4875
  FormModal,
4167
4876
  InlineError,
@@ -4171,6 +4880,7 @@ export {
4171
4880
  LoadingOverlay,
4172
4881
  LoadingSpinner,
4173
4882
  LoadingState,
4883
+ MAX_TOASTS,
4174
4884
  Modal,
4175
4885
  NavGroup,
4176
4886
  NavItem,
@@ -4224,6 +4934,7 @@ export {
4224
4934
  TabsTrigger,
4225
4935
  TagCombobox,
4226
4936
  Textarea,
4937
+ Toaster,
4227
4938
  Tooltip,
4228
4939
  TooltipContent,
4229
4940
  TooltipProvider,
@@ -4233,13 +4944,23 @@ export {
4233
4944
  badgeVariants,
4234
4945
  buttonVariants,
4235
4946
  cn,
4947
+ datePlaceholder,
4948
+ defaultDateInputLabels,
4236
4949
  defaultErrorStateLabels,
4950
+ defaultFilteredEmptyStateLabels,
4951
+ defaultToasterLabels,
4237
4952
  describeSpeechError,
4953
+ formatIsoDay,
4238
4954
  getConfirmButtonPlacement,
4239
4955
  isWindowsPlatform,
4240
4956
  loadingSpinnerIconVariants,
4957
+ parseIsoDay,
4958
+ resetToasts,
4241
4959
  sheetVariants,
4960
+ shiftIsoDay,
4242
4961
  toErrorInfo,
4962
+ toLocalIsoDay,
4963
+ toast,
4243
4964
  useDelayedFlag,
4244
4965
  useMediaQuery,
4245
4966
  useSidebarCollapsed,