@nubitio/crud 0.5.15 → 0.5.16

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.cjs CHANGED
@@ -28,7 +28,6 @@ let react_dom = require("react-dom");
28
28
  let _nubitio_ui = require("@nubitio/ui");
29
29
  let react_jsx_runtime = require("react/jsx-runtime");
30
30
  let _nubitio_core = require("@nubitio/core");
31
- let react_dropzone = require("react-dropzone");
32
31
  let _tanstack_react_query = require("@tanstack/react-query");
33
32
  //#region packages/crud/crud/defineResource.ts
34
33
  const stringResourceCache = /* @__PURE__ */ new Map();
@@ -1243,9 +1242,6 @@ const enumTypeModule = {
1243
1242
  };
1244
1243
  //#endregion
1245
1244
  //#region packages/crud/form/FileUploadField.tsx
1246
- function cx$1(...values) {
1247
- return values.filter(Boolean).join(" ");
1248
- }
1249
1245
  function resolveMediaPath(media) {
1250
1246
  if (!media) return null;
1251
1247
  const path = media["path"];
@@ -1271,27 +1267,6 @@ function resolveMediaIri(uploadUrl, media) {
1271
1267
  function isImageMimeType(mimeType) {
1272
1268
  return !!mimeType && mimeType.startsWith("image/");
1273
1269
  }
1274
- function buildDropzoneAccept(accept) {
1275
- if (!accept || accept === "*/*" || accept === "*") return void 0;
1276
- if (accept === "image/*") return {
1277
- "image/png": [".png"],
1278
- "image/jpeg": [".jpg", ".jpeg"],
1279
- "image/webp": [".webp"],
1280
- "image/gif": [".gif"]
1281
- };
1282
- if (accept.includes(",")) return accept.split(",").reduce((acc, token) => {
1283
- const trimmed = token.trim();
1284
- if (!trimmed) return acc;
1285
- if (trimmed.startsWith(".")) {
1286
- acc["application/octet-stream"] = [...acc["application/octet-stream"] ?? [], trimmed];
1287
- return acc;
1288
- }
1289
- acc[trimmed] = [];
1290
- return acc;
1291
- }, {});
1292
- if (accept.startsWith(".")) return { "application/octet-stream": [accept] };
1293
- return { [accept]: [] };
1294
- }
1295
1270
  async function uploadMediaFile(file, uploadUrl, httpClient) {
1296
1271
  const body = new FormData();
1297
1272
  body.append("file", file);
@@ -1371,17 +1346,6 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
1371
1346
  t,
1372
1347
  uploadUrl
1373
1348
  ]);
1374
- const { getRootProps, getInputProps, isDragActive, open } = (0, react_dropzone.useDropzone)({
1375
- accept: buildDropzoneAccept(field.accept),
1376
- disabled: disabled || readOnly || status === "uploading",
1377
- multiple: false,
1378
- noClick: !!(previewUrl || fileName),
1379
- noKeyboard: !!(previewUrl || fileName),
1380
- onDrop: (acceptedFiles) => {
1381
- const file = acceptedFiles[0];
1382
- if (file) uploadFile(file);
1383
- }
1384
- });
1385
1349
  const handleClear = () => {
1386
1350
  revokeLocalPreview();
1387
1351
  setPreviewUrl(null);
@@ -1391,104 +1355,35 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
1391
1355
  setErrorMessage(null);
1392
1356
  onCleared(field.name);
1393
1357
  };
1394
- const isInteractive = !disabled && !readOnly;
1395
- const hasContent = !!(previewUrl || fileName);
1396
- const placeholderIcon = imageMode ? "ph-image" : "ph-file-arrow-up";
1397
- const placeholderTitle = isDragActive ? t("form.fileUploadDrop") : imageMode ? t("form.imageUploadPrompt") : t("form.fileUploadPrompt");
1398
- const placeholderHint = imageMode ? t("form.imageUploadHint") : t("form.fileUploadHint");
1399
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1400
- className: cx$1("nb-form__file-upload", imageMode && "nb-form__file-upload--image", invalid && "nb-form__file-upload--invalid"),
1401
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1402
- ...getRootProps({ className: cx$1("nb-form__file-upload-zone", isDragActive && "nb-form__file-upload-zone--active", hasContent && "nb-form__file-upload-zone--filled", status === "uploading" && "nb-form__file-upload-zone--uploading", !isInteractive && "nb-form__file-upload-zone--disabled") }),
1403
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { ...getInputProps({
1404
- id: `nb-form-${field.name}`,
1405
- "aria-label": field.label
1406
- }) }), hasContent ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1407
- previewUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1408
- className: "nb-form__file-upload-preview",
1409
- src: previewUrl,
1410
- alt: field.label
1411
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1412
- className: "nb-form__file-upload-file",
1413
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1414
- className: "nb-form__file-upload-file-icon",
1415
- "aria-hidden": "true",
1416
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: "ph ph-file" })
1417
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1418
- className: "nb-form__file-upload-file-meta",
1419
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1420
- className: "nb-form__file-upload-file-name",
1421
- children: fileName
1422
- }), fileUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1423
- className: "nb-form__file-upload-file-link",
1424
- href: fileUrl,
1425
- target: "_blank",
1426
- rel: "noreferrer",
1427
- onClick: (event) => event.stopPropagation(),
1428
- children: t("form.fileUploadOpen")
1429
- })]
1430
- })]
1431
- }),
1432
- status === "uploading" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1433
- className: "nb-form__file-upload-overlay",
1434
- "aria-live": "polite",
1435
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1436
- className: "nb-form__file-upload-spinner",
1437
- "aria-hidden": "true"
1438
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("form.fileUploading") })]
1439
- }),
1440
- isInteractive && status !== "uploading" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1441
- className: "nb-form__file-upload-actions",
1442
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1443
- type: "button",
1444
- className: "nb-form__file-upload-action",
1445
- onClick: (event) => {
1446
- event.stopPropagation();
1447
- open();
1448
- },
1449
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
1450
- className: "ph ph-arrows-clockwise",
1451
- "aria-hidden": "true"
1452
- }), t("form.fileUploadReplace")]
1453
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1454
- type: "button",
1455
- className: "nb-form__file-upload-action nb-form__file-upload-action--danger",
1456
- onClick: (event) => {
1457
- event.stopPropagation();
1458
- handleClear();
1459
- },
1460
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
1461
- className: "ph ph-trash",
1462
- "aria-hidden": "true"
1463
- }), t("form.fileUploadRemove")]
1464
- })]
1465
- })
1466
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1467
- className: "nb-form__file-upload-placeholder",
1468
- children: [
1469
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1470
- className: "nb-form__file-upload-icon",
1471
- "aria-hidden": "true",
1472
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: `ph ${placeholderIcon}` })
1473
- }),
1474
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1475
- className: "nb-form__file-upload-title",
1476
- children: placeholderTitle
1477
- }),
1478
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1479
- className: "nb-form__file-upload-hint",
1480
- children: placeholderHint
1481
- })
1482
- ]
1483
- })]
1484
- }), errorMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1485
- className: "nb-form__file-upload-error",
1486
- role: "alert",
1487
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
1488
- className: "ph ph-warning-circle",
1489
- "aria-hidden": "true"
1490
- }), errorMessage]
1491
- })]
1358
+ const value = {
1359
+ fileName,
1360
+ fileUrl,
1361
+ previewUrl
1362
+ };
1363
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FileDropzone, {
1364
+ accept: field.accept,
1365
+ disabled,
1366
+ readOnly,
1367
+ invalid,
1368
+ image: imageMode,
1369
+ value,
1370
+ uploading: status === "uploading",
1371
+ error: errorMessage,
1372
+ inputId: `nb-form-${field.name}`,
1373
+ inputLabel: field.label,
1374
+ labels: {
1375
+ dropPrompt: t("form.fileUploadDrop"),
1376
+ prompt: t("form.fileUploadPrompt"),
1377
+ imagePrompt: t("form.imageUploadPrompt"),
1378
+ hint: t("form.fileUploadHint"),
1379
+ imageHint: t("form.imageUploadHint"),
1380
+ uploading: t("form.fileUploading"),
1381
+ replace: t("form.fileUploadReplace"),
1382
+ remove: t("form.fileUploadRemove"),
1383
+ open: t("form.fileUploadOpen")
1384
+ },
1385
+ onFileSelect: (file) => void uploadFile(file),
1386
+ onClear: handleClear
1492
1387
  });
1493
1388
  }
1494
1389
  function isImageFileField(field) {
package/dist/index.mjs CHANGED
@@ -1,10 +1,9 @@
1
1
  import React, { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
2
2
  import { Route, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
3
3
  import { createPortal } from "react-dom";
4
- import { AppDialog, AppDropdown, Badge, Button, ConfirmDialog, DatePicker, DateRangePicker, Drawer, EmptyState, IconButton, Skeleton, Timeline, TimelineItem } from "@nubitio/ui";
4
+ import { AppDialog, AppDropdown, Badge, Button, ConfirmDialog, DatePicker, DateRangePicker, Drawer, EmptyState, FileDropzone, IconButton, Skeleton, Timeline, TimelineItem } from "@nubitio/ui";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { createCrudEvents, createScopedEventBus, getCoreCurrency, getCoreLocale, getCoreTimezone, useCoreHttpClient, useCoreRuntime, useCoreTranslation, useEvents, useMercureSubscription } from "@nubitio/core";
7
- import { useDropzone } from "react-dropzone";
8
7
  import { useQueryClient } from "@tanstack/react-query";
9
8
  //#region packages/crud/crud/defineResource.ts
10
9
  const stringResourceCache = /* @__PURE__ */ new Map();
@@ -1219,9 +1218,6 @@ const enumTypeModule = {
1219
1218
  };
1220
1219
  //#endregion
1221
1220
  //#region packages/crud/form/FileUploadField.tsx
1222
- function cx$1(...values) {
1223
- return values.filter(Boolean).join(" ");
1224
- }
1225
1221
  function resolveMediaPath(media) {
1226
1222
  if (!media) return null;
1227
1223
  const path = media["path"];
@@ -1247,27 +1243,6 @@ function resolveMediaIri(uploadUrl, media) {
1247
1243
  function isImageMimeType(mimeType) {
1248
1244
  return !!mimeType && mimeType.startsWith("image/");
1249
1245
  }
1250
- function buildDropzoneAccept(accept) {
1251
- if (!accept || accept === "*/*" || accept === "*") return void 0;
1252
- if (accept === "image/*") return {
1253
- "image/png": [".png"],
1254
- "image/jpeg": [".jpg", ".jpeg"],
1255
- "image/webp": [".webp"],
1256
- "image/gif": [".gif"]
1257
- };
1258
- if (accept.includes(",")) return accept.split(",").reduce((acc, token) => {
1259
- const trimmed = token.trim();
1260
- if (!trimmed) return acc;
1261
- if (trimmed.startsWith(".")) {
1262
- acc["application/octet-stream"] = [...acc["application/octet-stream"] ?? [], trimmed];
1263
- return acc;
1264
- }
1265
- acc[trimmed] = [];
1266
- return acc;
1267
- }, {});
1268
- if (accept.startsWith(".")) return { "application/octet-stream": [accept] };
1269
- return { [accept]: [] };
1270
- }
1271
1246
  async function uploadMediaFile(file, uploadUrl, httpClient) {
1272
1247
  const body = new FormData();
1273
1248
  body.append("file", file);
@@ -1347,17 +1322,6 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
1347
1322
  t,
1348
1323
  uploadUrl
1349
1324
  ]);
1350
- const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
1351
- accept: buildDropzoneAccept(field.accept),
1352
- disabled: disabled || readOnly || status === "uploading",
1353
- multiple: false,
1354
- noClick: !!(previewUrl || fileName),
1355
- noKeyboard: !!(previewUrl || fileName),
1356
- onDrop: (acceptedFiles) => {
1357
- const file = acceptedFiles[0];
1358
- if (file) uploadFile(file);
1359
- }
1360
- });
1361
1325
  const handleClear = () => {
1362
1326
  revokeLocalPreview();
1363
1327
  setPreviewUrl(null);
@@ -1367,104 +1331,35 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
1367
1331
  setErrorMessage(null);
1368
1332
  onCleared(field.name);
1369
1333
  };
1370
- const isInteractive = !disabled && !readOnly;
1371
- const hasContent = !!(previewUrl || fileName);
1372
- const placeholderIcon = imageMode ? "ph-image" : "ph-file-arrow-up";
1373
- const placeholderTitle = isDragActive ? t("form.fileUploadDrop") : imageMode ? t("form.imageUploadPrompt") : t("form.fileUploadPrompt");
1374
- const placeholderHint = imageMode ? t("form.imageUploadHint") : t("form.fileUploadHint");
1375
- return /* @__PURE__ */ jsxs("div", {
1376
- className: cx$1("nb-form__file-upload", imageMode && "nb-form__file-upload--image", invalid && "nb-form__file-upload--invalid"),
1377
- children: [/* @__PURE__ */ jsxs("div", {
1378
- ...getRootProps({ className: cx$1("nb-form__file-upload-zone", isDragActive && "nb-form__file-upload-zone--active", hasContent && "nb-form__file-upload-zone--filled", status === "uploading" && "nb-form__file-upload-zone--uploading", !isInteractive && "nb-form__file-upload-zone--disabled") }),
1379
- children: [/* @__PURE__ */ jsx("input", { ...getInputProps({
1380
- id: `nb-form-${field.name}`,
1381
- "aria-label": field.label
1382
- }) }), hasContent ? /* @__PURE__ */ jsxs(Fragment, { children: [
1383
- previewUrl ? /* @__PURE__ */ jsx("img", {
1384
- className: "nb-form__file-upload-preview",
1385
- src: previewUrl,
1386
- alt: field.label
1387
- }) : /* @__PURE__ */ jsxs("div", {
1388
- className: "nb-form__file-upload-file",
1389
- children: [/* @__PURE__ */ jsx("span", {
1390
- className: "nb-form__file-upload-file-icon",
1391
- "aria-hidden": "true",
1392
- children: /* @__PURE__ */ jsx("i", { className: "ph ph-file" })
1393
- }), /* @__PURE__ */ jsxs("div", {
1394
- className: "nb-form__file-upload-file-meta",
1395
- children: [/* @__PURE__ */ jsx("span", {
1396
- className: "nb-form__file-upload-file-name",
1397
- children: fileName
1398
- }), fileUrl && /* @__PURE__ */ jsx("a", {
1399
- className: "nb-form__file-upload-file-link",
1400
- href: fileUrl,
1401
- target: "_blank",
1402
- rel: "noreferrer",
1403
- onClick: (event) => event.stopPropagation(),
1404
- children: t("form.fileUploadOpen")
1405
- })]
1406
- })]
1407
- }),
1408
- status === "uploading" && /* @__PURE__ */ jsxs("div", {
1409
- className: "nb-form__file-upload-overlay",
1410
- "aria-live": "polite",
1411
- children: [/* @__PURE__ */ jsx("span", {
1412
- className: "nb-form__file-upload-spinner",
1413
- "aria-hidden": "true"
1414
- }), /* @__PURE__ */ jsx("span", { children: t("form.fileUploading") })]
1415
- }),
1416
- isInteractive && status !== "uploading" && /* @__PURE__ */ jsxs("div", {
1417
- className: "nb-form__file-upload-actions",
1418
- children: [/* @__PURE__ */ jsxs("button", {
1419
- type: "button",
1420
- className: "nb-form__file-upload-action",
1421
- onClick: (event) => {
1422
- event.stopPropagation();
1423
- open();
1424
- },
1425
- children: [/* @__PURE__ */ jsx("i", {
1426
- className: "ph ph-arrows-clockwise",
1427
- "aria-hidden": "true"
1428
- }), t("form.fileUploadReplace")]
1429
- }), /* @__PURE__ */ jsxs("button", {
1430
- type: "button",
1431
- className: "nb-form__file-upload-action nb-form__file-upload-action--danger",
1432
- onClick: (event) => {
1433
- event.stopPropagation();
1434
- handleClear();
1435
- },
1436
- children: [/* @__PURE__ */ jsx("i", {
1437
- className: "ph ph-trash",
1438
- "aria-hidden": "true"
1439
- }), t("form.fileUploadRemove")]
1440
- })]
1441
- })
1442
- ] }) : /* @__PURE__ */ jsxs("div", {
1443
- className: "nb-form__file-upload-placeholder",
1444
- children: [
1445
- /* @__PURE__ */ jsx("span", {
1446
- className: "nb-form__file-upload-icon",
1447
- "aria-hidden": "true",
1448
- children: /* @__PURE__ */ jsx("i", { className: `ph ${placeholderIcon}` })
1449
- }),
1450
- /* @__PURE__ */ jsx("span", {
1451
- className: "nb-form__file-upload-title",
1452
- children: placeholderTitle
1453
- }),
1454
- /* @__PURE__ */ jsx("span", {
1455
- className: "nb-form__file-upload-hint",
1456
- children: placeholderHint
1457
- })
1458
- ]
1459
- })]
1460
- }), errorMessage && /* @__PURE__ */ jsxs("span", {
1461
- className: "nb-form__file-upload-error",
1462
- role: "alert",
1463
- children: [/* @__PURE__ */ jsx("i", {
1464
- className: "ph ph-warning-circle",
1465
- "aria-hidden": "true"
1466
- }), errorMessage]
1467
- })]
1334
+ const value = {
1335
+ fileName,
1336
+ fileUrl,
1337
+ previewUrl
1338
+ };
1339
+ return /* @__PURE__ */ jsx(FileDropzone, {
1340
+ accept: field.accept,
1341
+ disabled,
1342
+ readOnly,
1343
+ invalid,
1344
+ image: imageMode,
1345
+ value,
1346
+ uploading: status === "uploading",
1347
+ error: errorMessage,
1348
+ inputId: `nb-form-${field.name}`,
1349
+ inputLabel: field.label,
1350
+ labels: {
1351
+ dropPrompt: t("form.fileUploadDrop"),
1352
+ prompt: t("form.fileUploadPrompt"),
1353
+ imagePrompt: t("form.imageUploadPrompt"),
1354
+ hint: t("form.fileUploadHint"),
1355
+ imageHint: t("form.imageUploadHint"),
1356
+ uploading: t("form.fileUploading"),
1357
+ replace: t("form.fileUploadReplace"),
1358
+ remove: t("form.fileUploadRemove"),
1359
+ open: t("form.fileUploadOpen")
1360
+ },
1361
+ onFileSelect: (file) => void uploadFile(file),
1362
+ onClear: handleClear
1468
1363
  });
1469
1364
  }
1470
1365
  function isImageFileField(field) {
package/dist/style.css CHANGED
@@ -1705,471 +1705,6 @@ html[data-density=compact] .nb-datagrid .nb-badge {
1705
1705
  box-shadow: none;
1706
1706
  }
1707
1707
  }
1708
- .nb-form__file-upload {
1709
- display: flex;
1710
- flex-direction: column;
1711
- gap: var(--space-1);
1712
- width: 100%;
1713
- }
1714
-
1715
- .nb-form__file-upload-zone {
1716
- align-items: center;
1717
- background: var(--surface-1);
1718
- border: 1px dashed var(--border-color);
1719
- border-radius: var(--radius-lg);
1720
- box-sizing: border-box;
1721
- cursor: pointer;
1722
- display: flex;
1723
- justify-content: center;
1724
- min-height: 112px;
1725
- overflow: hidden;
1726
- position: relative;
1727
- transition: border-color var(--transition-base), background var(--transition-base), box-shadow var(--transition-base);
1728
- width: 100%;
1729
- }
1730
- .nb-form__file-upload-zone:hover:not(.nb-form__file-upload-zone--disabled):not(.nb-form__file-upload-zone--filled) {
1731
- background: color-mix(in srgb, var(--accent-color) 4%, var(--surface-1));
1732
- border-color: var(--accent-color);
1733
- }
1734
- .nb-form__file-upload-zone--active {
1735
- background: color-mix(in srgb, var(--accent-color) 8%, var(--surface-1));
1736
- border-color: var(--accent-color);
1737
- }
1738
- .nb-form__file-upload-zone--filled {
1739
- border-style: solid;
1740
- cursor: default;
1741
- min-height: 88px;
1742
- }
1743
- .nb-form__file-upload-zone--uploading {
1744
- pointer-events: none;
1745
- }
1746
- .nb-form__file-upload-zone--disabled {
1747
- cursor: not-allowed;
1748
- opacity: 0.72;
1749
- }
1750
- .nb-form__file-upload-zone:focus-visible {
1751
- box-shadow: 0 0 0 3px var(--focus-ring-color);
1752
- outline: none;
1753
- }
1754
-
1755
- .nb-form__file-upload--image .nb-form__file-upload-zone {
1756
- min-height: 168px;
1757
- }
1758
-
1759
- .nb-form__file-upload--image .nb-form__file-upload-zone--filled {
1760
- min-height: 180px;
1761
- }
1762
-
1763
- .nb-form__file-upload--invalid .nb-form__file-upload-zone {
1764
- border-color: var(--error-color);
1765
- }
1766
-
1767
- .nb-form__file-upload-placeholder {
1768
- align-items: center;
1769
- display: flex;
1770
- flex-direction: column;
1771
- gap: var(--space-2);
1772
- max-width: 320px;
1773
- padding: var(--space-4);
1774
- text-align: center;
1775
- }
1776
-
1777
- .nb-form__file-upload-icon {
1778
- align-items: center;
1779
- background: color-mix(in srgb, var(--accent-color) 10%, transparent);
1780
- border-radius: 999px;
1781
- color: var(--accent-color);
1782
- display: inline-flex;
1783
- font-size: 24px;
1784
- height: 48px;
1785
- justify-content: center;
1786
- width: 48px;
1787
- }
1788
-
1789
- .nb-form__file-upload-title {
1790
- color: var(--text-primary);
1791
- font-size: var(--font-size-sm);
1792
- font-weight: var(--font-weight-semibold);
1793
- }
1794
-
1795
- .nb-form__file-upload-hint {
1796
- color: var(--text-tertiary);
1797
- font-size: var(--font-size-xs);
1798
- line-height: var(--line-height-tight);
1799
- }
1800
-
1801
- .nb-form__file-upload-preview {
1802
- display: block;
1803
- height: 100%;
1804
- max-height: 220px;
1805
- object-fit: contain;
1806
- width: 100%;
1807
- }
1808
-
1809
- .nb-form__file-upload-file {
1810
- align-items: center;
1811
- display: flex;
1812
- gap: var(--space-3);
1813
- max-width: 100%;
1814
- padding: var(--space-3) var(--space-4);
1815
- width: 100%;
1816
- }
1817
-
1818
- .nb-form__file-upload-file-icon {
1819
- align-items: center;
1820
- background: var(--surface-0);
1821
- border: 1px solid var(--border-subtle);
1822
- border-radius: var(--radius-md);
1823
- color: var(--accent-color);
1824
- display: inline-flex;
1825
- flex: 0 0 auto;
1826
- font-size: 22px;
1827
- height: 44px;
1828
- justify-content: center;
1829
- width: 44px;
1830
- }
1831
-
1832
- .nb-form__file-upload-file-meta {
1833
- display: flex;
1834
- flex: 1 1 auto;
1835
- flex-direction: column;
1836
- gap: 2px;
1837
- min-width: 0;
1838
- }
1839
-
1840
- .nb-form__file-upload-file-name {
1841
- color: var(--text-primary);
1842
- font-size: var(--font-size-sm);
1843
- font-weight: var(--font-weight-medium);
1844
- overflow: hidden;
1845
- text-overflow: ellipsis;
1846
- white-space: nowrap;
1847
- }
1848
-
1849
- .nb-form__file-upload-file-link {
1850
- color: var(--accent-color);
1851
- font-size: var(--font-size-xs);
1852
- text-decoration: none;
1853
- }
1854
- .nb-form__file-upload-file-link:hover {
1855
- text-decoration: underline;
1856
- }
1857
-
1858
- .nb-form__file-upload-overlay {
1859
- align-items: center;
1860
- background: rgba(0, 0, 0, 0.42);
1861
- color: #fff;
1862
- display: flex;
1863
- flex-direction: column;
1864
- font-size: var(--font-size-sm);
1865
- gap: var(--space-2);
1866
- inset: 0;
1867
- justify-content: center;
1868
- position: absolute;
1869
- }
1870
-
1871
- .nb-form__file-upload-spinner {
1872
- animation: nb-form-file-spin 700ms linear infinite;
1873
- border: 2px solid rgba(255, 255, 255, 0.35);
1874
- border-radius: 999px;
1875
- border-top-color: #fff;
1876
- height: 24px;
1877
- width: 24px;
1878
- }
1879
-
1880
- @keyframes nb-form-file-spin {
1881
- to {
1882
- transform: rotate(360deg);
1883
- }
1884
- }
1885
- .nb-form__file-upload-actions {
1886
- align-items: center;
1887
- background: linear-gradient(to top, rgba(0, 0, 0, 0.58), transparent);
1888
- bottom: 0;
1889
- display: flex;
1890
- gap: var(--space-2);
1891
- inset-inline: 0;
1892
- justify-content: center;
1893
- opacity: 0;
1894
- padding: var(--space-3);
1895
- position: absolute;
1896
- transition: opacity var(--transition-base);
1897
- }
1898
-
1899
- .nb-form__file-upload-zone--filled:hover .nb-form__file-upload-actions,
1900
- .nb-form__file-upload-zone--filled:focus-within .nb-form__file-upload-actions {
1901
- opacity: 1;
1902
- }
1903
-
1904
- .nb-form__file-upload-action {
1905
- align-items: center;
1906
- background: var(--surface-1);
1907
- border: 1px solid var(--border-subtle);
1908
- border-radius: var(--radius-md);
1909
- color: var(--text-primary);
1910
- cursor: pointer;
1911
- display: inline-flex;
1912
- font: inherit;
1913
- font-size: var(--font-size-xs);
1914
- font-weight: var(--font-weight-medium);
1915
- gap: var(--space-1);
1916
- min-height: 28px;
1917
- padding: 0 var(--space-2);
1918
- transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
1919
- }
1920
- .nb-form__file-upload-action:hover {
1921
- border-color: var(--accent-color);
1922
- color: var(--accent-color);
1923
- }
1924
- .nb-form__file-upload-action--danger:hover {
1925
- border-color: var(--error-color);
1926
- color: var(--error-color);
1927
- }
1928
- .nb-form__file-upload-action:focus-visible {
1929
- box-shadow: 0 0 0 2px var(--focus-ring-color);
1930
- outline: none;
1931
- }
1932
-
1933
- .nb-form__file-upload-error {
1934
- align-items: center;
1935
- color: var(--error-color);
1936
- display: inline-flex;
1937
- font-size: var(--font-size-xs);
1938
- gap: var(--space-1);
1939
- }
1940
- .nb-form__file-upload {
1941
- display: flex;
1942
- flex-direction: column;
1943
- gap: var(--space-1);
1944
- width: 100%;
1945
- }
1946
-
1947
- .nb-form__file-upload-zone {
1948
- align-items: center;
1949
- background: var(--surface-1);
1950
- border: 1px dashed var(--border-color);
1951
- border-radius: var(--radius-lg);
1952
- box-sizing: border-box;
1953
- cursor: pointer;
1954
- display: flex;
1955
- justify-content: center;
1956
- min-height: 112px;
1957
- overflow: hidden;
1958
- position: relative;
1959
- transition: border-color var(--transition-base), background var(--transition-base), box-shadow var(--transition-base);
1960
- width: 100%;
1961
- }
1962
- .nb-form__file-upload-zone:hover:not(.nb-form__file-upload-zone--disabled):not(.nb-form__file-upload-zone--filled) {
1963
- background: color-mix(in srgb, var(--accent-color) 4%, var(--surface-1));
1964
- border-color: var(--accent-color);
1965
- }
1966
- .nb-form__file-upload-zone--active {
1967
- background: color-mix(in srgb, var(--accent-color) 8%, var(--surface-1));
1968
- border-color: var(--accent-color);
1969
- }
1970
- .nb-form__file-upload-zone--filled {
1971
- border-style: solid;
1972
- cursor: default;
1973
- min-height: 88px;
1974
- }
1975
- .nb-form__file-upload-zone--uploading {
1976
- pointer-events: none;
1977
- }
1978
- .nb-form__file-upload-zone--disabled {
1979
- cursor: not-allowed;
1980
- opacity: 0.72;
1981
- }
1982
- .nb-form__file-upload-zone:focus-visible {
1983
- box-shadow: 0 0 0 3px var(--focus-ring-color);
1984
- outline: none;
1985
- }
1986
-
1987
- .nb-form__file-upload--image .nb-form__file-upload-zone {
1988
- min-height: 168px;
1989
- }
1990
-
1991
- .nb-form__file-upload--image .nb-form__file-upload-zone--filled {
1992
- min-height: 180px;
1993
- }
1994
-
1995
- .nb-form__file-upload--invalid .nb-form__file-upload-zone {
1996
- border-color: var(--error-color);
1997
- }
1998
-
1999
- .nb-form__file-upload-placeholder {
2000
- align-items: center;
2001
- display: flex;
2002
- flex-direction: column;
2003
- gap: var(--space-2);
2004
- max-width: 320px;
2005
- padding: var(--space-4);
2006
- text-align: center;
2007
- }
2008
-
2009
- .nb-form__file-upload-icon {
2010
- align-items: center;
2011
- background: color-mix(in srgb, var(--accent-color) 10%, transparent);
2012
- border-radius: 999px;
2013
- color: var(--accent-color);
2014
- display: inline-flex;
2015
- font-size: 24px;
2016
- height: 48px;
2017
- justify-content: center;
2018
- width: 48px;
2019
- }
2020
-
2021
- .nb-form__file-upload-title {
2022
- color: var(--text-primary);
2023
- font-size: var(--font-size-sm);
2024
- font-weight: var(--font-weight-semibold);
2025
- }
2026
-
2027
- .nb-form__file-upload-hint {
2028
- color: var(--text-tertiary);
2029
- font-size: var(--font-size-xs);
2030
- line-height: var(--line-height-tight);
2031
- }
2032
-
2033
- .nb-form__file-upload-preview {
2034
- display: block;
2035
- height: 100%;
2036
- max-height: 220px;
2037
- object-fit: contain;
2038
- width: 100%;
2039
- }
2040
-
2041
- .nb-form__file-upload-file {
2042
- align-items: center;
2043
- display: flex;
2044
- gap: var(--space-3);
2045
- max-width: 100%;
2046
- padding: var(--space-3) var(--space-4);
2047
- width: 100%;
2048
- }
2049
-
2050
- .nb-form__file-upload-file-icon {
2051
- align-items: center;
2052
- background: var(--surface-0);
2053
- border: 1px solid var(--border-subtle);
2054
- border-radius: var(--radius-md);
2055
- color: var(--accent-color);
2056
- display: inline-flex;
2057
- flex: 0 0 auto;
2058
- font-size: 22px;
2059
- height: 44px;
2060
- justify-content: center;
2061
- width: 44px;
2062
- }
2063
-
2064
- .nb-form__file-upload-file-meta {
2065
- display: flex;
2066
- flex: 1 1 auto;
2067
- flex-direction: column;
2068
- gap: 2px;
2069
- min-width: 0;
2070
- }
2071
-
2072
- .nb-form__file-upload-file-name {
2073
- color: var(--text-primary);
2074
- font-size: var(--font-size-sm);
2075
- font-weight: var(--font-weight-medium);
2076
- overflow: hidden;
2077
- text-overflow: ellipsis;
2078
- white-space: nowrap;
2079
- }
2080
-
2081
- .nb-form__file-upload-file-link {
2082
- color: var(--accent-color);
2083
- font-size: var(--font-size-xs);
2084
- text-decoration: none;
2085
- }
2086
- .nb-form__file-upload-file-link:hover {
2087
- text-decoration: underline;
2088
- }
2089
-
2090
- .nb-form__file-upload-overlay {
2091
- align-items: center;
2092
- background: rgba(0, 0, 0, 0.42);
2093
- color: #fff;
2094
- display: flex;
2095
- flex-direction: column;
2096
- font-size: var(--font-size-sm);
2097
- gap: var(--space-2);
2098
- inset: 0;
2099
- justify-content: center;
2100
- position: absolute;
2101
- }
2102
-
2103
- .nb-form__file-upload-spinner {
2104
- animation: nb-form-file-spin 700ms linear infinite;
2105
- border: 2px solid rgba(255, 255, 255, 0.35);
2106
- border-radius: 999px;
2107
- border-top-color: #fff;
2108
- height: 24px;
2109
- width: 24px;
2110
- }
2111
-
2112
- @keyframes nb-form-file-spin {
2113
- to {
2114
- transform: rotate(360deg);
2115
- }
2116
- }
2117
- .nb-form__file-upload-actions {
2118
- align-items: center;
2119
- background: linear-gradient(to top, rgba(0, 0, 0, 0.58), transparent);
2120
- bottom: 0;
2121
- display: flex;
2122
- gap: var(--space-2);
2123
- inset-inline: 0;
2124
- justify-content: center;
2125
- opacity: 0;
2126
- padding: var(--space-3);
2127
- position: absolute;
2128
- transition: opacity var(--transition-base);
2129
- }
2130
-
2131
- .nb-form__file-upload-zone--filled:hover .nb-form__file-upload-actions,
2132
- .nb-form__file-upload-zone--filled:focus-within .nb-form__file-upload-actions {
2133
- opacity: 1;
2134
- }
2135
-
2136
- .nb-form__file-upload-action {
2137
- align-items: center;
2138
- background: var(--surface-1);
2139
- border: 1px solid var(--border-subtle);
2140
- border-radius: var(--radius-md);
2141
- color: var(--text-primary);
2142
- cursor: pointer;
2143
- display: inline-flex;
2144
- font: inherit;
2145
- font-size: var(--font-size-xs);
2146
- font-weight: var(--font-weight-medium);
2147
- gap: var(--space-1);
2148
- min-height: 28px;
2149
- padding: 0 var(--space-2);
2150
- transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
2151
- }
2152
- .nb-form__file-upload-action:hover {
2153
- border-color: var(--accent-color);
2154
- color: var(--accent-color);
2155
- }
2156
- .nb-form__file-upload-action--danger:hover {
2157
- border-color: var(--error-color);
2158
- color: var(--error-color);
2159
- }
2160
- .nb-form__file-upload-action:focus-visible {
2161
- box-shadow: 0 0 0 2px var(--focus-ring-color);
2162
- outline: none;
2163
- }
2164
-
2165
- .nb-form__file-upload-error {
2166
- align-items: center;
2167
- color: var(--error-color);
2168
- display: inline-flex;
2169
- font-size: var(--font-size-xs);
2170
- gap: var(--space-1);
2171
- }
2172
-
2173
1708
  .nb-form {
2174
1709
  color: var(--text-primary);
2175
1710
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubitio/crud",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "type": "module",
5
5
  "description": "Declarative CRUD engine with field DSL, forms, datagrids, RBAC, conditional logic and pluggable adapters (Hydra/REST).",
6
6
  "license": "MIT",
@@ -56,10 +56,7 @@
56
56
  "react-dom": "^19.0.0",
57
57
  "react-i18next": "^14.0.0",
58
58
  "react-router-dom": "^6.0.0",
59
- "@nubitio/core": "^0.5.15",
60
- "@nubitio/ui": "^0.5.15"
61
- },
62
- "dependencies": {
63
- "react-dropzone": "^15.0.0"
59
+ "@nubitio/core": "^0.5.16",
60
+ "@nubitio/ui": "^0.5.16"
64
61
  }
65
62
  }