@geomak/ui 5.0.1 → 5.0.2

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.d.cts CHANGED
@@ -560,26 +560,38 @@ type NotificationPosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-ri
560
560
  interface NotificationPayload {
561
561
  title: React$1.ReactNode;
562
562
  description?: React$1.ReactNode;
563
- /** Auto-dismiss duration in ms (default 4000) */
563
+ /** Auto-dismiss duration in ms (default 4000). Pass `Infinity` to disable auto-dismiss. */
564
564
  duration?: number;
565
565
  type?: NotificationType;
566
566
  }
567
567
  /** ─────────────────── provider ─────────────────── */
568
568
  /**
569
- * Wrap your app in `NotificationProvider`, then call `useNotification()` anywhere inside.
569
+ * Wrap your app in `NotificationProvider`, then call `useNotification()`
570
+ * anywhere inside to show toast notifications.
570
571
  *
571
- * @param position One of 6 viewport positions (default: `top-right`)
572
+ * @param position One of 6 viewport positions (default `'top-right'`).
572
573
  *
573
574
  * @example
574
575
  * <NotificationProvider position="bottom-right">
575
576
  * <App />
576
577
  * </NotificationProvider>
578
+ *
579
+ * // anywhere inside:
580
+ * const { success, danger } = useNotification()
581
+ * success({ title: 'Saved', description: 'Changes were stored.' })
577
582
  */
578
583
  declare function NotificationProvider({ children, position, }: {
579
584
  children: React$1.ReactNode;
580
585
  position?: NotificationPosition;
581
586
  }): react_jsx_runtime.JSX.Element;
582
587
  /** ─────────────────── hook ─────────────────── */
588
+ /**
589
+ * Returns four type-specific show functions.
590
+ *
591
+ * @example
592
+ * const { info, success, warning, danger } = useNotification()
593
+ * danger({ title: 'Save failed', description: 'Could not reach the server.' })
594
+ */
583
595
  declare function useNotification(): {
584
596
  info: (props: Omit<NotificationPayload, "type">) => void;
585
597
  success: (props: Omit<NotificationPayload, "type">) => void;
package/dist/index.d.ts CHANGED
@@ -560,26 +560,38 @@ type NotificationPosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-ri
560
560
  interface NotificationPayload {
561
561
  title: React$1.ReactNode;
562
562
  description?: React$1.ReactNode;
563
- /** Auto-dismiss duration in ms (default 4000) */
563
+ /** Auto-dismiss duration in ms (default 4000). Pass `Infinity` to disable auto-dismiss. */
564
564
  duration?: number;
565
565
  type?: NotificationType;
566
566
  }
567
567
  /** ─────────────────── provider ─────────────────── */
568
568
  /**
569
- * Wrap your app in `NotificationProvider`, then call `useNotification()` anywhere inside.
569
+ * Wrap your app in `NotificationProvider`, then call `useNotification()`
570
+ * anywhere inside to show toast notifications.
570
571
  *
571
- * @param position One of 6 viewport positions (default: `top-right`)
572
+ * @param position One of 6 viewport positions (default `'top-right'`).
572
573
  *
573
574
  * @example
574
575
  * <NotificationProvider position="bottom-right">
575
576
  * <App />
576
577
  * </NotificationProvider>
578
+ *
579
+ * // anywhere inside:
580
+ * const { success, danger } = useNotification()
581
+ * success({ title: 'Saved', description: 'Changes were stored.' })
577
582
  */
578
583
  declare function NotificationProvider({ children, position, }: {
579
584
  children: React$1.ReactNode;
580
585
  position?: NotificationPosition;
581
586
  }): react_jsx_runtime.JSX.Element;
582
587
  /** ─────────────────── hook ─────────────────── */
588
+ /**
589
+ * Returns four type-specific show functions.
590
+ *
591
+ * @example
592
+ * const { info, success, warning, danger } = useNotification()
593
+ * danger({ title: 'Save failed', description: 'Could not reach the server.' })
594
+ */
583
595
  declare function useNotification(): {
584
596
  info: (props: Omit<NotificationPayload, "type">) => void;
585
597
  success: (props: Omit<NotificationPayload, "type">) => void;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { colors_default } from './chunk-GKXP6OJJ.js';
2
2
  export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } from './chunk-GKXP6OJJ.js';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import React8, { createContext, useState, useEffect, useMemo, useContext, useRef, useId, useCallback, useLayoutEffect } from 'react';
4
+ import React8, { createContext, useState, useEffect, useMemo, useCallback, useContext, useRef, useId, useLayoutEffect } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
  import * as Dialog from '@radix-ui/react-dialog';
7
7
  import { useReducedMotion, AnimatePresence, motion } from 'framer-motion';
@@ -9,7 +9,6 @@ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
9
9
  import * as TabsPrimitive from '@radix-ui/react-tabs';
10
10
  import * as Accordion from '@radix-ui/react-accordion';
11
11
  import * as ToggleGroup from '@radix-ui/react-toggle-group';
12
- import * as Toast from '@radix-ui/react-toast';
13
12
  import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
14
13
  import * as Popover from '@radix-ui/react-popover';
15
14
  import * as SwitchPrimitive from '@radix-ui/react-switch';
@@ -783,12 +782,12 @@ var VIEWPORT_CLASSES = {
783
782
  function getInitialMotion(pos, reduced) {
784
783
  if (reduced) return { opacity: 0, y: 0, scale: 1 };
785
784
  const bottom = pos.startsWith("bottom");
786
- return {
787
- opacity: 0,
788
- y: bottom ? 24 : -24,
789
- // rise from below (bottom) or drop from above (top)
790
- scale: 0.92
791
- };
785
+ return { opacity: 0, y: bottom ? 28 : -28, scale: 0.92 };
786
+ }
787
+ function getExitMotion(pos, reduced) {
788
+ if (reduced) return { opacity: 0, y: 0, scale: 1 };
789
+ const bottom = pos.startsWith("bottom");
790
+ return { opacity: 0, y: bottom ? 18 : -18, scale: 0.94 };
792
791
  }
793
792
  function TypeIcon({ type }) {
794
793
  if (type === "success") {
@@ -820,66 +819,87 @@ function NotificationItem({
820
819
  onClose,
821
820
  reduced
822
821
  }) {
823
- const [hovered, setHovered] = useState(false);
824
- const initial = getInitialMotion(pos, reduced);
825
- const center = pos.endsWith("center");
822
+ const [paused, setPaused] = useState(false);
826
823
  const duration = n.duration ?? 4e3;
827
- const showProgress = !reduced && isFinite(duration) && duration > 0;
824
+ const isAutoDismissing = isFinite(duration) && duration > 0;
825
+ const showProgress = !reduced && isAutoDismissing;
826
+ const timerRef = useRef(null);
827
+ const startTimeRef = useRef(0);
828
+ const remainingRef = useRef(duration);
829
+ const clearTimer = useCallback(() => {
830
+ if (timerRef.current !== null) {
831
+ clearTimeout(timerRef.current);
832
+ timerRef.current = null;
833
+ }
834
+ }, []);
835
+ const scheduleDismiss = useCallback((ms) => {
836
+ clearTimer();
837
+ if (!isAutoDismissing) return;
838
+ startTimeRef.current = Date.now();
839
+ timerRef.current = setTimeout(() => onClose(n.id), ms);
840
+ }, [clearTimer, isAutoDismissing, n.id, onClose]);
841
+ useEffect(() => {
842
+ if (paused || !isAutoDismissing) return;
843
+ scheduleDismiss(remainingRef.current);
844
+ return clearTimer;
845
+ }, [paused, isAutoDismissing, scheduleDismiss, clearTimer]);
846
+ const onPauseStart = () => {
847
+ if (!isAutoDismissing) return;
848
+ const elapsed = Date.now() - startTimeRef.current;
849
+ remainingRef.current = Math.max(0, remainingRef.current - elapsed);
850
+ setPaused(true);
851
+ };
852
+ const onPauseEnd = () => {
853
+ if (!isAutoDismissing) return;
854
+ setPaused(false);
855
+ };
828
856
  return /* @__PURE__ */ jsx(
829
857
  motion.div,
830
858
  {
859
+ layout: true,
831
860
  className: "pointer-events-auto",
832
- initial,
861
+ initial: getInitialMotion(pos, reduced),
833
862
  animate: { opacity: 1, y: 0, scale: 1 },
834
- exit: {
835
- opacity: 0,
836
- y: pos.startsWith("bottom") ? 16 : -16,
837
- scale: 0.94,
838
- transition: reduced ? { duration: 0 } : {
839
- opacity: { duration: 0.14, delay: 0.06 },
840
- y: { type: "tween", duration: 0.22, ease: [0.4, 0, 1, 1] },
841
- scale: { type: "tween", duration: 0.22, ease: [0.4, 0, 1, 1] }
842
- }
843
- },
863
+ exit: getExitMotion(pos, reduced),
844
864
  transition: reduced ? { duration: 0 } : {
845
- // Opacity finishes in 0.15 s card is fully opaque while y/scale
846
- // still have ~55 % of their travel left → movement is clearly visible.
865
+ // Opacity finishes in 0.15 s; y/scale take 0.34 s.
866
+ // Card is opaque while still travelling movement
867
+ // is clearly visible to the user.
847
868
  opacity: { duration: 0.15 },
848
869
  y: { type: "tween", duration: 0.34, ease: [0.16, 1, 0.3, 1] },
849
- scale: { type: "tween", duration: 0.34, ease: [0.16, 1, 0.3, 1] }
870
+ scale: { type: "tween", duration: 0.34, ease: [0.16, 1, 0.3, 1] },
871
+ layout: { duration: 0.22, ease: [0.16, 1, 0.3, 1] }
850
872
  },
851
- onMouseEnter: () => setHovered(true),
852
- onMouseLeave: () => setHovered(false),
873
+ onMouseEnter: onPauseStart,
874
+ onMouseLeave: onPauseEnd,
875
+ onFocus: onPauseStart,
876
+ onBlur: onPauseEnd,
877
+ role: n.type === "danger" || n.type === "warning" ? "alert" : "status",
878
+ "aria-live": n.type === "danger" || n.type === "warning" ? "assertive" : "polite",
853
879
  children: /* @__PURE__ */ jsxs(
854
- Toast.Root,
880
+ "div",
855
881
  {
856
- open: true,
857
- duration,
858
- onOpenChange: (o) => {
859
- if (!o) onClose(n.id);
860
- },
861
882
  className: [
862
- "w-[300px] rounded-md shadow-lg overflow-hidden",
863
- center ? "mx-auto" : "",
864
- "focus:outline-none",
883
+ "w-[300px] rounded-md shadow-lg overflow-hidden focus:outline-none",
865
884
  TYPE_BG[n.type ?? "info"]
866
885
  ].join(" "),
867
886
  children: [
868
887
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 pr-2.5", children: [
869
888
  /* @__PURE__ */ jsx("span", { className: "mt-0.5 flex-shrink-0 text-white/90", children: /* @__PURE__ */ jsx(TypeIcon, { type: n.type ?? "info" }) }),
870
889
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
871
- /* @__PURE__ */ jsx(Toast.Title, { className: "text-sm font-semibold text-white leading-snug", children: n.title }),
872
- n.description && /* @__PURE__ */ jsx(Toast.Description, { className: "mt-0.5 text-xs text-white/75 leading-relaxed", children: n.description })
890
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-white leading-snug", children: n.title }),
891
+ n.description && /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-xs text-white/75 leading-relaxed", children: n.description })
873
892
  ] }),
874
- /* @__PURE__ */ jsx(Toast.Action, { asChild: true, altText: "Close", children: /* @__PURE__ */ jsx(
893
+ /* @__PURE__ */ jsx(
875
894
  "button",
876
895
  {
877
- "aria-label": "Close",
896
+ type: "button",
897
+ "aria-label": "Close notification",
878
898
  onClick: () => onClose(n.id),
879
899
  className: "flex-shrink-0 mt-0.5 rounded p-1 text-white/60 hover:text-white hover:bg-white/15 transition-colors duration-100 focus:outline-none focus-visible:ring-1 focus-visible:ring-white/50",
880
900
  children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M9 3L3 9M3 3l6 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
881
901
  }
882
- ) })
902
+ )
883
903
  ] }),
884
904
  showProgress && /* @__PURE__ */ jsx("div", { className: "relative h-[3px] bg-white/20 overflow-hidden", children: /* @__PURE__ */ jsx(
885
905
  "div",
@@ -887,7 +907,7 @@ function NotificationItem({
887
907
  className: "absolute inset-0 bg-white/60 [transform-origin:left]",
888
908
  style: {
889
909
  animation: `notification-progress ${duration}ms linear forwards`,
890
- animationPlayState: hovered ? "paused" : "running"
910
+ animationPlayState: paused ? "paused" : "running"
891
911
  }
892
912
  }
893
913
  ) })
@@ -903,26 +923,27 @@ function NotificationProvider({
903
923
  }) {
904
924
  const [notifications, setNotifications] = useState([]);
905
925
  const reduced = useReducedMotion();
906
- const open = (payload) => {
926
+ const open = useCallback((payload) => {
907
927
  setNotifications((prev) => [
908
928
  ...prev,
909
929
  { duration: 4e3, ...payload, id: Date.now() + Math.random() }
910
930
  ]);
911
- };
912
- const close = (id) => {
931
+ }, []);
932
+ const close = useCallback((id) => {
913
933
  setNotifications((prev) => prev.filter((n) => n.id !== id));
914
- };
915
- return /* @__PURE__ */ jsx(NotificationContext.Provider, { value: { open, close }, children: /* @__PURE__ */ jsxs(Toast.Provider, { swipeDirection: position.endsWith("right") ? "right" : position.endsWith("left") ? "left" : "up", children: [
934
+ }, []);
935
+ return /* @__PURE__ */ jsxs(NotificationContext.Provider, { value: { open, close }, children: [
916
936
  children,
917
937
  /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
918
- Toast.Viewport,
938
+ "ul",
919
939
  {
920
- asChild: true,
940
+ role: "region",
941
+ "aria-label": "Notifications",
921
942
  className: [
922
943
  VIEWPORT_CLASSES[position],
923
- "z-[500000] gap-2 w-[332px] outline-none pointer-events-none"
944
+ "z-[500000] gap-2 w-[316px] outline-none pointer-events-none m-0 p-0 list-none"
924
945
  ].join(" "),
925
- children: /* @__PURE__ */ jsx("ul", { children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: notifications.map((n) => /* @__PURE__ */ jsx(
946
+ children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: notifications.map((n) => /* @__PURE__ */ jsx(
926
947
  NotificationItem,
927
948
  {
928
949
  n,
@@ -931,10 +952,10 @@ function NotificationProvider({
931
952
  reduced
932
953
  },
933
954
  n.id
934
- )) }) })
955
+ )) })
935
956
  }
936
957
  ) })
937
- ] }) });
958
+ ] });
938
959
  }
939
960
  function useNotification() {
940
961
  const { open } = useContext(NotificationContext);
@@ -1523,6 +1544,7 @@ function Wizard({
1523
1544
  const tooltipRef = useRef(null);
1524
1545
  const tooltipTitleId = useId();
1525
1546
  const tooltipBodyId = useId();
1547
+ const reduced = useReducedMotion();
1526
1548
  const [open, setOpen] = useState(() => steps.length > 0 && !readDismissed(storageKey));
1527
1549
  const [activeIndex, setActiveIndex] = useState(0);
1528
1550
  const step = steps[activeIndex];
@@ -1566,24 +1588,36 @@ function Wizard({
1566
1588
  const isLast = activeIndex === steps.length - 1;
1567
1589
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1568
1590
  children,
1569
- open && step && /* @__PURE__ */ jsxs(Portal, { children: [
1591
+ /* @__PURE__ */ jsx(AnimatePresence, { children: open && step && /* @__PURE__ */ jsxs(Portal, { children: [
1570
1592
  /* @__PURE__ */ jsx(
1571
- "div",
1593
+ motion.div,
1572
1594
  {
1573
1595
  className: "fixed inset-0 z-[7000000] bg-foreground/40 backdrop-blur-[1px] pointer-events-auto",
1596
+ initial: { opacity: 0 },
1597
+ animate: { opacity: 1 },
1598
+ exit: { opacity: 0 },
1599
+ transition: { duration: reduced ? 0 : 0.18, ease: "easeOut" },
1574
1600
  "aria-hidden": "true"
1575
1601
  }
1576
1602
  ),
1577
1603
  /* @__PURE__ */ jsx(
1578
- "div",
1604
+ motion.div,
1579
1605
  {
1580
- className: "fixed z-[7000001] pointer-events-none rounded-md ring-2 ring-accent ring-offset-2 ring-offset-background transition-all duration-200",
1606
+ className: "fixed z-[7000001] pointer-events-none rounded-md ring-2 ring-accent ring-offset-2 ring-offset-background",
1581
1607
  style: highlightStyle,
1608
+ initial: { opacity: 0, scale: 1.08 },
1609
+ animate: { opacity: 1, scale: 1 },
1610
+ exit: { opacity: 0, scale: 1.08 },
1611
+ transition: {
1612
+ duration: reduced ? 0 : 0.32,
1613
+ ease: [0.16, 1, 0.3, 1]
1614
+ // ease-out-expo — settles softly
1615
+ },
1582
1616
  "aria-hidden": "true"
1583
1617
  }
1584
1618
  ),
1585
1619
  /* @__PURE__ */ jsxs(
1586
- "div",
1620
+ motion.div,
1587
1621
  {
1588
1622
  ref: tooltipRef,
1589
1623
  role: "dialog",
@@ -1592,6 +1626,14 @@ function Wizard({
1592
1626
  "aria-describedby": tooltipBodyId,
1593
1627
  className: "fixed z-[7000002] rounded-lg bg-surface text-foreground border border-border shadow-xl p-4 pointer-events-auto",
1594
1628
  style: tooltipStyle,
1629
+ initial: { opacity: 0, scale: 0.96, y: 6 },
1630
+ animate: { opacity: 1, scale: 1, y: 0 },
1631
+ exit: { opacity: 0, scale: 0.97, y: 4 },
1632
+ transition: reduced ? { duration: 0 } : {
1633
+ opacity: { duration: 0.18 },
1634
+ scale: { type: "tween", duration: 0.26, ease: [0.16, 1, 0.3, 1] },
1635
+ y: { type: "tween", duration: 0.26, ease: [0.16, 1, 0.3, 1] }
1636
+ },
1595
1637
  children: [
1596
1638
  step.title && /* @__PURE__ */ jsx("h3", { id: tooltipTitleId, className: "text-sm font-semibold text-foreground mb-1", children: step.title }),
1597
1639
  /* @__PURE__ */ jsx("div", { id: tooltipBodyId, className: "text-sm text-foreground-secondary leading-relaxed", children: step.description }),
@@ -1631,9 +1673,10 @@ function Wizard({
1631
1673
  ] })
1632
1674
  ] })
1633
1675
  ]
1634
- }
1676
+ },
1677
+ activeIndex
1635
1678
  )
1636
- ] })
1679
+ ] }) })
1637
1680
  ] });
1638
1681
  }
1639
1682
  var SearchInput = React8.forwardRef(function SearchInput2({