@geomak/ui 1.7.1 → 1.7.3

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
@@ -588,16 +588,44 @@ declare function useNotification(): {
588
588
  };
589
589
 
590
590
  interface LoadingSpinnerProps {
591
- /** Text animated letter by letter */
591
+ /** Text revealed letter-by-letter beneath the spinner. */
592
592
  prompt: string;
593
+ /**
594
+ * Optional override for the spinner ring colour. Accepts any CSS colour.
595
+ * Defaults to the accent token so it picks up theme overrides.
596
+ */
597
+ spinnerColor?: string;
598
+ /**
599
+ * Optional override for the prompt text colour.
600
+ * Defaults to the foreground token (light/dark aware).
601
+ */
602
+ textColor?: string;
603
+ /**
604
+ * Backdrop opacity (0 – 1). Defaults to 0.92 — close enough to opaque to
605
+ * block UI underneath while still hinting at the previous state.
606
+ */
607
+ backdropOpacity?: number;
593
608
  }
594
609
  /**
595
- * Full-screen loading overlay with a spinning shape and staggered text reveal.
610
+ * Full-screen loading overlay with a spinning ring and a staggered text
611
+ * reveal. Portaled into `document.body` so it always covers the actual
612
+ * viewport regardless of where it's rendered in the React tree.
613
+ *
614
+ * Honours `prefers-reduced-motion`: the spinner still rotates (continuous
615
+ * spin is informative, not decorative) but the letter stagger collapses to
616
+ * an instant reveal.
596
617
  *
597
618
  * @example
598
- * {isLoading && <LoadingSpinner prompt="Loading data..." />}
619
+ * {isLoading && <LoadingSpinner prompt="Loading vessels…" />}
620
+ *
621
+ * @example
622
+ * <LoadingSpinner
623
+ * prompt="Saving"
624
+ * spinnerColor="#10b981"
625
+ * backdropOpacity={0.7}
626
+ * />
599
627
  */
600
- declare function LoadingSpinner({ prompt }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
628
+ declare function LoadingSpinner({ prompt, spinnerColor, textColor, backdropOpacity, }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
601
629
 
602
630
  interface FadingBaseProps {
603
631
  className?: string;
@@ -857,11 +885,34 @@ interface WizardProps {
857
885
  declare function Wizard({ children, steps, storageKey }: WizardProps): react_jsx_runtime.JSX.Element;
858
886
 
859
887
  /** ─────────────────── types ─────────────────── */
860
- interface TableColumn {
861
- key: string | number;
888
+ /**
889
+ * Column descriptor for the Table.
890
+ *
891
+ * The generic `T` is the shape of a row — `keyBind` must be one of T's
892
+ * string-keyed properties, and `component(cellValue, row)` receives the
893
+ * matching value with full type inference. When used without a generic
894
+ * (`TableColumn[]`), `T` falls back to `Record<string, any>` for backwards
895
+ * compatibility — narrower typing is preferred whenever possible:
896
+ *
897
+ * ```ts
898
+ * type Vessel = { id: number; name: string; status: 'At Sea' | 'In Port' }
899
+ * const cols: TableColumn<Vessel>[] = [
900
+ * { key: 'name', label: 'Name', keyBind: 'name' }, // cellValue inferred as string
901
+ * ]
902
+ * ```
903
+ */
904
+ interface TableColumn<T extends Record<string, any> = Record<string, any>> {
905
+ /** React reconciliation key for the column itself. */
906
+ key: React$1.Key;
862
907
  label: React$1.ReactNode;
863
- keyBind: string;
864
- component?: (cellValue: any, row: any) => React$1.ReactNode;
908
+ /** Property on the row to read for this column. */
909
+ keyBind: keyof T & string;
910
+ /** Custom cell renderer. Receives the cell value and the full row. */
911
+ component?: (cellValue: T[keyof T], row: T) => React$1.ReactNode;
912
+ /** Explicit column width (CSS length or px number). Optional — defaults to auto. */
913
+ width?: string | number;
914
+ /** Text alignment for both header and cells. Defaults to `'center'`. */
915
+ align?: 'left' | 'center' | 'right';
865
916
  }
866
917
  interface PaginationOptions {
867
918
  enabled?: boolean;
@@ -882,36 +933,70 @@ interface PaginationOptions {
882
933
  onPageChange?: (page: number) => void;
883
934
  onPerPageChange?: (perPage: number) => void;
884
935
  }
885
- interface ExpandRowOptions {
936
+ interface ExpandRowOptions<T extends Record<string, any> = Record<string, any>> {
886
937
  enabled?: boolean;
887
938
  expandIcon?: React$1.ReactNode;
888
- expandComponent?: (row: any) => React$1.ReactNode;
939
+ expandComponent?: (row: T) => React$1.ReactNode;
889
940
  }
890
- interface TableProps {
891
- columns?: TableColumn[];
892
- rows?: any[];
941
+ interface TableProps<T extends Record<string, any> = Record<string, any>> {
942
+ columns?: TableColumn<T>[];
943
+ rows?: T[];
944
+ /**
945
+ * Returns a stable key for each row, used for React reconciliation AND
946
+ * for tracking expanded state when `expandRow.enabled` is true.
947
+ * Defaults to the row index — fine for static lists, but pass an
948
+ * explicit getter (e.g. `(row) => row.id`) if rows can be reordered or
949
+ * filtered while expand state should persist.
950
+ */
951
+ getRowKey?: (row: T, index: number) => React$1.Key;
893
952
  pagination?: PaginationOptions;
894
- expandRow?: ExpandRowOptions;
953
+ expandRow?: ExpandRowOptions<T>;
895
954
  hasSearch?: boolean;
896
955
  footer?: React$1.ReactNode;
897
956
  header?: React$1.ReactNode;
898
- tableRef?: React$1.Ref<any>;
899
- [key: string]: any;
900
957
  }
901
958
  /** ─────────────────── main component ─────────────────── */
902
959
  /**
903
960
  * Data table with optional search, pagination, and expandable rows.
904
961
  *
905
- * Supports both client-side and server-side pagination.
962
+ * - **Typed rows**: pass a generic `T` for full type inference on columns
963
+ * and cell renderers (`<Table<Vessel> ... />`).
964
+ * - **Real `<table>` semantics**: keeps row / col / cell context intact for
965
+ * screen readers and lets the browser handle column sizing natively.
966
+ * Per-column widths via `column.width`.
967
+ * - **Search**: client-side filter across ALL row values; result is
968
+ * memoized so each keystroke costs O(n) once per term change, not per
969
+ * render. Set `pagination.serverSide` to skip client-side filter and
970
+ * pagination entirely.
971
+ * - **Expand**: each row gets a real `<button>` with `aria-expanded`.
972
+ * Expand state is keyed by `getRowKey(row, i)` so it survives reorders.
973
+ *
974
+ * @example Static, fully typed
975
+ * ```tsx
976
+ * type Vessel = { id: number; name: string; status: string }
977
+ * <Table<Vessel>
978
+ * columns={[
979
+ * { key: 'name', label: 'Name', keyBind: 'name' },
980
+ * { key: 'status', label: 'Status', keyBind: 'status', width: 120 },
981
+ * ]}
982
+ * rows={vessels}
983
+ * getRowKey={(row) => row.id}
984
+ * />
985
+ * ```
906
986
  *
907
- * @example
987
+ * @example Server-side pagination
988
+ * ```tsx
908
989
  * <Table
909
- * columns={[{ key: 'name', label: 'Name', keyBind: 'name' }]}
910
- * rows={data}
911
- * pagination={{ enabled: true, perPage: 15 }}
990
+ * columns={cols}
991
+ * rows={pageRows}
992
+ * pagination={{
993
+ * enabled: true, serverSide: true, perPage: 20,
994
+ * page: currentPage, totalCount, onPageChange, onPerPageChange,
995
+ * }}
912
996
  * />
997
+ * ```
913
998
  */
914
- declare function Table({ columns, rows, pagination, expandRow, hasSearch, footer, header, }: TableProps): react_jsx_runtime.JSX.Element;
999
+ declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, footer, header, }: TableProps<T>): react_jsx_runtime.JSX.Element;
915
1000
 
916
1001
  interface ThemeSwitchProps {
917
1002
  checked: boolean;
package/dist/index.d.ts CHANGED
@@ -588,16 +588,44 @@ declare function useNotification(): {
588
588
  };
589
589
 
590
590
  interface LoadingSpinnerProps {
591
- /** Text animated letter by letter */
591
+ /** Text revealed letter-by-letter beneath the spinner. */
592
592
  prompt: string;
593
+ /**
594
+ * Optional override for the spinner ring colour. Accepts any CSS colour.
595
+ * Defaults to the accent token so it picks up theme overrides.
596
+ */
597
+ spinnerColor?: string;
598
+ /**
599
+ * Optional override for the prompt text colour.
600
+ * Defaults to the foreground token (light/dark aware).
601
+ */
602
+ textColor?: string;
603
+ /**
604
+ * Backdrop opacity (0 – 1). Defaults to 0.92 — close enough to opaque to
605
+ * block UI underneath while still hinting at the previous state.
606
+ */
607
+ backdropOpacity?: number;
593
608
  }
594
609
  /**
595
- * Full-screen loading overlay with a spinning shape and staggered text reveal.
610
+ * Full-screen loading overlay with a spinning ring and a staggered text
611
+ * reveal. Portaled into `document.body` so it always covers the actual
612
+ * viewport regardless of where it's rendered in the React tree.
613
+ *
614
+ * Honours `prefers-reduced-motion`: the spinner still rotates (continuous
615
+ * spin is informative, not decorative) but the letter stagger collapses to
616
+ * an instant reveal.
596
617
  *
597
618
  * @example
598
- * {isLoading && <LoadingSpinner prompt="Loading data..." />}
619
+ * {isLoading && <LoadingSpinner prompt="Loading vessels…" />}
620
+ *
621
+ * @example
622
+ * <LoadingSpinner
623
+ * prompt="Saving"
624
+ * spinnerColor="#10b981"
625
+ * backdropOpacity={0.7}
626
+ * />
599
627
  */
600
- declare function LoadingSpinner({ prompt }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
628
+ declare function LoadingSpinner({ prompt, spinnerColor, textColor, backdropOpacity, }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
601
629
 
602
630
  interface FadingBaseProps {
603
631
  className?: string;
@@ -857,11 +885,34 @@ interface WizardProps {
857
885
  declare function Wizard({ children, steps, storageKey }: WizardProps): react_jsx_runtime.JSX.Element;
858
886
 
859
887
  /** ─────────────────── types ─────────────────── */
860
- interface TableColumn {
861
- key: string | number;
888
+ /**
889
+ * Column descriptor for the Table.
890
+ *
891
+ * The generic `T` is the shape of a row — `keyBind` must be one of T's
892
+ * string-keyed properties, and `component(cellValue, row)` receives the
893
+ * matching value with full type inference. When used without a generic
894
+ * (`TableColumn[]`), `T` falls back to `Record<string, any>` for backwards
895
+ * compatibility — narrower typing is preferred whenever possible:
896
+ *
897
+ * ```ts
898
+ * type Vessel = { id: number; name: string; status: 'At Sea' | 'In Port' }
899
+ * const cols: TableColumn<Vessel>[] = [
900
+ * { key: 'name', label: 'Name', keyBind: 'name' }, // cellValue inferred as string
901
+ * ]
902
+ * ```
903
+ */
904
+ interface TableColumn<T extends Record<string, any> = Record<string, any>> {
905
+ /** React reconciliation key for the column itself. */
906
+ key: React$1.Key;
862
907
  label: React$1.ReactNode;
863
- keyBind: string;
864
- component?: (cellValue: any, row: any) => React$1.ReactNode;
908
+ /** Property on the row to read for this column. */
909
+ keyBind: keyof T & string;
910
+ /** Custom cell renderer. Receives the cell value and the full row. */
911
+ component?: (cellValue: T[keyof T], row: T) => React$1.ReactNode;
912
+ /** Explicit column width (CSS length or px number). Optional — defaults to auto. */
913
+ width?: string | number;
914
+ /** Text alignment for both header and cells. Defaults to `'center'`. */
915
+ align?: 'left' | 'center' | 'right';
865
916
  }
866
917
  interface PaginationOptions {
867
918
  enabled?: boolean;
@@ -882,36 +933,70 @@ interface PaginationOptions {
882
933
  onPageChange?: (page: number) => void;
883
934
  onPerPageChange?: (perPage: number) => void;
884
935
  }
885
- interface ExpandRowOptions {
936
+ interface ExpandRowOptions<T extends Record<string, any> = Record<string, any>> {
886
937
  enabled?: boolean;
887
938
  expandIcon?: React$1.ReactNode;
888
- expandComponent?: (row: any) => React$1.ReactNode;
939
+ expandComponent?: (row: T) => React$1.ReactNode;
889
940
  }
890
- interface TableProps {
891
- columns?: TableColumn[];
892
- rows?: any[];
941
+ interface TableProps<T extends Record<string, any> = Record<string, any>> {
942
+ columns?: TableColumn<T>[];
943
+ rows?: T[];
944
+ /**
945
+ * Returns a stable key for each row, used for React reconciliation AND
946
+ * for tracking expanded state when `expandRow.enabled` is true.
947
+ * Defaults to the row index — fine for static lists, but pass an
948
+ * explicit getter (e.g. `(row) => row.id`) if rows can be reordered or
949
+ * filtered while expand state should persist.
950
+ */
951
+ getRowKey?: (row: T, index: number) => React$1.Key;
893
952
  pagination?: PaginationOptions;
894
- expandRow?: ExpandRowOptions;
953
+ expandRow?: ExpandRowOptions<T>;
895
954
  hasSearch?: boolean;
896
955
  footer?: React$1.ReactNode;
897
956
  header?: React$1.ReactNode;
898
- tableRef?: React$1.Ref<any>;
899
- [key: string]: any;
900
957
  }
901
958
  /** ─────────────────── main component ─────────────────── */
902
959
  /**
903
960
  * Data table with optional search, pagination, and expandable rows.
904
961
  *
905
- * Supports both client-side and server-side pagination.
962
+ * - **Typed rows**: pass a generic `T` for full type inference on columns
963
+ * and cell renderers (`<Table<Vessel> ... />`).
964
+ * - **Real `<table>` semantics**: keeps row / col / cell context intact for
965
+ * screen readers and lets the browser handle column sizing natively.
966
+ * Per-column widths via `column.width`.
967
+ * - **Search**: client-side filter across ALL row values; result is
968
+ * memoized so each keystroke costs O(n) once per term change, not per
969
+ * render. Set `pagination.serverSide` to skip client-side filter and
970
+ * pagination entirely.
971
+ * - **Expand**: each row gets a real `<button>` with `aria-expanded`.
972
+ * Expand state is keyed by `getRowKey(row, i)` so it survives reorders.
973
+ *
974
+ * @example Static, fully typed
975
+ * ```tsx
976
+ * type Vessel = { id: number; name: string; status: string }
977
+ * <Table<Vessel>
978
+ * columns={[
979
+ * { key: 'name', label: 'Name', keyBind: 'name' },
980
+ * { key: 'status', label: 'Status', keyBind: 'status', width: 120 },
981
+ * ]}
982
+ * rows={vessels}
983
+ * getRowKey={(row) => row.id}
984
+ * />
985
+ * ```
906
986
  *
907
- * @example
987
+ * @example Server-side pagination
988
+ * ```tsx
908
989
  * <Table
909
- * columns={[{ key: 'name', label: 'Name', keyBind: 'name' }]}
910
- * rows={data}
911
- * pagination={{ enabled: true, perPage: 15 }}
990
+ * columns={cols}
991
+ * rows={pageRows}
992
+ * pagination={{
993
+ * enabled: true, serverSide: true, perPage: 20,
994
+ * page: currentPage, totalCount, onPageChange, onPerPageChange,
995
+ * }}
912
996
  * />
997
+ * ```
913
998
  */
914
- declare function Table({ columns, rows, pagination, expandRow, hasSearch, footer, header, }: TableProps): react_jsx_runtime.JSX.Element;
999
+ declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, footer, header, }: TableProps<T>): react_jsx_runtime.JSX.Element;
915
1000
 
916
1001
  interface ThemeSwitchProps {
917
1002
  checked: boolean;