@yimingliao/cms 0.0.115 → 0.0.117

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.
@@ -1,5 +1,5 @@
1
- import { cn, useSidebar, Sidebar, Skeleton, SidebarInset, SIDEBAR_WIDTH, SidebarGroup, SidebarMenu, Collapsible, SidebarMenuItem, SidebarMenuButton, Separator, CollapsibleTrigger, SidebarMenuAction, CollapsibleContent, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton, Button, Spinner, useParentPathname, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, PAGE_HEADER_HEIGHT, Label, FORM_MIDDLE_GAP_WIDTH, FORM_SIDE_FIELDS_WIDTH, InputGroup, InputGroupAddon, InputGroupInput, InputGroupButton, Textarea, Card, useDeviceInfo, CardHeader, CardTitle, CardContent, useCountdown, CardDescription, NAVBAR_HEIGHT, isConfirm, Avatar, AvatarImage, AvatarFallback, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup } from '../chunk-WU47KNJR.js';
2
- export { NAVBAR_HEIGHT, PAGE_HEADER_HEIGHT, SIDEBAR_WIDTH, Sidebar, SidebarContent, SidebarInset, SidebarProvider, cn, useDeviceInfo, useSidebar } from '../chunk-WU47KNJR.js';
1
+ import { cn, useSidebar, Sidebar, Skeleton, SidebarInset, SIDEBAR_WIDTH, SidebarGroup, SidebarMenu, Collapsible, SidebarMenuItem, SidebarMenuButton, Separator, CollapsibleTrigger, SidebarMenuAction, CollapsibleContent, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton, Button, Spinner, useParentPathname, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, PAGE_HEADER_HEIGHT, InputGroup, InputGroupAddon, Textarea, InputGroupInput, InputGroupButton, Label, FORM_MIDDLE_GAP_WIDTH, FORM_SIDE_FIELDS_WIDTH, Card, useDeviceInfo, CardHeader, CardTitle, CardContent, useCountdown, CardDescription, NAVBAR_HEIGHT, isConfirm, Avatar, AvatarImage, AvatarFallback, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, Pagination, PaginationContent, PaginationItem, PaginationPrevious, PaginationLink, PaginationEllipsis, PaginationNext, Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectLabel, SelectItem } from '../chunk-7YMYPMIB.js';
2
+ export { NAVBAR_HEIGHT, PAGE_HEADER_HEIGHT, SIDEBAR_WIDTH, Sidebar, SidebarContent, SidebarInset, SidebarProvider, cn, useDeviceInfo, useSidebar } from '../chunk-7YMYPMIB.js';
3
3
  import { ensureArray, findTranslation, joinUrl } from '../chunk-VSV6SQWC.js';
4
4
  import { toast } from 'sonner';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -7,7 +7,7 @@ import { useMutation, useQuery } from '@tanstack/react-query';
7
7
  import { createContext, useState, useContext, useEffect, createElement, useRef } from 'react';
8
8
  import { ThemeProvider as ThemeProvider$1, useTheme } from 'next-themes';
9
9
  import { useTranslator } from 'intor/react';
10
- import { ChevronRight, Undo2, SidebarIcon, House, SquareArrowOutUpRight, Languages, LogOut, Files, Asterisk, Eye, EyeOff, CircleX, CirclePlus, Search, Mail, Sun, Moon, TvMinimal, UserCircle, ChevronsUpDown, PenLine, Trash2, CopyCheck, CopyX, FileSymlink, FileX, FilePlus, FilePen, File, FileStack, FolderSearch, Lock, FolderCog, FileSpreadsheet, Archive, Binary, MapPinCheckInside, Star, CircleMinus } from 'lucide-react';
10
+ import { ChevronRight, Undo2, SidebarIcon, House, SquareArrowOutUpRight, Languages, LogOut, Files, CircleX, CirclePlus, Eye, EyeOff, Search, Asterisk, Mail, Sun, Moon, TvMinimal, UserCircle, ChevronsUpDown, PenLine, Trash2, CopyCheck, CopyX, FileSymlink, FileX, FilePlus, FilePen, File, FileStack, FolderSearch, Lock, FolderCog, FileSpreadsheet, Archive, Binary, MapPinCheckInside, Star, CircleMinus } from 'lucide-react';
11
11
  import Link from 'next/link';
12
12
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
13
13
  import { Link as Link$1 } from 'intor/next';
@@ -956,119 +956,598 @@ function PageHeader(props) {
956
956
  /* @__PURE__ */ jsx(Separator, {})
957
957
  ] });
958
958
  }
959
- function Form({
960
- onSubmit,
961
- className,
959
+ function Textarea2({
960
+ // form context
961
+ fieldName,
962
+ setFormData,
963
+ // ui states
964
+ isLoading = false,
965
+ isDisabled = false,
966
+ isError = false,
967
+ // base
962
968
  ...props
963
969
  }) {
964
- const handleSubmit = (e) => {
965
- if (!onSubmit) return;
966
- e.preventDefault();
967
- void onSubmit(e);
970
+ isDisabled = isDisabled || isLoading;
971
+ const textareaRef = useRef(null);
972
+ const resetHeight = (element) => {
973
+ element.style.height = "40px";
974
+ element.style.height = `${element.scrollHeight + 2}px`;
968
975
  };
969
- return /* @__PURE__ */ jsx("form", { className: cn(className), onSubmit: handleSubmit, ...props });
976
+ useEffect(() => {
977
+ if (!textareaRef.current) return;
978
+ resetHeight(textareaRef.current);
979
+ }, [props.value, isLoading]);
980
+ return /* @__PURE__ */ jsx(Fragment, { children: isLoading ? /* @__PURE__ */ jsx(InputGroup, { "data-disabled": isDisabled, className: "h-10", children: /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(Spinner, {}) }) }) : /* @__PURE__ */ jsx(
981
+ Textarea,
982
+ {
983
+ ref: textareaRef,
984
+ disabled: isDisabled,
985
+ "aria-invalid": isError,
986
+ value: isLoading ? "" : props.value,
987
+ onChange: (e) => {
988
+ resetHeight(e.target);
989
+ if (!setFormData || !fieldName) return;
990
+ setFormData((p) => ({ ...p, [fieldName]: e.target.value }));
991
+ },
992
+ ...props
993
+ }
994
+ ) });
970
995
  }
971
- function Field({
972
- label,
973
- isRequired = false,
974
- hint,
975
- labelChildren,
976
- children,
996
+ function ArrayInput({
997
+ // form context
998
+ formData,
999
+ fieldName,
1000
+ setFormData,
1001
+ // ui states
1002
+ isLoading = false,
1003
+ isDisabled = false,
1004
+ isResizable = false,
1005
+ errors,
1006
+ // base
1007
+ placeholder = "",
977
1008
  ...props
978
1009
  }) {
979
- return /* @__PURE__ */ jsxs("div", { className: "grid w-full items-center gap-3", children: [
980
- /* @__PURE__ */ jsxs("span", { className: "flex gap-2", children: [
981
- /* @__PURE__ */ jsxs(Label, { className: "flex gap-1 truncate", ...props, children: [
982
- label,
983
- isRequired && /* @__PURE__ */ jsx(Asterisk, { className: "text-destructive size-3" }),
984
- hint && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground ml-2 text-xs", children: hint })
1010
+ isDisabled = isDisabled || isLoading;
1011
+ const rawValue = formData?.[fieldName ?? ""];
1012
+ const values = Array.isArray(rawValue) && rawValue.length > 0 ? rawValue.map(String) : [""];
1013
+ const errorIndexes = new Set(
1014
+ errors.filter((e) => e.startsWith(fieldName ?? "")).map((e) => e.split(".").pop()).map(Number)
1015
+ );
1016
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: values.map((value, index) => {
1017
+ const isError = errorIndexes.has(index);
1018
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1019
+ /* @__PURE__ */ jsxs("div", { className: "flex-center absolute top-0 left-0 z-10 h-full", children: [
1020
+ /* @__PURE__ */ jsx("span", { className: "min-w-10 pl-0.5 text-center text-sm opacity-50", children: Array.isArray(rawValue) && rawValue.length === 0 ? "0" : index + 1 }),
1021
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical" })
985
1022
  ] }),
986
- /* @__PURE__ */ jsx("span", { children: labelChildren })
987
- ] }),
988
- children
989
- ] });
1023
+ /* @__PURE__ */ jsx(
1024
+ Textarea2,
1025
+ {
1026
+ disabled: isDisabled,
1027
+ placeholder: isLoading ? "" : placeholder,
1028
+ value,
1029
+ onChange: (e) => {
1030
+ if (!setFormData || !fieldName) return;
1031
+ const copy = [...values];
1032
+ copy[index] = e.target.value;
1033
+ setFormData((p) => ({ ...p, [fieldName]: copy }));
1034
+ },
1035
+ onKeyDown: (e) => {
1036
+ if (e.key === "Enter" && !isResizable) {
1037
+ e.preventDefault();
1038
+ }
1039
+ },
1040
+ isError,
1041
+ className: cn("pr-20 pl-13", !isResizable && "resize-none"),
1042
+ ...props
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ jsxs("div", { className: "flex-center absolute top-1 right-1 gap-1", children: [
1046
+ /* @__PURE__ */ jsx(
1047
+ Button2,
1048
+ {
1049
+ variant: "outline",
1050
+ type: "button",
1051
+ size: "icon-sm",
1052
+ className: "rounded-sm",
1053
+ onClick: () => {
1054
+ if (!setFormData || !fieldName) return;
1055
+ const copy = values.filter((_, i) => i !== index);
1056
+ setFormData((p) => ({ ...p, [fieldName]: copy }));
1057
+ },
1058
+ children: /* @__PURE__ */ jsx(CircleX, { className: "text-destructive" })
1059
+ }
1060
+ ),
1061
+ /* @__PURE__ */ jsx(
1062
+ Button2,
1063
+ {
1064
+ variant: "outline",
1065
+ type: "button",
1066
+ size: "icon-sm",
1067
+ className: "rounded-sm",
1068
+ onClick: () => {
1069
+ if (!setFormData || !fieldName) return;
1070
+ const copy = [...values];
1071
+ copy.splice(index + 1, 0, "");
1072
+ setFormData((p) => ({ ...p, [fieldName]: copy }));
1073
+ },
1074
+ children: /* @__PURE__ */ jsx(CirclePlus, { className: "text-success" })
1075
+ }
1076
+ )
1077
+ ] })
1078
+ ] }, index);
1079
+ }) });
990
1080
  }
991
- function FieldBody({
1081
+ function Checkbox({
1082
+ // form context
1083
+ fieldName,
1084
+ formData,
1085
+ setFormData,
992
1086
  // ui states
993
1087
  isLoading = false,
994
1088
  isDisabled = false,
995
- isEmpty = false,
996
1089
  // base
997
- className,
998
- backgroundClassName,
999
- childrenClassName,
1000
- children,
1090
+ isDisplay = false,
1091
+ className = "",
1001
1092
  ...props
1002
1093
  }) {
1003
- const { t } = useTranslator();
1094
+ isDisabled = isDisabled || isLoading || isDisplay;
1095
+ const isChecked = !!formData?.[fieldName ?? ""];
1004
1096
  return /* @__PURE__ */ jsxs(
1005
1097
  "div",
1006
1098
  {
1007
1099
  className: cn(
1008
1100
  className,
1009
- "relative",
1010
- "min-h-9 w-full min-w-0",
1011
- "flex items-center",
1012
- "text-sm"
1101
+ "group relative",
1102
+ "h-6 w-12 min-w-12",
1103
+ "p-1",
1104
+ "rounded-full",
1105
+ "overflow-hidden",
1106
+ "shadow-inner",
1107
+ !isChecked || isLoading ? "bg-secondary" : "bg-success",
1108
+ "transition"
1013
1109
  ),
1014
- ...props,
1015
1110
  children: [
1016
1111
  /* @__PURE__ */ jsx(
1017
1112
  "div",
1018
1113
  {
1019
1114
  className: cn(
1020
- "absolute size-full",
1021
- "dark:bg-input/30 bg-foreground/2 rounded-md",
1022
- backgroundClassName
1115
+ "size-4",
1116
+ "rounded-full",
1117
+ "bg-white",
1118
+ "shadow",
1119
+ isChecked && "translate-x-6",
1120
+ isLoading && "opacity-0",
1121
+ !isDisabled && "group-hover:scale-95 group-active:scale-90",
1122
+ "duration-200"
1023
1123
  )
1024
1124
  }
1025
1125
  ),
1026
- isLoading && /* @__PURE__ */ jsx("div", { className: "px-3", children: /* @__PURE__ */ jsx(Spinner, {}) }),
1027
- !isLoading && (!children || isEmpty) && /* @__PURE__ */ jsx("div", { className: "flex-center h-9 px-3 opacity-50", children: /* @__PURE__ */ jsx("p", { className: "opacity-50", children: t("ui.no-data.text") }) }),
1028
- !isLoading && children && !isEmpty && /* @__PURE__ */ jsx(
1029
- "div",
1126
+ isLoading && /* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1", children: /* @__PURE__ */ jsx(Spinner, {}) }),
1127
+ /* @__PURE__ */ jsx(
1128
+ "input",
1030
1129
  {
1130
+ type: "checkbox",
1131
+ onChange: (e) => {
1132
+ if (!setFormData || !fieldName) return;
1133
+ setFormData((p) => ({ ...p, [fieldName]: e.target.checked }));
1134
+ },
1135
+ checked: isChecked,
1136
+ disabled: isDisabled,
1031
1137
  className: cn(
1032
- "relative size-full",
1033
- "flex items-center gap-3",
1034
- "px-3 py-2",
1035
- "break-all",
1036
- (isDisabled || isLoading) && "opacity-50",
1037
- childrenClassName
1138
+ "absolute top-0 left-0",
1139
+ "h-full w-full",
1140
+ "rounded-full",
1141
+ "opacity-0",
1142
+ isDisabled ? "cursor-not-allowed" : "cursor-pointer"
1038
1143
  ),
1039
- children
1144
+ ...props
1040
1145
  }
1041
1146
  )
1042
1147
  ]
1043
1148
  }
1044
1149
  );
1045
1150
  }
1046
- function FieldsContainer({
1047
- className = "",
1151
+ function Input({
1152
+ // form context
1153
+ fieldName,
1154
+ setFormData,
1155
+ // ui states
1156
+ isLoading = false,
1157
+ isDisabled = false,
1158
+ isError = false,
1159
+ // base
1160
+ className,
1161
+ inputGroupClassName,
1048
1162
  children,
1049
1163
  ...props
1050
1164
  }) {
1051
- return /* @__PURE__ */ jsx(
1052
- "div",
1165
+ return /* @__PURE__ */ jsxs(
1166
+ InputGroup,
1053
1167
  {
1054
- className: cn("relative flex w-full", className),
1055
- style: { gap: FORM_MIDDLE_GAP_WIDTH },
1056
- ...props,
1057
- children
1168
+ "data-disabled": isDisabled || isLoading,
1169
+ className: cn("h-10", inputGroupClassName),
1170
+ children: [
1171
+ isLoading ? /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(Spinner, {}) }) : /* @__PURE__ */ jsx(
1172
+ InputGroupInput,
1173
+ {
1174
+ disabled: isDisabled || isLoading,
1175
+ "aria-invalid": isError,
1176
+ onChange: (e) => {
1177
+ if (!setFormData || !fieldName) return;
1178
+ setFormData((p) => ({ ...p, [fieldName]: e.target.value }));
1179
+ },
1180
+ className: cn("h-10", className),
1181
+ ...props
1182
+ }
1183
+ ),
1184
+ children
1185
+ ]
1058
1186
  }
1059
1187
  );
1060
1188
  }
1061
- function MainFields({
1062
- className,
1063
- children,
1189
+ function PasswordInput({
1064
1190
  ...props
1065
1191
  }) {
1066
- const sideWidth = FORM_SIDE_FIELDS_WIDTH + FORM_MIDDLE_GAP_WIDTH;
1067
- return /* @__PURE__ */ jsx(
1068
- "div",
1192
+ const [showPassword, setShowPassword] = useState(false);
1193
+ return /* @__PURE__ */ jsx(Input, { type: showPassword ? "text" : "password", ...props, children: /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", children: /* @__PURE__ */ jsx(
1194
+ InputGroupButton,
1069
1195
  {
1070
- className: cn("relative", "flex flex-1 flex-col gap-6", className),
1071
- style: { maxWidth: `calc(100% - ${sideWidth}px)` },
1196
+ "aria-label": showPassword ? "Hide password" : "Show password",
1197
+ title: showPassword ? "Hide password" : "Show password",
1198
+ size: "icon-xs",
1199
+ onClick: () => setShowPassword((prev) => !prev),
1200
+ children: showPassword ? /* @__PURE__ */ jsx(Eye, {}) : /* @__PURE__ */ jsx(EyeOff, {})
1201
+ }
1202
+ ) }) });
1203
+ }
1204
+ function SearchInput({
1205
+ searchString = "",
1206
+ setSearchString,
1207
+ isLoading = false,
1208
+ isDisabled = false,
1209
+ ...props
1210
+ }) {
1211
+ const { t } = useTranslator();
1212
+ const isComposingRef = useRef(false);
1213
+ const [inputValue, setInputValue] = useState(searchString);
1214
+ const handleCompositionEnd = (e) => {
1215
+ isComposingRef.current = false;
1216
+ setInputValue(e.currentTarget.value);
1217
+ setSearchString(e.currentTarget.value);
1218
+ };
1219
+ const handleChange = (e) => {
1220
+ setInputValue(e.target.value);
1221
+ if (!isComposingRef.current) setSearchString(e.target.value);
1222
+ };
1223
+ return /* @__PURE__ */ jsx("div", { className: "relative size-full", children: /* @__PURE__ */ jsx(
1224
+ Input,
1225
+ {
1226
+ isDisabled,
1227
+ placeholder: isLoading ? "" : t("ui.search.text"),
1228
+ autoComplete: "off",
1229
+ value: inputValue,
1230
+ onChange: handleChange,
1231
+ onCompositionStart: () => isComposingRef.current = true,
1232
+ onCompositionEnd: handleCompositionEnd,
1233
+ className: "w-full pr-9",
1234
+ ...props,
1235
+ children: /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", children: isLoading ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(Search, {}) })
1236
+ }
1237
+ ) });
1238
+ }
1239
+ function Select2({
1240
+ // form context
1241
+ fieldName,
1242
+ setFormData,
1243
+ // ui states
1244
+ isDisabled = false,
1245
+ isLoading = false,
1246
+ // base
1247
+ placeholder,
1248
+ label,
1249
+ className,
1250
+ children,
1251
+ ...props
1252
+ }) {
1253
+ return /* @__PURE__ */ jsxs(
1254
+ Select,
1255
+ {
1256
+ onValueChange: (value) => {
1257
+ if (!setFormData || !fieldName) return;
1258
+ setFormData((p) => ({ ...p, [fieldName]: value }));
1259
+ },
1260
+ disabled: isDisabled || isLoading,
1261
+ ...props,
1262
+ children: [
1263
+ /* @__PURE__ */ jsx(
1264
+ SelectTrigger,
1265
+ {
1266
+ className: cn("w-full", className),
1267
+ disabled: isDisabled || isLoading,
1268
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
1269
+ }
1270
+ ),
1271
+ /* @__PURE__ */ jsx(SelectContent, { children: /* @__PURE__ */ jsxs(SelectGroup, { children: [
1272
+ label && /* @__PURE__ */ jsx(SelectLabel, { children: label }),
1273
+ children
1274
+ ] }) })
1275
+ ]
1276
+ }
1277
+ );
1278
+ }
1279
+ function Option({
1280
+ value,
1281
+ isDisabled = false,
1282
+ children
1283
+ }) {
1284
+ return /* @__PURE__ */ jsx(SelectItem, { value, disabled: isDisabled, children });
1285
+ }
1286
+ function PageSizeSelector({
1287
+ pageSize,
1288
+ setPageSize,
1289
+ setPage
1290
+ }) {
1291
+ const { t } = useTranslator();
1292
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-52 items-center gap-4", children: [
1293
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm whitespace-nowrap", children: t("ui.layout.item-list-container.items-per-page.text") }),
1294
+ /* @__PURE__ */ jsxs(
1295
+ Select2,
1296
+ {
1297
+ value: String(pageSize),
1298
+ onValueChange: (value) => {
1299
+ setPageSize(Number(value ?? 20));
1300
+ setPage(1);
1301
+ },
1302
+ className: "w-full",
1303
+ children: [
1304
+ /* @__PURE__ */ jsx(Option, { value: String(20), children: "20" }),
1305
+ /* @__PURE__ */ jsx(Option, { value: String(40), children: "40" }),
1306
+ /* @__PURE__ */ jsx(Option, { value: String(80), children: "80" })
1307
+ ]
1308
+ }
1309
+ )
1310
+ ] });
1311
+ }
1312
+ function Pagination2({
1313
+ page,
1314
+ setPage,
1315
+ pageSize,
1316
+ total = 0
1317
+ }) {
1318
+ const totalPages = Math.ceil(total / pageSize);
1319
+ const isFirst = page === 1;
1320
+ const isLastPage = page === totalPages;
1321
+ const handlePrev = () => {
1322
+ if (isFirst) return;
1323
+ setPage((prev) => prev - 1);
1324
+ };
1325
+ const handleNext = () => {
1326
+ if (isLastPage) return;
1327
+ setPage((prev) => prev + 1);
1328
+ };
1329
+ const siblingsCount = 2;
1330
+ let startPage = Math.max(2, page - siblingsCount);
1331
+ let endPage = Math.min(totalPages - 1, page + siblingsCount);
1332
+ if (page - siblingsCount < 2) {
1333
+ endPage = Math.min(totalPages - 1, endPage + (2 - (page - siblingsCount)));
1334
+ }
1335
+ if (page + siblingsCount > totalPages - 1) {
1336
+ startPage = Math.max(
1337
+ 2,
1338
+ startPage - (page + siblingsCount - (totalPages - 1))
1339
+ );
1340
+ }
1341
+ const pages = [];
1342
+ for (let i = startPage; i <= endPage; i++) {
1343
+ pages.push(i);
1344
+ }
1345
+ const isAtFirstPage = page === 1;
1346
+ const isAtLastPage = page === totalPages;
1347
+ return /* @__PURE__ */ jsx(Pagination, { className: "flex-1", children: /* @__PURE__ */ jsxs(PaginationContent, { children: [
1348
+ /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationPrevious, { disabled: isAtFirstPage, onClick: handlePrev }) }),
1349
+ /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
1350
+ PaginationLink,
1351
+ {
1352
+ isActive: page === 1,
1353
+ disabled: isAtFirstPage,
1354
+ onClick: () => setPage(1),
1355
+ children: "1"
1356
+ }
1357
+ ) }),
1358
+ startPage > 2 && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationEllipsis, {}) }),
1359
+ pages.map((p) => {
1360
+ const isThisPage = page === p;
1361
+ return /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationLink, { isActive: isThisPage, onClick: () => setPage(p), children: p }) }, p);
1362
+ }),
1363
+ endPage < totalPages - 1 && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationEllipsis, {}) }),
1364
+ totalPages > 1 && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
1365
+ PaginationLink,
1366
+ {
1367
+ isActive: page === totalPages,
1368
+ disabled: isAtLastPage,
1369
+ onClick: () => setPage(totalPages),
1370
+ children: totalPages
1371
+ }
1372
+ ) }),
1373
+ /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
1374
+ PaginationNext,
1375
+ {
1376
+ disabled: isAtLastPage || totalPages <= 1,
1377
+ onClick: handleNext
1378
+ }
1379
+ ) })
1380
+ ] }) });
1381
+ }
1382
+ function ListCardsContainer({
1383
+ // pagination
1384
+ page,
1385
+ setPage,
1386
+ pageSize,
1387
+ setPageSize,
1388
+ total,
1389
+ // search string
1390
+ searchString,
1391
+ setSearchString,
1392
+ // ui states
1393
+ isFetching,
1394
+ // base
1395
+ children,
1396
+ headerChildren
1397
+ }) {
1398
+ const { t } = useTranslator();
1399
+ const isNotFound = total === 0;
1400
+ useEffect(() => {
1401
+ setPage(1);
1402
+ }, [searchString, setPage]);
1403
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-6", children: [
1404
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1405
+ headerChildren,
1406
+ /* @__PURE__ */ jsx("div", { className: "ml-auto w-64", children: /* @__PURE__ */ jsx(
1407
+ SearchInput,
1408
+ {
1409
+ searchString,
1410
+ setSearchString,
1411
+ isLoading: isFetching
1412
+ }
1413
+ ) })
1414
+ ] }),
1415
+ /* @__PURE__ */ jsx("div", { className: "max-h-[88%] flex-1 overflow-y-auto", children: isFetching ? /* @__PURE__ */ jsx(Spinner, {}) : isNotFound ? /* @__PURE__ */ jsx("p", { className: "text-sm opacity-50", children: t("ui.no-data.text") }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-4", children }) }),
1416
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1417
+ /* @__PURE__ */ jsx("div", { className: "w-52" }),
1418
+ /* @__PURE__ */ jsx(
1419
+ Pagination2,
1420
+ {
1421
+ page,
1422
+ setPage,
1423
+ pageSize,
1424
+ total
1425
+ }
1426
+ ),
1427
+ /* @__PURE__ */ jsx(
1428
+ PageSizeSelector,
1429
+ {
1430
+ pageSize,
1431
+ setPageSize,
1432
+ setPage
1433
+ }
1434
+ )
1435
+ ] })
1436
+ ] });
1437
+ }
1438
+ function Form({
1439
+ onSubmit,
1440
+ className,
1441
+ ...props
1442
+ }) {
1443
+ const handleSubmit = (e) => {
1444
+ if (!onSubmit) return;
1445
+ e.preventDefault();
1446
+ void onSubmit(e);
1447
+ };
1448
+ return /* @__PURE__ */ jsx("form", { className: cn(className), onSubmit: handleSubmit, ...props });
1449
+ }
1450
+ function Field({
1451
+ label,
1452
+ isRequired = false,
1453
+ hint,
1454
+ labelChildren,
1455
+ children,
1456
+ ...props
1457
+ }) {
1458
+ return /* @__PURE__ */ jsxs("div", { className: "grid w-full items-center gap-3", children: [
1459
+ /* @__PURE__ */ jsxs("span", { className: "flex gap-2", children: [
1460
+ /* @__PURE__ */ jsxs(Label, { className: "flex gap-1 truncate", ...props, children: [
1461
+ label,
1462
+ isRequired && /* @__PURE__ */ jsx(Asterisk, { className: "text-destructive size-3" }),
1463
+ hint && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground ml-2 text-xs", children: hint })
1464
+ ] }),
1465
+ /* @__PURE__ */ jsx("span", { children: labelChildren })
1466
+ ] }),
1467
+ children
1468
+ ] });
1469
+ }
1470
+ function FieldBody({
1471
+ // ui states
1472
+ isLoading = false,
1473
+ isDisabled = false,
1474
+ isEmpty = false,
1475
+ // base
1476
+ className,
1477
+ backgroundClassName,
1478
+ childrenClassName,
1479
+ children,
1480
+ ...props
1481
+ }) {
1482
+ const { t } = useTranslator();
1483
+ return /* @__PURE__ */ jsxs(
1484
+ "div",
1485
+ {
1486
+ className: cn(
1487
+ className,
1488
+ "relative",
1489
+ "min-h-9 w-full min-w-0",
1490
+ "flex items-center",
1491
+ "text-sm"
1492
+ ),
1493
+ ...props,
1494
+ children: [
1495
+ /* @__PURE__ */ jsx(
1496
+ "div",
1497
+ {
1498
+ className: cn(
1499
+ "absolute size-full",
1500
+ "dark:bg-input/30 bg-foreground/2 rounded-md",
1501
+ backgroundClassName
1502
+ )
1503
+ }
1504
+ ),
1505
+ isLoading && /* @__PURE__ */ jsx("div", { className: "px-3", children: /* @__PURE__ */ jsx(Spinner, {}) }),
1506
+ !isLoading && (!children || isEmpty) && /* @__PURE__ */ jsx("div", { className: "flex-center h-9 px-3 opacity-50", children: /* @__PURE__ */ jsx("p", { className: "opacity-50", children: t("ui.no-data.text") }) }),
1507
+ !isLoading && children && !isEmpty && /* @__PURE__ */ jsx(
1508
+ "div",
1509
+ {
1510
+ className: cn(
1511
+ "relative size-full",
1512
+ "flex items-center gap-3",
1513
+ "px-3 py-2",
1514
+ "break-all",
1515
+ (isDisabled || isLoading) && "opacity-50",
1516
+ childrenClassName
1517
+ ),
1518
+ children
1519
+ }
1520
+ )
1521
+ ]
1522
+ }
1523
+ );
1524
+ }
1525
+ function FieldsContainer({
1526
+ className = "",
1527
+ children,
1528
+ ...props
1529
+ }) {
1530
+ return /* @__PURE__ */ jsx(
1531
+ "div",
1532
+ {
1533
+ className: cn("relative flex w-full", className),
1534
+ style: { gap: FORM_MIDDLE_GAP_WIDTH },
1535
+ ...props,
1536
+ children
1537
+ }
1538
+ );
1539
+ }
1540
+ function MainFields({
1541
+ className,
1542
+ children,
1543
+ ...props
1544
+ }) {
1545
+ const sideWidth = FORM_SIDE_FIELDS_WIDTH + FORM_MIDDLE_GAP_WIDTH;
1546
+ return /* @__PURE__ */ jsx(
1547
+ "div",
1548
+ {
1549
+ className: cn("relative", "flex flex-1 flex-col gap-6", className),
1550
+ style: { maxWidth: `calc(100% - ${sideWidth}px)` },
1072
1551
  ...props,
1073
1552
  children
1074
1553
  }
@@ -1188,286 +1667,6 @@ function ExpandableList({
1188
1667
  }
1189
1668
  );
1190
1669
  }
1191
- function Input({
1192
- // form context
1193
- fieldName,
1194
- setFormData,
1195
- // ui states
1196
- isLoading = false,
1197
- isDisabled = false,
1198
- isError = false,
1199
- // base
1200
- className,
1201
- inputGroupClassName,
1202
- children,
1203
- ...props
1204
- }) {
1205
- return /* @__PURE__ */ jsxs(
1206
- InputGroup,
1207
- {
1208
- "data-disabled": isDisabled || isLoading,
1209
- className: cn("h-10", inputGroupClassName),
1210
- children: [
1211
- isLoading ? /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(Spinner, {}) }) : /* @__PURE__ */ jsx(
1212
- InputGroupInput,
1213
- {
1214
- disabled: isDisabled || isLoading,
1215
- "aria-invalid": isError,
1216
- onChange: (e) => {
1217
- if (!setFormData || !fieldName) return;
1218
- setFormData((p) => ({ ...p, [fieldName]: e.target.value }));
1219
- },
1220
- className: cn("h-10", className),
1221
- ...props
1222
- }
1223
- ),
1224
- children
1225
- ]
1226
- }
1227
- );
1228
- }
1229
- function PasswordInput({
1230
- ...props
1231
- }) {
1232
- const [showPassword, setShowPassword] = useState(false);
1233
- return /* @__PURE__ */ jsx(Input, { type: showPassword ? "text" : "password", ...props, children: /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", children: /* @__PURE__ */ jsx(
1234
- InputGroupButton,
1235
- {
1236
- "aria-label": showPassword ? "Hide password" : "Show password",
1237
- title: showPassword ? "Hide password" : "Show password",
1238
- size: "icon-xs",
1239
- onClick: () => setShowPassword((prev) => !prev),
1240
- children: showPassword ? /* @__PURE__ */ jsx(Eye, {}) : /* @__PURE__ */ jsx(EyeOff, {})
1241
- }
1242
- ) }) });
1243
- }
1244
- function Checkbox({
1245
- // form context
1246
- fieldName,
1247
- formData,
1248
- setFormData,
1249
- // ui states
1250
- isLoading = false,
1251
- isDisabled = false,
1252
- // base
1253
- isDisplay = false,
1254
- className = "",
1255
- ...props
1256
- }) {
1257
- isDisabled = isDisabled || isLoading || isDisplay;
1258
- const isChecked = !!formData?.[fieldName ?? ""];
1259
- return /* @__PURE__ */ jsxs(
1260
- "div",
1261
- {
1262
- className: cn(
1263
- className,
1264
- "group relative",
1265
- "h-6 w-12 min-w-12",
1266
- "p-1",
1267
- "rounded-full",
1268
- "overflow-hidden",
1269
- "shadow-inner",
1270
- !isChecked || isLoading ? "bg-secondary" : "bg-success",
1271
- "transition"
1272
- ),
1273
- children: [
1274
- /* @__PURE__ */ jsx(
1275
- "div",
1276
- {
1277
- className: cn(
1278
- "size-4",
1279
- "rounded-full",
1280
- "bg-white",
1281
- "shadow",
1282
- isChecked && "translate-x-6",
1283
- isLoading && "opacity-0",
1284
- !isDisabled && "group-hover:scale-95 group-active:scale-90",
1285
- "duration-200"
1286
- )
1287
- }
1288
- ),
1289
- isLoading && /* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1", children: /* @__PURE__ */ jsx(Spinner, {}) }),
1290
- /* @__PURE__ */ jsx(
1291
- "input",
1292
- {
1293
- type: "checkbox",
1294
- onChange: (e) => {
1295
- if (!setFormData || !fieldName) return;
1296
- setFormData((p) => ({ ...p, [fieldName]: e.target.checked }));
1297
- },
1298
- checked: isChecked,
1299
- disabled: isDisabled,
1300
- className: cn(
1301
- "absolute top-0 left-0",
1302
- "h-full w-full",
1303
- "rounded-full",
1304
- "opacity-0",
1305
- isDisabled ? "cursor-not-allowed" : "cursor-pointer"
1306
- ),
1307
- ...props
1308
- }
1309
- )
1310
- ]
1311
- }
1312
- );
1313
- }
1314
- function Textarea2({
1315
- // form context
1316
- fieldName,
1317
- setFormData,
1318
- // ui states
1319
- isLoading = false,
1320
- isDisabled = false,
1321
- isError = false,
1322
- // base
1323
- ...props
1324
- }) {
1325
- isDisabled = isDisabled || isLoading;
1326
- const textareaRef = useRef(null);
1327
- const resetHeight = (element) => {
1328
- element.style.height = "40px";
1329
- element.style.height = `${element.scrollHeight + 2}px`;
1330
- };
1331
- useEffect(() => {
1332
- if (!textareaRef.current) return;
1333
- resetHeight(textareaRef.current);
1334
- }, [props.value, isLoading]);
1335
- return /* @__PURE__ */ jsx(Fragment, { children: isLoading ? /* @__PURE__ */ jsx(InputGroup, { "data-disabled": isDisabled, className: "h-10", children: /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(Spinner, {}) }) }) : /* @__PURE__ */ jsx(
1336
- Textarea,
1337
- {
1338
- ref: textareaRef,
1339
- disabled: isDisabled,
1340
- "aria-invalid": isError,
1341
- value: isLoading ? "" : props.value,
1342
- onChange: (e) => {
1343
- resetHeight(e.target);
1344
- if (!setFormData || !fieldName) return;
1345
- setFormData((p) => ({ ...p, [fieldName]: e.target.value }));
1346
- },
1347
- ...props
1348
- }
1349
- ) });
1350
- }
1351
- function ArrayInput({
1352
- // form context
1353
- formData,
1354
- fieldName,
1355
- setFormData,
1356
- // ui states
1357
- isLoading = false,
1358
- isDisabled = false,
1359
- isResizable = false,
1360
- errors,
1361
- // base
1362
- placeholder = "",
1363
- ...props
1364
- }) {
1365
- isDisabled = isDisabled || isLoading;
1366
- const rawValue = formData?.[fieldName ?? ""];
1367
- const values = Array.isArray(rawValue) && rawValue.length > 0 ? rawValue.map(String) : [""];
1368
- const errorIndexes = new Set(
1369
- errors.filter((e) => e.startsWith(fieldName ?? "")).map((e) => e.split(".").pop()).map(Number)
1370
- );
1371
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: values.map((value, index) => {
1372
- const isError = errorIndexes.has(index);
1373
- return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1374
- /* @__PURE__ */ jsxs("div", { className: "flex-center absolute top-0 left-0 z-10 h-full", children: [
1375
- /* @__PURE__ */ jsx("span", { className: "min-w-10 pl-0.5 text-center text-sm opacity-50", children: Array.isArray(rawValue) && rawValue.length === 0 ? "0" : index + 1 }),
1376
- /* @__PURE__ */ jsx(Separator, { orientation: "vertical" })
1377
- ] }),
1378
- /* @__PURE__ */ jsx(
1379
- Textarea2,
1380
- {
1381
- disabled: isDisabled,
1382
- placeholder: isLoading ? "" : placeholder,
1383
- value,
1384
- onChange: (e) => {
1385
- if (!setFormData || !fieldName) return;
1386
- const copy = [...values];
1387
- copy[index] = e.target.value;
1388
- setFormData((p) => ({ ...p, [fieldName]: copy }));
1389
- },
1390
- onKeyDown: (e) => {
1391
- if (e.key === "Enter" && !isResizable) {
1392
- e.preventDefault();
1393
- }
1394
- },
1395
- isError,
1396
- className: cn("pr-20 pl-13", !isResizable && "resize-none"),
1397
- ...props
1398
- }
1399
- ),
1400
- /* @__PURE__ */ jsxs("div", { className: "flex-center absolute top-1 right-1 gap-1", children: [
1401
- /* @__PURE__ */ jsx(
1402
- Button2,
1403
- {
1404
- variant: "outline",
1405
- type: "button",
1406
- size: "icon-sm",
1407
- className: "rounded-sm",
1408
- onClick: () => {
1409
- if (!setFormData || !fieldName) return;
1410
- const copy = values.filter((_, i) => i !== index);
1411
- setFormData((p) => ({ ...p, [fieldName]: copy }));
1412
- },
1413
- children: /* @__PURE__ */ jsx(CircleX, { className: "text-destructive" })
1414
- }
1415
- ),
1416
- /* @__PURE__ */ jsx(
1417
- Button2,
1418
- {
1419
- variant: "outline",
1420
- type: "button",
1421
- size: "icon-sm",
1422
- className: "rounded-sm",
1423
- onClick: () => {
1424
- if (!setFormData || !fieldName) return;
1425
- const copy = [...values];
1426
- copy.splice(index + 1, 0, "");
1427
- setFormData((p) => ({ ...p, [fieldName]: copy }));
1428
- },
1429
- children: /* @__PURE__ */ jsx(CirclePlus, { className: "text-success" })
1430
- }
1431
- )
1432
- ] })
1433
- ] }, index);
1434
- }) });
1435
- }
1436
- function SearchInput({
1437
- searchString = "",
1438
- setSearchString,
1439
- isLoading = false,
1440
- isDisabled = false,
1441
- ...props
1442
- }) {
1443
- const { t } = useTranslator();
1444
- const isComposingRef = useRef(false);
1445
- const [inputValue, setInputValue] = useState(searchString);
1446
- const handleCompositionEnd = (e) => {
1447
- isComposingRef.current = false;
1448
- setInputValue(e.currentTarget.value);
1449
- setSearchString(e.currentTarget.value);
1450
- };
1451
- const handleChange = (e) => {
1452
- setInputValue(e.target.value);
1453
- if (!isComposingRef.current) setSearchString(e.target.value);
1454
- };
1455
- return /* @__PURE__ */ jsx("div", { className: "relative size-full", children: /* @__PURE__ */ jsx(
1456
- Input,
1457
- {
1458
- isDisabled,
1459
- placeholder: isLoading ? "" : t("ui.search.text"),
1460
- autoComplete: "off",
1461
- value: inputValue,
1462
- onChange: handleChange,
1463
- onCompositionStart: () => isComposingRef.current = true,
1464
- onCompositionEnd: handleCompositionEnd,
1465
- className: "w-full pr-9",
1466
- ...props,
1467
- children: /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", children: isLoading ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(Search, {}) })
1468
- }
1469
- ) });
1470
- }
1471
1670
  function IndexField({
1472
1671
  existingIndexes,
1473
1672
  availableIndex = 1,
@@ -2318,4 +2517,4 @@ function createChangePasswordPage({
2318
2517
  };
2319
2518
  }
2320
2519
 
2321
- export { AdminProvider, ArrayInput, Button2 as Button, Checkbox, ContentContainer, LayoutSkeleton as ContentSkeleton, ControlFields, Field, FieldBody, FieldsContainer, Form, IndexField, Input, MainFields, NavMain, PageHeader, PasswordInput, ReturnButton, SearchInput, SideFields, SlugField, Textarea2 as Textarea, ThemeProvider, createAdminInitializer, createChangePasswordPage, createEmailUnverifiedPage, createForgotPasswordPage, createI18nSelector, createNavbar, createRequestInterceptor, createResetPasswordPage, createResponseInterceptor, createSignInPage, createSignOutButton, createSmartFetch, createUseCommand, createUseQuery, createVerifyEmailPage, handleToast, useAdmin };
2520
+ export { AdminProvider, ArrayInput, Button2 as Button, Checkbox, ContentContainer, ControlFields, Field, FieldBody, FieldsContainer, Form, IndexField, Input, LayoutSkeleton, ListCardsContainer, MainFields, NavMain, PageHeader, PasswordInput, ReturnButton, SearchInput, SideFields, SlugField, Textarea2 as Textarea, ThemeProvider, createAdminInitializer, createChangePasswordPage, createEmailUnverifiedPage, createForgotPasswordPage, createI18nSelector, createNavbar, createRequestInterceptor, createResetPasswordPage, createResponseInterceptor, createSignInPage, createSignOutButton, createSmartFetch, createUseCommand, createUseQuery, createVerifyEmailPage, handleToast, useAdmin };