@arqel-dev/ui 0.9.1 → 0.9.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/pages.js CHANGED
@@ -2,11 +2,12 @@ import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import { useMemo, useState, useRef, useEffect, useCallback, useId } from 'react';
4
4
  import { cva } from 'class-variance-authority';
5
- import { Slot } from 'radix-ui';
5
+ import { Slot, DropdownMenu as DropdownMenu$1, Dialog as Dialog$1 } from 'radix-ui';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import { isFieldEntry, isLayoutEntry } from '@arqel-dev/types/forms';
8
8
  import { useArqelForm } from '@arqel-dev/hooks';
9
9
  import { usePage, router } from '@inertiajs/react';
10
+ import { XIcon } from 'lucide-react';
10
11
  import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
11
12
 
12
13
  var __defProp = Object.defineProperty;
@@ -865,6 +866,477 @@ var init_ArqelEditPage = __esm({
865
866
  };
866
867
  }
867
868
  });
869
+ function DropdownMenu({
870
+ ...props
871
+ }) {
872
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
873
+ }
874
+ function DropdownMenuTrigger({
875
+ ...props
876
+ }) {
877
+ return /* @__PURE__ */ jsx(
878
+ DropdownMenu$1.Trigger,
879
+ {
880
+ "data-slot": "dropdown-menu-trigger",
881
+ ...props
882
+ }
883
+ );
884
+ }
885
+ function DropdownMenuContent({
886
+ className,
887
+ sideOffset = 4,
888
+ ...props
889
+ }) {
890
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
891
+ DropdownMenu$1.Content,
892
+ {
893
+ "data-slot": "dropdown-menu-content",
894
+ sideOffset,
895
+ className: cn(
896
+ "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
897
+ className
898
+ ),
899
+ ...props
900
+ }
901
+ ) });
902
+ }
903
+ function DropdownMenuItem({
904
+ className,
905
+ inset,
906
+ variant = "default",
907
+ ...props
908
+ }) {
909
+ return /* @__PURE__ */ jsx(
910
+ DropdownMenu$1.Item,
911
+ {
912
+ "data-slot": "dropdown-menu-item",
913
+ "data-inset": inset,
914
+ "data-variant": variant,
915
+ className: cn(
916
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
917
+ className
918
+ ),
919
+ ...props
920
+ }
921
+ );
922
+ }
923
+ var init_dropdown_menu = __esm({
924
+ "src/shadcn/ui/dropdown-menu.tsx"() {
925
+ "use client";
926
+ init_cn();
927
+ }
928
+ });
929
+ function Dialog({
930
+ ...props
931
+ }) {
932
+ return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
933
+ }
934
+ function DialogPortal({
935
+ ...props
936
+ }) {
937
+ return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
938
+ }
939
+ function DialogClose({
940
+ ...props
941
+ }) {
942
+ return /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", ...props });
943
+ }
944
+ function DialogOverlay({
945
+ className,
946
+ ...props
947
+ }) {
948
+ return /* @__PURE__ */ jsx(
949
+ Dialog$1.Overlay,
950
+ {
951
+ "data-slot": "dialog-overlay",
952
+ className: cn(
953
+ "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
954
+ className
955
+ ),
956
+ ...props
957
+ }
958
+ );
959
+ }
960
+ function DialogContent({
961
+ className,
962
+ children,
963
+ showCloseButton = true,
964
+ ...props
965
+ }) {
966
+ return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
967
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
968
+ /* @__PURE__ */ jsxs(
969
+ Dialog$1.Content,
970
+ {
971
+ "data-slot": "dialog-content",
972
+ className: cn(
973
+ "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
974
+ className
975
+ ),
976
+ ...props,
977
+ children: [
978
+ children,
979
+ showCloseButton && /* @__PURE__ */ jsxs(
980
+ Dialog$1.Close,
981
+ {
982
+ "data-slot": "dialog-close",
983
+ className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
984
+ children: [
985
+ /* @__PURE__ */ jsx(XIcon, {}),
986
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
987
+ ]
988
+ }
989
+ )
990
+ ]
991
+ }
992
+ )
993
+ ] });
994
+ }
995
+ function DialogHeader({ className, ...props }) {
996
+ return /* @__PURE__ */ jsx(
997
+ "div",
998
+ {
999
+ "data-slot": "dialog-header",
1000
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
1001
+ ...props
1002
+ }
1003
+ );
1004
+ }
1005
+ function DialogFooter({
1006
+ className,
1007
+ showCloseButton = false,
1008
+ children,
1009
+ ...props
1010
+ }) {
1011
+ return /* @__PURE__ */ jsxs(
1012
+ "div",
1013
+ {
1014
+ "data-slot": "dialog-footer",
1015
+ className: cn(
1016
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1017
+ className
1018
+ ),
1019
+ ...props,
1020
+ children: [
1021
+ children,
1022
+ showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
1023
+ ]
1024
+ }
1025
+ );
1026
+ }
1027
+ function DialogTitle({
1028
+ className,
1029
+ ...props
1030
+ }) {
1031
+ return /* @__PURE__ */ jsx(
1032
+ Dialog$1.Title,
1033
+ {
1034
+ "data-slot": "dialog-title",
1035
+ className: cn("text-lg leading-none font-semibold", className),
1036
+ ...props
1037
+ }
1038
+ );
1039
+ }
1040
+ function DialogDescription({
1041
+ className,
1042
+ ...props
1043
+ }) {
1044
+ return /* @__PURE__ */ jsx(
1045
+ Dialog$1.Description,
1046
+ {
1047
+ "data-slot": "dialog-description",
1048
+ className: cn("text-sm text-muted-foreground", className),
1049
+ ...props
1050
+ }
1051
+ );
1052
+ }
1053
+ var init_dialog = __esm({
1054
+ "src/shadcn/ui/dialog.tsx"() {
1055
+ init_cn();
1056
+ init_button();
1057
+ }
1058
+ });
1059
+ function ActionFormModal({
1060
+ open,
1061
+ onOpenChange,
1062
+ action,
1063
+ fields,
1064
+ onSubmit,
1065
+ processing = false,
1066
+ errors = {},
1067
+ initialValues = {}
1068
+ }) {
1069
+ const [values, setValues] = useState(initialValues);
1070
+ const size = action.modalSize ?? "md";
1071
+ const schema = useMemo(
1072
+ () => ({
1073
+ schema: (action.form ?? []).map((entry) => ({
1074
+ kind: "field",
1075
+ name: entry.name,
1076
+ type: entry.type
1077
+ })),
1078
+ columns: 1,
1079
+ model: null,
1080
+ inline: false,
1081
+ disabled: false
1082
+ }),
1083
+ [action.form]
1084
+ );
1085
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => onOpenChange(next), children: /* @__PURE__ */ jsxs(DialogContent, { className: cn("overflow-auto", SIZE_CLASSES[size]), children: [
1086
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: action.label }) }),
1087
+ /* @__PURE__ */ jsxs(
1088
+ "form",
1089
+ {
1090
+ className: "flex flex-col gap-4",
1091
+ onSubmit: (e) => {
1092
+ e.preventDefault();
1093
+ onSubmit(values);
1094
+ },
1095
+ children: [
1096
+ /* @__PURE__ */ jsx(
1097
+ FormRenderer,
1098
+ {
1099
+ schema,
1100
+ fields,
1101
+ values,
1102
+ errors,
1103
+ onChange: (name, value) => setValues((prev) => ({ ...prev, [name]: value })),
1104
+ disabled: processing
1105
+ }
1106
+ ),
1107
+ /* @__PURE__ */ jsx(
1108
+ FormActions,
1109
+ {
1110
+ processing,
1111
+ submitLabel: action.label,
1112
+ extra: /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", disabled: processing, children: "Cancel" }) })
1113
+ }
1114
+ )
1115
+ ]
1116
+ }
1117
+ )
1118
+ ] }) });
1119
+ }
1120
+ var SIZE_CLASSES;
1121
+ var init_ActionFormModal = __esm({
1122
+ "src/action/ActionFormModal.tsx"() {
1123
+ init_FormActions();
1124
+ init_FormRenderer();
1125
+ init_dialog();
1126
+ init_cn();
1127
+ init_Button();
1128
+ SIZE_CLASSES = {
1129
+ sm: "sm:max-w-[24rem]",
1130
+ md: "sm:max-w-[32rem]",
1131
+ lg: "sm:max-w-[44rem]",
1132
+ xl: "sm:max-w-[56rem]",
1133
+ full: "sm:max-w-[90vw] h-[90vh]"
1134
+ };
1135
+ }
1136
+ });
1137
+ function ConfirmDialog({
1138
+ open,
1139
+ onOpenChange,
1140
+ config,
1141
+ onConfirm,
1142
+ processing = false
1143
+ }) {
1144
+ const [typed, setTyped] = useState("");
1145
+ useEffect(() => {
1146
+ if (!open) setTyped("");
1147
+ }, [open]);
1148
+ const requiresText = config?.requiresText;
1149
+ const submitDisabled = processing || requiresText !== void 0 && typed !== requiresText;
1150
+ const variant = COLOR_VARIANT[config?.color ?? "destructive"];
1151
+ const submitLabel = config?.submitLabel ?? (config?.color === "info" ? "Confirm" : "Delete");
1152
+ const cancelLabel = config?.cancelLabel ?? "Cancel";
1153
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => onOpenChange(next), children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-[28rem]", showCloseButton: false, children: [
1154
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
1155
+ /* @__PURE__ */ jsx(DialogTitle, { children: config?.heading ?? "Are you sure?" }),
1156
+ config?.description && /* @__PURE__ */ jsx(DialogDescription, { children: config.description })
1157
+ ] }),
1158
+ requiresText !== void 0 && /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
1159
+ /* @__PURE__ */ jsxs("span", { children: [
1160
+ "Type ",
1161
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: requiresText }),
1162
+ " to confirm"
1163
+ ] }),
1164
+ /* @__PURE__ */ jsx(
1165
+ "input",
1166
+ {
1167
+ type: "text",
1168
+ ref: (el) => el?.focus(),
1169
+ value: typed,
1170
+ onChange: (e) => setTyped(e.target.value),
1171
+ onKeyDown: (e) => {
1172
+ if (e.key === "Enter" && !submitDisabled) {
1173
+ e.preventDefault();
1174
+ onConfirm();
1175
+ }
1176
+ },
1177
+ className: "h-9 rounded-md border border-input bg-background px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1178
+ }
1179
+ )
1180
+ ] }),
1181
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1182
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", disabled: processing, children: cancelLabel }) }),
1183
+ /* @__PURE__ */ jsx(Button, { type: "button", variant, disabled: submitDisabled, onClick: onConfirm, children: processing ? "Working\u2026" : submitLabel })
1184
+ ] })
1185
+ ] }) });
1186
+ }
1187
+ var COLOR_VARIANT;
1188
+ var init_ConfirmDialog = __esm({
1189
+ "src/action/ConfirmDialog.tsx"() {
1190
+ init_dialog();
1191
+ init_Button();
1192
+ COLOR_VARIANT = {
1193
+ destructive: "destructive",
1194
+ warning: "default",
1195
+ info: "default"
1196
+ };
1197
+ }
1198
+ });
1199
+ function ActionButton({
1200
+ action,
1201
+ formFields = [],
1202
+ onInvoke,
1203
+ processing = false,
1204
+ errors,
1205
+ size = "default",
1206
+ className
1207
+ }) {
1208
+ const [confirmOpen, setConfirmOpen] = useState(false);
1209
+ const [formOpen, setFormOpen] = useState(false);
1210
+ const variant = VARIANT_OVERRIDE[action.variant] ?? COLOR_TO_VARIANT[action.color];
1211
+ const handleClick = () => {
1212
+ if (action.disabled) return;
1213
+ if (action.requiresConfirmation) {
1214
+ setConfirmOpen(true);
1215
+ } else if (action.form && action.form.length > 0) {
1216
+ setFormOpen(true);
1217
+ } else {
1218
+ onInvoke();
1219
+ }
1220
+ };
1221
+ const handleConfirm = () => {
1222
+ setConfirmOpen(false);
1223
+ if (action.form && action.form.length > 0) {
1224
+ setFormOpen(true);
1225
+ } else {
1226
+ onInvoke();
1227
+ }
1228
+ };
1229
+ const handleFormSubmit = (values) => {
1230
+ onInvoke(values);
1231
+ setFormOpen(false);
1232
+ };
1233
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1234
+ /* @__PURE__ */ jsx(
1235
+ Button,
1236
+ {
1237
+ variant,
1238
+ size,
1239
+ disabled: action.disabled === true || processing,
1240
+ title: action.tooltip,
1241
+ onClick: handleClick,
1242
+ className,
1243
+ children: action.label
1244
+ }
1245
+ ),
1246
+ action.requiresConfirmation && /* @__PURE__ */ jsx(
1247
+ ConfirmDialog,
1248
+ {
1249
+ open: confirmOpen,
1250
+ onOpenChange: setConfirmOpen,
1251
+ config: action.confirmation,
1252
+ onConfirm: handleConfirm,
1253
+ processing
1254
+ }
1255
+ ),
1256
+ action.form && action.form.length > 0 && /* @__PURE__ */ jsx(
1257
+ ActionFormModal,
1258
+ {
1259
+ open: formOpen,
1260
+ onOpenChange: setFormOpen,
1261
+ action,
1262
+ fields: formFields,
1263
+ onSubmit: handleFormSubmit,
1264
+ processing,
1265
+ errors
1266
+ }
1267
+ )
1268
+ ] });
1269
+ }
1270
+ var COLOR_TO_VARIANT, VARIANT_OVERRIDE;
1271
+ var init_ActionButton = __esm({
1272
+ "src/action/ActionButton.tsx"() {
1273
+ init_ActionFormModal();
1274
+ init_Button();
1275
+ init_ConfirmDialog();
1276
+ COLOR_TO_VARIANT = {
1277
+ primary: "default",
1278
+ secondary: "outline",
1279
+ destructive: "destructive",
1280
+ success: "default",
1281
+ warning: "default",
1282
+ info: "default"
1283
+ };
1284
+ VARIANT_OVERRIDE = {
1285
+ default: "default",
1286
+ outline: "outline",
1287
+ ghost: "ghost",
1288
+ destructive: "destructive"
1289
+ };
1290
+ }
1291
+ });
1292
+ function ActionMenu({
1293
+ actions,
1294
+ formFieldsByAction = {},
1295
+ onInvoke,
1296
+ inlineThreshold = 3,
1297
+ processing = false,
1298
+ trigger,
1299
+ className
1300
+ }) {
1301
+ if (actions.length === 0) return null;
1302
+ if (actions.length <= inlineThreshold) {
1303
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-1", className), children: actions.map((action) => /* @__PURE__ */ jsx(
1304
+ ActionButton,
1305
+ {
1306
+ action,
1307
+ formFields: formFieldsByAction[action.name] ?? [],
1308
+ onInvoke: (values) => onInvoke(action, values),
1309
+ processing,
1310
+ size: "sm"
1311
+ },
1312
+ action.name
1313
+ )) });
1314
+ }
1315
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1316
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Actions", children: "\u22EF" }) }),
1317
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", sideOffset: 6, className: cn("min-w-[12rem]", className), children: actions.map((action) => /* @__PURE__ */ jsx(
1318
+ DropdownMenuItem,
1319
+ {
1320
+ disabled: action.disabled === true,
1321
+ variant: action.color === "destructive" ? "destructive" : "default",
1322
+ onSelect: () => {
1323
+ if (action.disabled) return;
1324
+ onInvoke(action);
1325
+ },
1326
+ children: action.label
1327
+ },
1328
+ action.name
1329
+ )) })
1330
+ ] });
1331
+ }
1332
+ var init_ActionMenu = __esm({
1333
+ "src/action/ActionMenu.tsx"() {
1334
+ init_dropdown_menu();
1335
+ init_cn();
1336
+ init_ActionButton();
1337
+ init_Button();
1338
+ }
1339
+ });
868
1340
  function TableCell({ column, value }) {
869
1341
  switch (column.type) {
870
1342
  case "text":
@@ -1575,11 +2047,23 @@ function pruneEmptyFilters(values) {
1575
2047
  }
1576
2048
  return out;
1577
2049
  }
2050
+ function invokeAction(action, record, formValues) {
2051
+ const recordId = record?.id;
2052
+ const url = action.url ? action.url.replace("{id}", String(recordId ?? "")) : `/arqel-dev/actions/${action.name}`;
2053
+ const method = action.method.toLowerCase();
2054
+ const data = { ...formValues ?? {} };
2055
+ if (recordId !== void 0) data["record_id"] = recordId;
2056
+ router.visit(url, {
2057
+ method,
2058
+ data,
2059
+ preserveScroll: true
2060
+ });
2061
+ }
1578
2062
  function buildQuery(params) {
1579
2063
  const data = {};
1580
2064
  const filters = pruneEmptyFilters(params.filters);
1581
2065
  if (Object.keys(filters).length > 0) data["filter"] = filters;
1582
- if (params.search) data["search"] = params.search;
2066
+ if (params.search !== void 0) data["search"] = params.search;
1583
2067
  if (params.sort?.column) {
1584
2068
  data["sort"] = params.sort.column;
1585
2069
  if (params.sort.direction) data["direction"] = params.sort.direction;
@@ -1603,6 +2087,8 @@ function ArqelIndexPage() {
1603
2087
  }, []);
1604
2088
  const [filterValues, setFilterValues] = useState(initialFilters);
1605
2089
  const [searchValue, setSearchValue] = useState(props.search ?? "");
2090
+ const [perPage, setPerPage] = useState(props.pagination?.perPage);
2091
+ const [selectedIds, setSelectedIds] = useState([]);
1606
2092
  const searchDebounceRef = useRef(null);
1607
2093
  const visit = (data) => {
1608
2094
  const url = typeof window !== "undefined" ? window.location.pathname : page.url.split("?")[0];
@@ -1621,17 +2107,17 @@ function ArqelIndexPage() {
1621
2107
  nextFilters[name] = value;
1622
2108
  }
1623
2109
  setFilterValues(nextFilters);
1624
- visit(buildQuery({ filters: nextFilters, search: searchValue, sort: currentSort }));
2110
+ visit(buildQuery({ filters: nextFilters, search: searchValue, sort: currentSort, perPage }));
1625
2111
  };
1626
2112
  const handleClearFilters = () => {
1627
2113
  setFilterValues({});
1628
- visit(buildQuery({ filters: {}, search: searchValue, sort: currentSort }));
2114
+ visit(buildQuery({ filters: {}, search: searchValue, sort: currentSort, perPage }));
1629
2115
  };
1630
2116
  const handleSearchChange = (value) => {
1631
2117
  setSearchValue(value);
1632
2118
  if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
1633
2119
  searchDebounceRef.current = setTimeout(() => {
1634
- visit(buildQuery({ filters: filterValues, search: value, sort: currentSort }));
2120
+ visit(buildQuery({ filters: filterValues, search: value, sort: currentSort, perPage }));
1635
2121
  }, 300);
1636
2122
  };
1637
2123
  useEffect(
@@ -1645,7 +2131,8 @@ function ArqelIndexPage() {
1645
2131
  buildQuery({
1646
2132
  filters: filterValues,
1647
2133
  search: searchValue,
1648
- sort: { column, direction }
2134
+ sort: { column, direction },
2135
+ perPage
1649
2136
  })
1650
2137
  );
1651
2138
  };
@@ -1655,20 +2142,50 @@ function ArqelIndexPage() {
1655
2142
  filters: filterValues,
1656
2143
  search: searchValue,
1657
2144
  sort: currentSort,
1658
- page: pageNum
2145
+ page: pageNum,
2146
+ perPage
1659
2147
  })
1660
2148
  );
1661
2149
  };
1662
- const handlePerPageChange = (perPage) => {
2150
+ const handlePerPageChange = (newPerPage) => {
2151
+ setPerPage(newPerPage);
1663
2152
  visit(
1664
2153
  buildQuery({
1665
2154
  filters: filterValues,
1666
2155
  search: searchValue,
1667
2156
  sort: currentSort,
1668
- perPage
2157
+ perPage: newPerPage
1669
2158
  })
1670
2159
  );
1671
2160
  };
2161
+ const bulkActionsList = props.actions?.bulk ?? [];
2162
+ const bulkActions = bulkActionsList.length > 0 && selectedIds.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: bulkActionsList.map((action) => /* @__PURE__ */ jsx(
2163
+ "button",
2164
+ {
2165
+ type: "button",
2166
+ onClick: () => {
2167
+ const url = action.url ?? `/arqel-dev/actions/${action.name}`;
2168
+ const method = action.method.toLowerCase();
2169
+ router.visit(url, {
2170
+ method,
2171
+ data: { record_ids: selectedIds },
2172
+ preserveScroll: true,
2173
+ onSuccess: () => setSelectedIds([])
2174
+ });
2175
+ },
2176
+ className: "inline-flex h-8 items-center gap-1 rounded-sm border border-destructive/40 bg-destructive/10 px-3 text-xs font-medium text-destructive hover:bg-destructive/20",
2177
+ children: action.label
2178
+ },
2179
+ action.name
2180
+ )) }) : void 0;
2181
+ const rowActionsList = props.actions?.row ?? [];
2182
+ const rowActions = rowActionsList.length > 0 ? (record) => /* @__PURE__ */ jsx(
2183
+ ActionMenu,
2184
+ {
2185
+ actions: rowActionsList,
2186
+ onInvoke: (action, formValues) => invokeAction(action, record, formValues)
2187
+ }
2188
+ ) : void 0;
1672
2189
  return /* @__PURE__ */ jsx(
1673
2190
  ResourceIndex,
1674
2191
  {
@@ -1680,12 +2197,17 @@ function ArqelIndexPage() {
1680
2197
  onSortChange: handleSortChange,
1681
2198
  onPageChange: handlePageChange,
1682
2199
  onPerPageChange: handlePerPageChange,
1683
- search: searchValue
2200
+ search: searchValue,
2201
+ selectedIds,
2202
+ onSelectionChange: (ids) => setSelectedIds(ids),
2203
+ ...rowActions ? { rowActions } : {},
2204
+ ...bulkActions ? { bulkActions } : {}
1684
2205
  }
1685
2206
  );
1686
2207
  }
1687
2208
  var init_ArqelIndexPage = __esm({
1688
2209
  "src/pages/ArqelIndexPage.tsx"() {
2210
+ init_ActionMenu();
1689
2211
  init_ResourceIndex();
1690
2212
  }
1691
2213
  });