@ews-admin/global-design-system 1.30.0 → 1.31.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.
Files changed (31) hide show
  1. package/dist/components/LockScreen/LockScreen.d.ts +5 -4
  2. package/dist/components/LockScreen/LockScreen.d.ts.map +1 -1
  3. package/dist/components/LockScreen/LockScreen.types.d.ts +8 -0
  4. package/dist/components/LockScreen/LockScreen.types.d.ts.map +1 -1
  5. package/dist/components/PinCodeInput/PinCodeInput.d.ts +11 -0
  6. package/dist/components/PinCodeInput/PinCodeInput.d.ts.map +1 -0
  7. package/dist/components/PinCodeInput/PinCodeInput.types.d.ts +26 -0
  8. package/dist/components/PinCodeInput/PinCodeInput.types.d.ts.map +1 -0
  9. package/dist/components/PinCodeInput/index.d.ts +3 -0
  10. package/dist/components/PinCodeInput/index.d.ts.map +1 -0
  11. package/dist/hooks/index.d.ts +1 -0
  12. package/dist/hooks/index.d.ts.map +1 -1
  13. package/dist/hooks/useMediaQuery.d.ts +7 -0
  14. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  15. package/dist/index.css +1 -1
  16. package/dist/index.d.ts +55 -6
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.esm.css +1 -1
  19. package/dist/index.esm.js +191 -12
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +192 -11
  22. package/dist/index.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/LockScreen/LockScreen.tsx +215 -89
  25. package/src/components/LockScreen/LockScreen.types.ts +8 -0
  26. package/src/components/PinCodeInput/PinCodeInput.tsx +184 -0
  27. package/src/components/PinCodeInput/PinCodeInput.types.ts +25 -0
  28. package/src/components/PinCodeInput/index.ts +2 -0
  29. package/src/hooks/index.ts +1 -0
  30. package/src/hooks/useMediaQuery.ts +31 -0
  31. package/src/index.ts +4 -0
package/dist/index.js CHANGED
@@ -33083,6 +33083,33 @@ function useDebouncedCallback(callback, delay) {
33083
33083
  return debouncedCallback;
33084
33084
  }
33085
33085
 
33086
+ const matches = (query, fallback) => typeof window !== "undefined" && typeof window.matchMedia === "function"
33087
+ ? window.matchMedia(query).matches
33088
+ : fallback;
33089
+ /**
33090
+ * Whether a CSS media query matches, updated when it changes. Returns
33091
+ * `defaultValue` where `matchMedia` does not exist (server rendering, old
33092
+ * test environments).
33093
+ */
33094
+ const useMediaQuery = (query, defaultValue = false) => {
33095
+ const [value, setValue] = React.useState(() => matches(query, defaultValue));
33096
+ React.useEffect(() => {
33097
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
33098
+ return;
33099
+ const list = window.matchMedia(query);
33100
+ const onChange = () => setValue(list.matches);
33101
+ onChange();
33102
+ // Safari < 14 only has addListener.
33103
+ if (list.addEventListener) {
33104
+ list.addEventListener("change", onChange);
33105
+ return () => list.removeEventListener("change", onChange);
33106
+ }
33107
+ list.addListener(onChange);
33108
+ return () => list.removeListener(onChange);
33109
+ }, [query]);
33110
+ return value;
33111
+ };
33112
+
33086
33113
  const DEFAULT_TIMEOUT_MS = 180000;
33087
33114
  const DEFAULT_STORAGE_KEY = "ews-idle-lock";
33088
33115
  const ACTIVITY_WRITE_INTERVAL_MS = 5000;
@@ -35502,6 +35529,131 @@ const PinPad = ({ length = 4, onComplete, onChange, disabled = false, invalid =
35502
35529
  : "bg-ews-primary-light", disabled && index < value.length && "animate-pulse") }, index))) }), jsxRuntime.jsx("p", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: progressLabel(value.length, length) }), jsxRuntime.jsxs("div", { dir: "ltr", role: "group", "aria-label": keypadLabel, className: "grid grid-cols-3 gap-x-8 gap-y-4", children: [DIGIT_ROWS.map((digit) => (jsxRuntime.jsx("button", { type: "button", className: keyClass, disabled: disabled, onClick: () => press(digit), children: digit }, digit))), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("button", { type: "button", className: keyClass, disabled: disabled, onClick: () => press("0"), children: "0" }), jsxRuntime.jsx("button", { type: "button", className: cn(keyClass, "text-ews-gray-700"), disabled: disabled || value.length === 0, "aria-label": eraseLabel, onClick: erase, children: jsxRuntime.jsx(Delete, { className: "h-7 w-7", strokeWidth: 1.5, "aria-hidden": "true" }) })] })] }));
35503
35530
  };
35504
35531
 
35532
+ const MASK = "•";
35533
+ const onlyDigits = (text) => text.replace(/\D/g, "");
35534
+ /**
35535
+ * Row of one-digit boxes for a PIN or a one-time code (desktop keyboards):
35536
+ * typing moves to the next box, Backspace goes back, arrows move, pasting a
35537
+ * code fills the boxes and `onComplete` fires once every box is filled.
35538
+ */
35539
+ const PinCodeInput = ({ length = 4, onComplete, onChange, masked = true, autoComplete = "off", autoFocus = true, disabled = false, invalid = false, resetKey, digitLabel, groupLabel, className, }) => {
35540
+ const empty = React.useCallback(() => Array.from({ length }, () => ""), [length]);
35541
+ const [digits, setDigits] = React.useState(empty);
35542
+ const digitsRef = React.useRef(digits);
35543
+ const inputs = React.useRef([]);
35544
+ const focusBox = (index) => {
35545
+ const input = inputs.current[Math.max(0, Math.min(index, length - 1))];
35546
+ input?.focus();
35547
+ input?.select();
35548
+ };
35549
+ const update = (next) => {
35550
+ digitsRef.current = next;
35551
+ setDigits(next);
35552
+ const value = next.join("");
35553
+ onChange?.(value);
35554
+ if (value.length === length && next.every(Boolean))
35555
+ onComplete(value);
35556
+ };
35557
+ /** Writes `chars` from box `start` on and focuses the box after them. */
35558
+ const fill = (start, chars) => {
35559
+ const next = [...digitsRef.current];
35560
+ const written = chars.slice(0, length - start).split("");
35561
+ written.forEach((char, offset) => {
35562
+ next[start + offset] = char;
35563
+ });
35564
+ focusBox(start + written.length);
35565
+ update(next);
35566
+ };
35567
+ // Clear the boxes when the caller asks for it.
35568
+ React.useEffect(() => {
35569
+ const cleared = empty();
35570
+ digitsRef.current = cleared;
35571
+ setDigits(cleared);
35572
+ }, [resetKey, empty]);
35573
+ // Focus the first empty box on mount, after a reset and once re-enabled.
35574
+ React.useEffect(() => {
35575
+ if (!autoFocus || disabled)
35576
+ return;
35577
+ const firstEmpty = digitsRef.current.findIndex((digit) => !digit);
35578
+ focusBox(firstEmpty === -1 ? length - 1 : firstEmpty);
35579
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35580
+ }, [autoFocus, disabled, resetKey, length]);
35581
+ const handleChange = (index, event) => {
35582
+ if (disabled)
35583
+ return;
35584
+ const { value, selectionStart } = event.target;
35585
+ const typed = onlyDigits(value);
35586
+ if (!typed) {
35587
+ const next = [...digitsRef.current];
35588
+ next[index] = "";
35589
+ update(next);
35590
+ return;
35591
+ }
35592
+ // A digit typed next to the one already in the box: keep the new one.
35593
+ if (digitsRef.current[index] && value.length === 2) {
35594
+ const inserted = value.charAt((selectionStart ?? value.length) - 1);
35595
+ if (/\d/.test(inserted)) {
35596
+ fill(index, inserted);
35597
+ return;
35598
+ }
35599
+ }
35600
+ // One digit, or a whole code (browser autofill of the one-time code).
35601
+ fill(typed.length >= length ? 0 : index, typed);
35602
+ };
35603
+ const handleKeyDown = (index, event) => {
35604
+ if (disabled)
35605
+ return;
35606
+ switch (event.key) {
35607
+ case "Backspace": {
35608
+ event.preventDefault();
35609
+ const next = [...digitsRef.current];
35610
+ const target = next[index] ? index : Math.max(0, index - 1);
35611
+ next[target] = "";
35612
+ focusBox(target);
35613
+ update(next);
35614
+ return;
35615
+ }
35616
+ case "Delete": {
35617
+ event.preventDefault();
35618
+ const next = [...digitsRef.current];
35619
+ next[index] = "";
35620
+ update(next);
35621
+ return;
35622
+ }
35623
+ case "ArrowLeft":
35624
+ event.preventDefault();
35625
+ focusBox(index - 1);
35626
+ return;
35627
+ case "ArrowRight":
35628
+ event.preventDefault();
35629
+ focusBox(index + 1);
35630
+ return;
35631
+ case "Home":
35632
+ event.preventDefault();
35633
+ focusBox(0);
35634
+ return;
35635
+ case "End":
35636
+ event.preventDefault();
35637
+ focusBox(length - 1);
35638
+ return;
35639
+ }
35640
+ };
35641
+ const handlePaste = (index, event) => {
35642
+ event.preventDefault();
35643
+ if (disabled)
35644
+ return;
35645
+ const pasted = onlyDigits(event.clipboardData.getData("text"));
35646
+ if (!pasted)
35647
+ return;
35648
+ fill(pasted.length >= length ? 0 : index, pasted);
35649
+ };
35650
+ return (
35651
+ // Codes stay left-to-right in RTL locales.
35652
+ jsxRuntime.jsx("div", { dir: "ltr", role: "group", "aria-label": groupLabel, "data-testid": "pin-code-input", className: cn("flex justify-center gap-3", invalid && "animate-pin-shake", className), children: digits.map((digit, index) => (jsxRuntime.jsx("input", { ref: (element) => {
35653
+ inputs.current[index] = element;
35654
+ }, type: "text", inputMode: "numeric", pattern: "[0-9]*", autoComplete: index === 0 ? autoComplete : "off", autoCorrect: "off", spellCheck: false, value: digit && masked ? MASK : digit, disabled: disabled, "aria-label": digitLabel(index + 1, length), "aria-invalid": invalid || undefined, "data-filled": !!digit, onChange: (event) => handleChange(index, event), onKeyDown: (event) => handleKeyDown(index, event), onPaste: (event) => handlePaste(index, event), onFocus: (event) => event.target.select(), className: cn("h-14 w-12 rounded-lg border bg-white text-center text-2xl font-medium text-ews-gray-900 outline-none", "transition-colors focus:border-ews-primary focus:ring-2 focus:ring-ews-primary", "disabled:cursor-not-allowed disabled:opacity-60", invalid ? "border-ews-error" : digit ? "border-ews-primary" : "border-ews-gray-300", disabled && digit && "animate-pulse") }, index))) }));
35655
+ };
35656
+
35505
35657
  const FIRST_STEP = {
35506
35658
  create: "new",
35507
35659
  unlock: "unlock",
@@ -35522,14 +35674,19 @@ const titleOf = (mode, step, texts) => {
35522
35674
  return texts.confirmTitle;
35523
35675
  }
35524
35676
  };
35677
+ /** Same breakpoint as Tailwind's `sm`. */
35678
+ const DESKTOP_QUERY = "(min-width: 640px)";
35679
+ /** Open lock screens, oldest first; only the last one traps focus. */
35680
+ const trapStack = [];
35525
35681
  const FOCUSABLE = 'button:not([disabled]), input:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
35526
35682
  /**
35527
35683
  * Blocking PIN screen: creates, checks, changes or resets (with a one-time
35528
- * code) the user's secret code. Full screen on mobile, centered card on a grey
35529
- * backdrop from 640px. Submit handlers return promises: a rejection clears the
35530
- * code and goes back to the first step, the app shows its message in `error`.
35684
+ * code) the user's secret code. Below 640px: full screen with an on-screen
35685
+ * keypad. From 640px: compact dialog on a grey backdrop with one box per digit,
35686
+ * typed on the keyboard. Submit handlers return promises: a rejection clears
35687
+ * the code and goes back to the first step, the app shows its message in `error`.
35531
35688
  */
35532
- const LockScreen = ({ mode, texts, error, busy = false, onCreate, onUnlock, onChangePin, onReset, onForgot, onCancel, pinLength = 4, otpLength = 6, logo, className, }) => {
35689
+ const LockScreen = ({ mode, texts, error, busy = false, onCreate, onUnlock, onChangePin, onReset, onForgot, onCancel, onSignOut, subtitle, pinLength = 4, otpLength = 6, logo, className, }) => {
35533
35690
  const [step, setStep] = React.useState(FIRST_STEP[mode]);
35534
35691
  const [firstValue, setFirstValue] = React.useState(""); // current PIN or one-time code
35535
35692
  const [newPin, setNewPin] = React.useState("");
@@ -35540,6 +35697,7 @@ const LockScreen = ({ mode, texts, error, busy = false, onCreate, onUnlock, onCh
35540
35697
  const [resetKey, setResetKey] = React.useState(0);
35541
35698
  const panelRef = React.useRef(null);
35542
35699
  const mounted = React.useRef(true);
35700
+ const isDesktop = useMediaQuery(DESKTOP_QUERY);
35543
35701
  const restart = React.useCallback((failed) => {
35544
35702
  setStep(FIRST_STEP[mode]);
35545
35703
  setFirstValue("");
@@ -35558,22 +35716,33 @@ const LockScreen = ({ mode, texts, error, busy = false, onCreate, onUnlock, onCh
35558
35716
  mounted.current = false;
35559
35717
  };
35560
35718
  }, []);
35561
- // Block page scroll and keep focus inside the screen.
35719
+ // Block page scroll and keep focus inside the screen. Only the most recently
35720
+ // mounted lock screen traps focus: two traps pulling focus back to their own
35721
+ // panel would loop forever (e.g. a Storybook docs page rendering every story).
35562
35722
  React.useEffect(() => {
35723
+ const token = {};
35724
+ trapStack.push(token);
35563
35725
  const previousOverflow = document.body.style.overflow;
35564
35726
  document.body.style.overflow = "hidden";
35565
35727
  const previousFocus = document.activeElement;
35566
- panelRef.current?.focus();
35728
+ // The desktop code boxes focus themselves first.
35729
+ if (!panelRef.current?.contains(document.activeElement))
35730
+ panelRef.current?.focus();
35567
35731
  const keepFocus = (event) => {
35732
+ if (trapStack[trapStack.length - 1] !== token)
35733
+ return;
35568
35734
  const panel = panelRef.current;
35569
35735
  if (panel && !panel.contains(event.target))
35570
35736
  panel.focus();
35571
35737
  };
35572
35738
  document.addEventListener("focusin", keepFocus);
35573
35739
  return () => {
35740
+ const wasTop = trapStack[trapStack.length - 1] === token;
35741
+ trapStack.splice(trapStack.indexOf(token), 1);
35574
35742
  document.body.style.overflow = previousOverflow;
35575
35743
  document.removeEventListener("focusin", keepFocus);
35576
- previousFocus?.focus?.();
35744
+ if (wasTop)
35745
+ previousFocus?.focus?.();
35577
35746
  };
35578
35747
  }, []);
35579
35748
  const trapTab = (event) => {
@@ -35654,16 +35823,26 @@ const LockScreen = ({ mode, texts, error, busy = false, onCreate, onUnlock, onCh
35654
35823
  setInvalid(false);
35655
35824
  setStep("new");
35656
35825
  };
35826
+ const completeOtp = (code) => {
35827
+ setLocalError(null);
35828
+ setFirstValue(code);
35829
+ setInvalid(false);
35830
+ setStep("new");
35831
+ };
35657
35832
  const isBusy = busy || pending;
35658
35833
  const shownError = localError ?? error;
35659
35834
  const showForgot = !!onForgot &&
35660
35835
  !!texts.forgot &&
35661
35836
  (step === "unlock" || step === "current");
35662
35837
  const title = titleOf(mode, step, texts);
35663
- return (jsxRuntime.jsx("div", { className: cn("fixed inset-0 z-[1000] flex justify-center overflow-y-auto bg-white", "sm:items-center sm:bg-ews-gray-900/50 sm:backdrop-blur-sm", className), "data-testid": "lock-screen", children: jsxRuntime.jsxs("div", { ref: panelRef, role: "dialog", "aria-modal": "true", "aria-label": texts.dialogLabel, "aria-busy": isBusy, tabIndex: -1, onKeyDown: trapTab, className: cn("flex min-h-full w-full flex-col items-center px-6 pb-8 outline-none", "sm:min-h-0 sm:w-auto sm:min-w-[22rem] sm:rounded-2xl sm:bg-white sm:px-10 sm:py-10 sm:shadow-xl"), children: [jsxRuntime.jsx("div", { className: "flex min-h-[33vh] shrink-0 items-center justify-center sm:min-h-0 sm:pb-8", children: logo ?? jsxRuntime.jsx(Logo, { size: "lg", variant: "normal" }) }), title && (jsxRuntime.jsx("h2", { className: "mb-8 text-center text-lg font-normal text-ews-gray-900", children: title })), step === "otp" ? (jsxRuntime.jsxs("form", { onSubmit: submitOtp, className: "flex w-full max-w-xs flex-col items-center gap-4", children: [texts.otpDescription && (jsxRuntime.jsx("p", { className: "text-center text-sm text-ews-gray-600", children: texts.otpDescription })), jsxRuntime.jsx("label", { className: "sr-only", htmlFor: "lock-screen-otp", children: texts.otpLabel }), jsxRuntime.jsx("input", { id: "lock-screen-otp", dir: "ltr", inputMode: "numeric", autoComplete: "one-time-code", maxLength: otpLength, value: otp, disabled: isBusy, "aria-label": texts.otpLabel, "aria-invalid": invalid || undefined, onChange: (event) => {
35664
- setOtp(event.target.value.replace(/\D/g, "").slice(0, otpLength));
35665
- setInvalid(false);
35666
- }, className: cn("w-full rounded-lg border bg-white px-4 py-3 text-center text-2xl tracking-[0.5em]", "focus:border-ews-primary focus:ring-2 focus:ring-ews-primary", invalid ? "border-ews-error" : "border-ews-gray-300") }), jsxRuntime.jsx(Button, { type: "submit", variant: "ews-primary", fullWidth: true, disabled: otp.length !== otpLength || isBusy, children: texts.otpSubmit })] })) : (jsxRuntime.jsx(PinPad, { length: pinLength, onComplete: handlePin, onChange: () => setInvalid(false), disabled: isBusy, invalid: invalid, resetKey: resetKey, eraseLabel: texts.erase, progressLabel: texts.progress, keypadLabel: texts.keypad }, step)), jsxRuntime.jsx("p", { role: "alert", className: "mt-6 min-h-[1.25rem] text-center text-sm text-ews-error", children: shownError }), jsxRuntime.jsxs("div", { className: "mt-auto flex flex-col items-center gap-3 pt-6 sm:mt-4", children: [showForgot && (jsxRuntime.jsx("button", { type: "button", onClick: onForgot, disabled: isBusy, className: "text-base font-medium text-ews-primary hover:underline focus-visible:underline", children: texts.forgot })), onCancel && texts.cancel && (jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: isBusy, className: "text-sm text-ews-gray-600 hover:underline focus-visible:underline", children: texts.cancel }))] })] }) }));
35838
+ const digitLabel = texts.digit ?? texts.progress;
35839
+ const desktopContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "pb-5", children: logo ?? jsxRuntime.jsx(Logo, { size: "sm", variant: "normal" }) }), title && (jsxRuntime.jsx("h2", { className: "text-center text-xl font-semibold text-ews-gray-900", children: title })), subtitle && (jsxRuntime.jsx("p", { className: "mt-1 text-center text-sm text-ews-gray-600", children: subtitle })), step === "otp" && texts.otpDescription && (jsxRuntime.jsx("p", { className: "mt-2 text-center text-sm text-ews-gray-600", children: texts.otpDescription })), jsxRuntime.jsx("div", { className: "mt-6", children: step === "otp" ? (jsxRuntime.jsx(PinCodeInput, { length: otpLength, masked: false, autoComplete: "one-time-code", onComplete: completeOtp, onChange: () => setInvalid(false), disabled: isBusy, invalid: invalid, resetKey: resetKey, digitLabel: digitLabel, groupLabel: texts.otpLabel ?? title }, step)) : (jsxRuntime.jsx(PinCodeInput, { length: pinLength, onComplete: handlePin, onChange: () => setInvalid(false), disabled: isBusy, invalid: invalid, resetKey: resetKey, digitLabel: digitLabel, groupLabel: title }, step)) }), jsxRuntime.jsx("p", { role: "alert", className: "mt-3 min-h-[1.25rem] text-center text-sm text-ews-error", children: shownError }), (showForgot || (onCancel && texts.cancel) || (onSignOut && texts.signOut)) && (jsxRuntime.jsxs("div", { className: "mt-4 flex w-full flex-col items-center gap-2 border-t border-ews-gray-200 pt-4", children: [showForgot && (jsxRuntime.jsx("button", { type: "button", onClick: onForgot, disabled: isBusy, className: "text-sm font-medium text-ews-primary hover:underline focus-visible:underline", children: texts.forgot })), onCancel && texts.cancel && (jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: isBusy, className: "text-sm text-ews-gray-600 hover:underline focus-visible:underline", children: texts.cancel })), onSignOut && texts.signOut && (jsxRuntime.jsx("button", { type: "button", onClick: onSignOut, disabled: isBusy, className: "text-sm text-ews-gray-600 hover:underline focus-visible:underline", children: texts.signOut }))] }))] }));
35840
+ return (jsxRuntime.jsx("div", { className: cn("fixed inset-0 z-[1000] flex justify-center overflow-y-auto bg-white", "sm:items-center sm:bg-ews-gray-900/50 sm:backdrop-blur-sm", isDesktop && "p-4", className), "data-testid": "lock-screen", "data-layout": isDesktop ? "desktop" : "mobile", children: jsxRuntime.jsx("div", { ref: panelRef, role: "dialog", "aria-modal": "true", "aria-label": texts.dialogLabel, "aria-busy": isBusy, tabIndex: -1, onKeyDown: trapTab, className: isDesktop
35841
+ ? "flex w-full max-w-[400px] flex-col items-center rounded-2xl bg-white px-8 py-8 shadow-xl outline-none"
35842
+ : cn("flex min-h-full w-full flex-col items-center px-6 pb-8 outline-none", "sm:min-h-0 sm:w-auto sm:min-w-[22rem] sm:rounded-2xl sm:bg-white sm:px-10 sm:py-10 sm:shadow-xl"), children: isDesktop ? (desktopContent) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "flex min-h-[33vh] shrink-0 items-center justify-center sm:min-h-0 sm:pb-8", children: logo ?? jsxRuntime.jsx(Logo, { size: "lg", variant: "normal" }) }), title && (jsxRuntime.jsx("h2", { className: "mb-8 text-center text-lg font-normal text-ews-gray-900", children: title })), step === "otp" ? (jsxRuntime.jsxs("form", { onSubmit: submitOtp, className: "flex w-full max-w-xs flex-col items-center gap-4", children: [texts.otpDescription && (jsxRuntime.jsx("p", { className: "text-center text-sm text-ews-gray-600", children: texts.otpDescription })), jsxRuntime.jsx("label", { className: "sr-only", htmlFor: "lock-screen-otp", children: texts.otpLabel }), jsxRuntime.jsx("input", { id: "lock-screen-otp", dir: "ltr", inputMode: "numeric", autoComplete: "one-time-code", maxLength: otpLength, value: otp, disabled: isBusy, "aria-label": texts.otpLabel, "aria-invalid": invalid || undefined, onChange: (event) => {
35843
+ setOtp(event.target.value.replace(/\D/g, "").slice(0, otpLength));
35844
+ setInvalid(false);
35845
+ }, className: cn("w-full rounded-lg border bg-white px-4 py-3 text-center text-2xl tracking-[0.5em]", "focus:border-ews-primary focus:ring-2 focus:ring-ews-primary", invalid ? "border-ews-error" : "border-ews-gray-300") }), jsxRuntime.jsx(Button, { type: "submit", variant: "ews-primary", fullWidth: true, disabled: otp.length !== otpLength || isBusy, children: texts.otpSubmit })] })) : (jsxRuntime.jsx(PinPad, { length: pinLength, onComplete: handlePin, onChange: () => setInvalid(false), disabled: isBusy, invalid: invalid, resetKey: resetKey, eraseLabel: texts.erase, progressLabel: texts.progress, keypadLabel: texts.keypad }, step)), jsxRuntime.jsx("p", { role: "alert", className: "mt-6 min-h-[1.25rem] text-center text-sm text-ews-error", children: shownError }), jsxRuntime.jsxs("div", { className: "mt-auto flex flex-col items-center gap-3 pt-6 sm:mt-4", children: [showForgot && (jsxRuntime.jsx("button", { type: "button", onClick: onForgot, disabled: isBusy, className: "text-base font-medium text-ews-primary hover:underline focus-visible:underline", children: texts.forgot })), onCancel && texts.cancel && (jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: isBusy, className: "text-sm text-ews-gray-600 hover:underline focus-visible:underline", children: texts.cancel }))] })] })) }) }));
35667
35846
  };
35668
35847
 
35669
35848
  const SpecialtySearchAutocomplete = ({ selectedSpecialties = [], onSpecialtiesChange, specialties: availableSpecialties, placeholder = "Search and select medical specialties...", className = "", disabled = false, maxSelections, showSelectedCount = true, title = "Medical Specialties", label = "Select Specialties", getSelectedCountText, removeSpecialtyLabel = "Remove specialty", }) => {
@@ -40163,6 +40342,7 @@ exports.PillBottle = PillBottle;
40163
40342
  exports.PillBottleIcon = PillBottle;
40164
40343
  exports.PillIcon = Pill;
40165
40344
  exports.Pin = Pin;
40345
+ exports.PinCodeInput = PinCodeInput;
40166
40346
  exports.PinIcon = Pin;
40167
40347
  exports.PinOff = PinOff;
40168
40348
  exports.PinOffIcon = PinOff;
@@ -41341,6 +41521,7 @@ exports.parseDateSafe = parseDateSafe;
41341
41521
  exports.useDebounce = useDebounce;
41342
41522
  exports.useDebouncedCallback = useDebouncedCallback;
41343
41523
  exports.useIdleLock = useIdleLock;
41524
+ exports.useMediaQuery = useMediaQuery;
41344
41525
  exports.useSelectField = useSelectField;
41345
41526
  exports.useTheme = useTheme;
41346
41527
  //# sourceMappingURL=index.js.map