@geomak/ui 6.26.1 → 6.27.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.d.cts CHANGED
@@ -1616,6 +1616,8 @@ interface SchedulerProps {
1616
1616
  onSelectEvent?: (event: SchedulerEvent) => void;
1617
1617
  /** Click the "New event" toolbar button. Omit to hide it. */
1618
1618
  onNewEvent?: () => void;
1619
+ /** Called when `loadEvents` rejects. The Scheduler also shows a retry state. */
1620
+ onError?: (error: unknown) => void;
1619
1621
  className?: string;
1620
1622
  style?: React__default.CSSProperties;
1621
1623
  }
@@ -1634,7 +1636,7 @@ interface SchedulerProps {
1634
1636
  * onNewEvent={() => create()}
1635
1637
  * />
1636
1638
  */
1637
- declare function Scheduler({ events: controlledEvents, loadEvents, defaultView, defaultDate, weekStartsOn, dayHours, hourHeight, onSelectSlot, onSelectEvent, onNewEvent, className, style, }: SchedulerProps): react_jsx_runtime.JSX.Element;
1639
+ declare function Scheduler({ events: controlledEvents, loadEvents, defaultView, defaultDate, weekStartsOn, dayHours, hourHeight, onSelectSlot, onSelectEvent, onNewEvent, onError, className, style, }: SchedulerProps): react_jsx_runtime.JSX.Element;
1638
1640
 
1639
1641
  interface CartLineItem {
1640
1642
  id: string | number;
package/dist/index.d.ts CHANGED
@@ -1616,6 +1616,8 @@ interface SchedulerProps {
1616
1616
  onSelectEvent?: (event: SchedulerEvent) => void;
1617
1617
  /** Click the "New event" toolbar button. Omit to hide it. */
1618
1618
  onNewEvent?: () => void;
1619
+ /** Called when `loadEvents` rejects. The Scheduler also shows a retry state. */
1620
+ onError?: (error: unknown) => void;
1619
1621
  className?: string;
1620
1622
  style?: React__default.CSSProperties;
1621
1623
  }
@@ -1634,7 +1636,7 @@ interface SchedulerProps {
1634
1636
  * onNewEvent={() => create()}
1635
1637
  * />
1636
1638
  */
1637
- declare function Scheduler({ events: controlledEvents, loadEvents, defaultView, defaultDate, weekStartsOn, dayHours, hourHeight, onSelectSlot, onSelectEvent, onNewEvent, className, style, }: SchedulerProps): react_jsx_runtime.JSX.Element;
1639
+ declare function Scheduler({ events: controlledEvents, loadEvents, defaultView, defaultDate, weekStartsOn, dayHours, hourHeight, onSelectSlot, onSelectEvent, onNewEvent, onError, className, style, }: SchedulerProps): react_jsx_runtime.JSX.Element;
1638
1640
 
1639
1641
  interface CartLineItem {
1640
1642
  id: string | number;
package/dist/index.js CHANGED
@@ -2910,6 +2910,7 @@ function Scheduler({
2910
2910
  onSelectSlot,
2911
2911
  onSelectEvent,
2912
2912
  onNewEvent,
2913
+ onError,
2913
2914
  className = "",
2914
2915
  style
2915
2916
  }) {
@@ -2918,9 +2919,11 @@ function Scheduler({
2918
2919
  const [cursor, setCursor] = useState(() => defaultDate ?? /* @__PURE__ */ new Date());
2919
2920
  const [loaded, setLoaded] = useState([]);
2920
2921
  const [loading, setLoading] = useState(false);
2922
+ const [error, setError] = useState(null);
2923
+ const [reloadKey, setReloadKey] = useState(0);
2921
2924
  const [dir, setDir] = useState(0);
2922
- const loaderRef = useRef(loadEvents);
2923
- loaderRef.current = loadEvents;
2925
+ const cbRef = useRef({ loadEvents, onError });
2926
+ cbRef.current = { loadEvents, onError };
2924
2927
  const range = useMemo(
2925
2928
  () => view === "month" ? monthRange(cursor) : weekRange(cursor, weekStartsOn),
2926
2929
  [view, cursor, weekStartsOn]
@@ -2928,21 +2931,26 @@ function Scheduler({
2928
2931
  const fromKey = range.from.getTime();
2929
2932
  const toKey = range.to.getTime();
2930
2933
  useEffect(() => {
2931
- const loader = loaderRef.current;
2934
+ const { loadEvents: loader, onError: onErr } = cbRef.current;
2932
2935
  if (!loader) return;
2933
2936
  let cancelled = false;
2934
2937
  setLoading(true);
2938
+ setError(null);
2935
2939
  Promise.resolve(loader({ from: new Date(fromKey), to: new Date(toKey) }, view)).then((evts) => {
2936
2940
  if (!cancelled) setLoaded(evts);
2937
- }).catch(() => {
2938
- if (!cancelled) setLoaded([]);
2941
+ }).catch((err) => {
2942
+ if (!cancelled) {
2943
+ setError(err ?? new Error("Failed to load events"));
2944
+ onErr?.(err);
2945
+ }
2939
2946
  }).finally(() => {
2940
2947
  if (!cancelled) setLoading(false);
2941
2948
  });
2942
2949
  return () => {
2943
2950
  cancelled = true;
2944
2951
  };
2945
- }, [fromKey, toKey, view]);
2952
+ }, [fromKey, toKey, view, reloadKey]);
2953
+ const retry = useCallback(() => setReloadKey((k) => k + 1), []);
2946
2954
  const events = useMemo(
2947
2955
  () => (controlledEvents ?? loaded).map(normalize),
2948
2956
  [controlledEvents, loaded]
@@ -2995,7 +3003,7 @@ function Scheduler({
2995
3003
  onNewEvent && /* @__PURE__ */ jsx(Button_default, { size: "sm", icon: /* @__PURE__ */ jsx(Plus, {}), content: "New event", onClick: onNewEvent })
2996
3004
  ] })
2997
3005
  ] }),
2998
- /* @__PURE__ */ jsx("div", { className: "relative flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(
3006
+ /* @__PURE__ */ jsx("div", { className: "relative flex-1 overflow-hidden", children: error ? /* @__PURE__ */ jsx(SchedulerError, { onRetry: retry }) : loadEvents && loading && events.length === 0 ? /* @__PURE__ */ jsx(SchedulerSkeleton, { view }) : /* @__PURE__ */ jsx(
2999
3007
  motion.div,
3000
3008
  {
3001
3009
  initial: { opacity: 0, x: reduced ? 0 : dir * 24 },
@@ -3030,6 +3038,36 @@ function Scheduler({
3030
3038
  }
3031
3039
  );
3032
3040
  }
3041
+ function SchedulerSkeleton({ view }) {
3042
+ const bar = "rounded bg-background animate-pulse";
3043
+ if (view === "week") {
3044
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col p-3", "aria-hidden": "true", children: [
3045
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 grid gap-2", style: { gridTemplateColumns: "3.5rem repeat(7, 1fr)" }, children: [
3046
+ /* @__PURE__ */ jsx("span", {}),
3047
+ Array.from({ length: 7 }, (_, i) => /* @__PURE__ */ jsx("span", { className: `${bar} mx-auto h-8 w-8 rounded-full` }, i))
3048
+ ] }),
3049
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col gap-3", children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsx("span", { className: `${bar} h-6`, style: { width: `${60 + i * 13 % 35}%` } }, i)) })
3050
+ ] });
3051
+ }
3052
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col p-3", "aria-hidden": "true", children: [
3053
+ /* @__PURE__ */ jsx("div", { className: "mb-3 grid grid-cols-7 gap-2", children: Array.from({ length: 7 }, (_, i) => /* @__PURE__ */ jsx("span", { className: `${bar} h-2.5` }, i)) }),
3054
+ /* @__PURE__ */ jsx("div", { className: "grid flex-1 grid-cols-7 grid-rows-5 gap-2", children: Array.from({ length: 35 }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3055
+ /* @__PURE__ */ jsx("span", { className: `${bar} h-5 w-5 rounded-full` }),
3056
+ i % 3 === 0 && /* @__PURE__ */ jsx("span", { className: `${bar} h-3` }),
3057
+ i % 5 === 0 && /* @__PURE__ */ jsx("span", { className: `${bar} h-3`, style: { width: "70%" } })
3058
+ ] }, i)) })
3059
+ ] });
3060
+ }
3061
+ function SchedulerError({ onRetry }) {
3062
+ return /* @__PURE__ */ jsxs("div", { role: "alert", className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [
3063
+ /* @__PURE__ */ jsx("span", { className: "flex h-10 w-10 items-center justify-center rounded-full text-status-error", style: { backgroundColor: "color-mix(in oklab, var(--color-error) 12%, var(--color-surface))" }, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": "true", className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h16.94a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z" }) }) }),
3064
+ /* @__PURE__ */ jsxs("div", { children: [
3065
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground", children: "Couldn\u2019t load events" }),
3066
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-xs text-foreground-muted", children: "Something went wrong fetching this range." })
3067
+ ] }),
3068
+ /* @__PURE__ */ jsx(Button_default, { size: "sm", variant: "secondary", content: "Retry", onClick: onRetry })
3069
+ ] });
3070
+ }
3033
3071
  function MonthYearPicker({ label, cursor, onPick }) {
3034
3072
  const [open, setOpen] = useState(false);
3035
3073
  const [viewYear, setViewYear] = useState(cursor.getFullYear());