@geomak/ui 6.26.0 → 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());
@@ -5729,48 +5767,45 @@ function Pagination({
5729
5767
  if (next) setPerPageKey(next.key);
5730
5768
  }
5731
5769
  }, [serverSide, options.perPage, picker]);
5732
- const navBtn = (icon, disabled, onClick) => /* @__PURE__ */ jsx(IconButton, { disabled, onClick, icon });
5733
- const chevronRight = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
5734
- const doubleChevronRight = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 5l7 7-7 7M5 5l7 7-7 7" }) });
5735
- return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center justify-end pt-2", children: [
5736
- navBtn(
5737
- /* @__PURE__ */ jsx("span", { className: "rotate-180 inline-flex", children: doubleChevronRight }),
5738
- activePage === 0,
5739
- () => onPageChange(0)
5740
- ),
5741
- navBtn(
5742
- /* @__PURE__ */ jsx("span", { className: "rotate-180 inline-flex", children: chevronRight }),
5743
- activePage === 0,
5744
- () => activePage > 0 && onPageChange(activePage - 1)
5745
- ),
5746
- /* @__PURE__ */ jsx("span", { className: "bg-surface-raised rounded-lg ml-2 mr-2 shadow-sm p-2 w-10 text-center select-none text-foreground", children: activePage + 1 }),
5747
- navBtn(
5748
- chevronRight,
5749
- activePage === maxPage,
5750
- () => activePage < maxPage && onPageChange(activePage + 1)
5751
- ),
5752
- navBtn(
5753
- doubleChevronRight,
5754
- activePage === maxPage,
5755
- () => onPageChange(maxPage)
5756
- ),
5757
- options.withPicker && /* @__PURE__ */ jsx(
5758
- Dropdown,
5759
- {
5760
- style: { width: 80, position: "relative", bottom: 4 },
5761
- hasSearch: false,
5762
- items: picker,
5763
- isMultiselect: false,
5764
- value: displayPerPageKey,
5765
- onChange: ({ target: { value } }) => {
5766
- if (Array.isArray(value)) return;
5767
- const numKey = typeof value === "number" ? value : Number(value);
5768
- if (!serverSide) setPerPageKey(numKey);
5769
- const opt = picker.find((o) => o.key === numKey);
5770
- onPerPageChange(opt?.label ?? opt?.value ?? numKey);
5770
+ const navBtn = (icon, disabled, onClick, title) => /* @__PURE__ */ jsx(IconButton, { type: "bordered", size: "sm", disabled, onClick, icon, title });
5771
+ const chevronRight = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
5772
+ const doubleChevronRight = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 5l7 7-7 7M5 5l7 7-7 7" }) });
5773
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-x-4 gap-y-3 pt-3", children: [
5774
+ options.withPicker && /* @__PURE__ */ jsxs("div", { className: "mr-auto flex items-center gap-2", children: [
5775
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs text-foreground-muted", children: "Rows per page" }),
5776
+ /* @__PURE__ */ jsx(
5777
+ Dropdown,
5778
+ {
5779
+ size: "sm",
5780
+ style: { width: 76 },
5781
+ hasSearch: false,
5782
+ items: picker,
5783
+ isMultiselect: false,
5784
+ value: displayPerPageKey,
5785
+ onChange: ({ target: { value } }) => {
5786
+ if (Array.isArray(value)) return;
5787
+ const numKey = typeof value === "number" ? value : Number(value);
5788
+ if (!serverSide) setPerPageKey(numKey);
5789
+ const opt = picker.find((o) => o.key === numKey);
5790
+ onPerPageChange(opt?.label ?? opt?.value ?? numKey);
5791
+ }
5771
5792
  }
5772
- }
5773
- )
5793
+ )
5794
+ ] }),
5795
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5796
+ navBtn(/* @__PURE__ */ jsx("span", { className: "inline-flex rotate-180", children: doubleChevronRight }), activePage === 0, () => onPageChange(0), "First page"),
5797
+ navBtn(/* @__PURE__ */ jsx("span", { className: "inline-flex rotate-180", children: chevronRight }), activePage === 0, () => activePage > 0 && onPageChange(activePage - 1), "Previous page"),
5798
+ /* @__PURE__ */ jsxs("span", { className: "px-2 text-sm tabular-nums text-foreground-secondary select-none", children: [
5799
+ activePage + 1,
5800
+ " ",
5801
+ /* @__PURE__ */ jsxs("span", { className: "text-foreground-muted", children: [
5802
+ "/ ",
5803
+ maxPage + 1
5804
+ ] })
5805
+ ] }),
5806
+ navBtn(chevronRight, activePage === maxPage, () => activePage < maxPage && onPageChange(activePage + 1), "Next page"),
5807
+ navBtn(doubleChevronRight, activePage === maxPage, () => onPageChange(maxPage), "Last page")
5808
+ ] })
5774
5809
  ] });
5775
5810
  }
5776
5811
  function Table({
@@ -8498,7 +8533,7 @@ function OtpInput({
8498
8533
  emit(valid.join(""));
8499
8534
  focusBox(valid.length);
8500
8535
  };
8501
- return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
8536
+ return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
8502
8537
  /* @__PURE__ */ jsx(
8503
8538
  "input",
8504
8539
  {
@@ -8909,9 +8944,9 @@ function DateRangePicker({
8909
8944
  {
8910
8945
  align: "start",
8911
8946
  sideOffset: 4,
8912
- className: "bg-surface text-foreground border border-border rounded-lg shadow-md z-50 p-3 flex gap-3 animate-in fade-in-0 zoom-in-95",
8947
+ className: "bg-surface text-foreground border border-border rounded-lg shadow-md z-50 p-3 flex flex-col gap-3 sm:flex-row max-w-[calc(100vw-1rem)] max-h-[calc(100vh-2rem)] overflow-auto animate-in fade-in-0 zoom-in-95",
8913
8948
  children: [
8914
- presets && presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1 pr-3 border-r border-border min-w-[120px]", children: presets.map((p) => /* @__PURE__ */ jsx(
8949
+ presets && presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1 min-w-[120px] sm:pr-3 sm:border-r sm:border-border", children: presets.map((p) => /* @__PURE__ */ jsx(
8915
8950
  "button",
8916
8951
  {
8917
8952
  type: "button",
@@ -8924,7 +8959,7 @@ function DateRangePicker({
8924
8959
  },
8925
8960
  p.label
8926
8961
  )) }),
8927
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
8962
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row", children: [
8928
8963
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
8929
8964
  /* @__PURE__ */ jsx(
8930
8965
  "button",