@incursa/ui-kit 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,12 @@
14
14
  nativeDialogOpen: "[data-inc-native-dialog-open]",
15
15
  autoRefresh: "[data-inc-auto-refresh]",
16
16
  autoRefreshToggle: '[data-inc-action="auto-refresh-toggle"]',
17
+ fileExample: "[data-inc-file-example]",
18
+ fileDropzone: "[data-inc-file-dropzone]",
19
+ fileInput: "[data-inc-file-input]",
20
+ fileBrowse: "[data-inc-file-browse]",
21
+ fileList: "[data-inc-file-list]",
22
+ fileRemove: '[data-inc-action="file-remove"]',
17
23
  modalToggle: '[data-inc-toggle="modal"]',
18
24
  modalDismiss: '[data-inc-dismiss="modal"]',
19
25
  offcanvasToggle: '[data-inc-toggle="offcanvas"]',
@@ -824,6 +830,298 @@
824
830
  return parsed;
825
831
  }
826
832
 
833
+ function formatFileExampleSize(bytes) {
834
+ if (!Number.isFinite(bytes) || bytes <= 0) {
835
+ return "0 B";
836
+ }
837
+
838
+ if (bytes < 1024) {
839
+ return `${bytes} B`;
840
+ }
841
+
842
+ const units = ["KB", "MB", "GB", "TB"];
843
+ let value = bytes / 1024;
844
+ let unitIndex = 0;
845
+
846
+ while (value >= 1024 && unitIndex < (units.length - 1)) {
847
+ value /= 1024;
848
+ unitIndex += 1;
849
+ }
850
+
851
+ const precision = value >= 10 ? 0 : 1;
852
+ return `${value.toFixed(precision)} ${units[unitIndex]}`;
853
+ }
854
+
855
+ function getFileExampleTypeLabel(file) {
856
+ if (!(file instanceof File)) {
857
+ return "File";
858
+ }
859
+
860
+ const extensionMatch = /\.([a-z0-9]+)$/i.exec(file.name || "");
861
+ if (extensionMatch?.[1]) {
862
+ return extensionMatch[1].toUpperCase();
863
+ }
864
+
865
+ if (typeof file.type === "string" && file.type.includes("/")) {
866
+ return file.type.split("/").at(-1)?.toUpperCase() || "File";
867
+ }
868
+
869
+ return "File";
870
+ }
871
+
872
+ function createFileExampleRow(file) {
873
+ const row = document.createElement("div");
874
+ const meta = document.createElement("div");
875
+ const name = document.createElement("p");
876
+ const detail = document.createElement("p");
877
+ const badge = document.createElement("span");
878
+ const actions = document.createElement("div");
879
+ const preview = document.createElement("a");
880
+ const remove = document.createElement("button");
881
+ const objectUrl = URL.createObjectURL(file);
882
+
883
+ row.className = "inc-file-row";
884
+ row._incFileExampleObjectUrl = objectUrl;
885
+
886
+ meta.className = "inc-file-row__meta";
887
+ name.className = "inc-file-row__name";
888
+ name.textContent = file.name || "untitled-file";
889
+ detail.className = "inc-file-row__detail";
890
+ detail.textContent = `${getFileExampleTypeLabel(file)} • ${formatFileExampleSize(file.size)} • selected just now`;
891
+ meta.append(name, detail);
892
+
893
+ badge.className = "inc-badge inc-badge--secondary inc-badge--pill";
894
+ badge.textContent = "Ready";
895
+
896
+ actions.className = "inc-file-row__actions";
897
+
898
+ preview.className = "inc-btn inc-btn--outline-secondary inc-btn--sm";
899
+ preview.href = objectUrl;
900
+ preview.target = "_blank";
901
+ preview.rel = "noreferrer";
902
+ preview.textContent = "Preview";
903
+
904
+ remove.type = "button";
905
+ remove.className = "inc-btn inc-btn--secondary inc-btn--sm";
906
+ remove.textContent = "Remove";
907
+ remove.setAttribute("data-inc-action", "file-remove");
908
+ remove.setAttribute("aria-label", `Remove ${file.name}`);
909
+
910
+ actions.append(preview, remove);
911
+ row.append(meta, badge, actions);
912
+ return row;
913
+ }
914
+
915
+ function revokeFileExampleRow(row) {
916
+ if (!(row instanceof HTMLElement)) {
917
+ return;
918
+ }
919
+
920
+ if (typeof row._incFileExampleObjectUrl === "string" && row._incFileExampleObjectUrl) {
921
+ URL.revokeObjectURL(row._incFileExampleObjectUrl);
922
+ row._incFileExampleObjectUrl = "";
923
+ }
924
+ }
925
+
926
+ function updateFileExampleEmptyState(controller) {
927
+ const { list } = controller.parts;
928
+ if (!(list instanceof HTMLElement)) {
929
+ return;
930
+ }
931
+
932
+ const rows = list.querySelectorAll(".inc-file-row");
933
+ let empty = list.querySelector("[data-inc-file-empty]");
934
+
935
+ if (rows.length > 0) {
936
+ if (empty instanceof HTMLElement) {
937
+ empty.hidden = true;
938
+ }
939
+ return;
940
+ }
941
+
942
+ if (!(empty instanceof HTMLElement)) {
943
+ empty = document.createElement("p");
944
+ empty.className = "inc-text inc-text--small inc-text--muted";
945
+ empty.setAttribute("data-inc-file-empty", "");
946
+ empty.textContent = controller.emptyText;
947
+ list.append(empty);
948
+ }
949
+
950
+ empty.hidden = false;
951
+ }
952
+
953
+ function appendFilesToExample(controller, files) {
954
+ const { list } = controller.parts;
955
+ if (!(list instanceof HTMLElement)) {
956
+ return;
957
+ }
958
+
959
+ const fileItems = Array.from(files || []).filter((file) => file instanceof File);
960
+ if (!fileItems.length) {
961
+ return;
962
+ }
963
+
964
+ fileItems.forEach((file) => {
965
+ list.append(createFileExampleRow(file));
966
+ });
967
+
968
+ updateFileExampleEmptyState(controller);
969
+ }
970
+
971
+ function dataTransferIncludesFiles(dataTransfer) {
972
+ if (!dataTransfer) {
973
+ return false;
974
+ }
975
+
976
+ if (dataTransfer.files?.length) {
977
+ return true;
978
+ }
979
+
980
+ return Array.from(dataTransfer.types || []).includes("Files");
981
+ }
982
+
983
+ function setFileDropzoneActiveState(controller, isActive) {
984
+ const { dropzone } = controller.parts;
985
+ if (!(dropzone instanceof HTMLElement)) {
986
+ return;
987
+ }
988
+
989
+ dropzone.classList.toggle("is-drag-over", Boolean(isActive));
990
+ }
991
+
992
+ function openFileExamplePicker(controller) {
993
+ const { input } = controller.parts;
994
+ if (!(input instanceof HTMLInputElement)) {
995
+ return;
996
+ }
997
+
998
+ input.click();
999
+ }
1000
+
1001
+ function initializeFileExamples() {
1002
+ document.querySelectorAll(selectors.fileExample).forEach((root) => {
1003
+ if (!(root instanceof HTMLElement) || root._incFileExampleInitialized) {
1004
+ return;
1005
+ }
1006
+
1007
+ const controller = {
1008
+ root,
1009
+ parts: {
1010
+ dropzone: root.querySelector(selectors.fileDropzone),
1011
+ input: root.querySelector(selectors.fileInput),
1012
+ browse: root.querySelector(selectors.fileBrowse),
1013
+ list: root.querySelector(selectors.fileList),
1014
+ },
1015
+ emptyText: root.getAttribute("data-inc-file-empty-text") || "No files selected yet.",
1016
+ dragDepth: 0,
1017
+ };
1018
+
1019
+ const { dropzone, input, browse, list } = controller.parts;
1020
+ if (!(dropzone instanceof HTMLElement) || !(input instanceof HTMLInputElement) || !(list instanceof HTMLElement)) {
1021
+ return;
1022
+ }
1023
+
1024
+ root._incFileExampleInitialized = true;
1025
+ root._incFileExampleController = controller;
1026
+
1027
+ dropzone.setAttribute("tabindex", dropzone.getAttribute("tabindex") || "0");
1028
+ dropzone.setAttribute("role", dropzone.getAttribute("role") || "button");
1029
+ dropzone.setAttribute("aria-label", dropzone.getAttribute("aria-label") || "Drop files here or browse for files");
1030
+
1031
+ const browseAction = (event) => {
1032
+ event.preventDefault();
1033
+ openFileExamplePicker(controller);
1034
+ };
1035
+
1036
+ if (browse instanceof HTMLElement) {
1037
+ browse.addEventListener("click", browseAction);
1038
+ }
1039
+
1040
+ dropzone.addEventListener("click", (event) => {
1041
+ if (event.target instanceof Element && event.target.closest("a, button, input, label")) {
1042
+ return;
1043
+ }
1044
+
1045
+ openFileExamplePicker(controller);
1046
+ });
1047
+
1048
+ dropzone.addEventListener("keydown", (event) => {
1049
+ if (event.key !== "Enter" && event.key !== " ") {
1050
+ return;
1051
+ }
1052
+
1053
+ event.preventDefault();
1054
+ openFileExamplePicker(controller);
1055
+ });
1056
+
1057
+ dropzone.addEventListener("dragenter", (event) => {
1058
+ if (!dataTransferIncludesFiles(event.dataTransfer)) {
1059
+ return;
1060
+ }
1061
+
1062
+ event.preventDefault();
1063
+ controller.dragDepth += 1;
1064
+ setFileDropzoneActiveState(controller, true);
1065
+ });
1066
+
1067
+ dropzone.addEventListener("dragover", (event) => {
1068
+ if (!dataTransferIncludesFiles(event.dataTransfer)) {
1069
+ return;
1070
+ }
1071
+
1072
+ event.preventDefault();
1073
+ if (event.dataTransfer) {
1074
+ event.dataTransfer.dropEffect = "copy";
1075
+ }
1076
+ setFileDropzoneActiveState(controller, true);
1077
+ });
1078
+
1079
+ dropzone.addEventListener("dragleave", (event) => {
1080
+ if (!dataTransferIncludesFiles(event.dataTransfer)) {
1081
+ return;
1082
+ }
1083
+
1084
+ event.preventDefault();
1085
+ controller.dragDepth = Math.max(0, controller.dragDepth - 1);
1086
+
1087
+ if (controller.dragDepth === 0 || !dropzone.contains(event.relatedTarget)) {
1088
+ setFileDropzoneActiveState(controller, false);
1089
+ }
1090
+ });
1091
+
1092
+ dropzone.addEventListener("drop", (event) => {
1093
+ if (!dataTransferIncludesFiles(event.dataTransfer)) {
1094
+ return;
1095
+ }
1096
+
1097
+ event.preventDefault();
1098
+ controller.dragDepth = 0;
1099
+ setFileDropzoneActiveState(controller, false);
1100
+ appendFilesToExample(controller, event.dataTransfer?.files);
1101
+ });
1102
+
1103
+ input.addEventListener("change", () => {
1104
+ appendFilesToExample(controller, input.files);
1105
+ input.value = "";
1106
+ });
1107
+
1108
+ list.addEventListener("click", (event) => {
1109
+ const removeButton = event.target.closest(selectors.fileRemove);
1110
+ if (!removeButton) {
1111
+ return;
1112
+ }
1113
+
1114
+ event.preventDefault();
1115
+ const row = removeButton.closest(".inc-file-row");
1116
+ revokeFileExampleRow(row);
1117
+ row?.remove();
1118
+ updateFileExampleEmptyState(controller);
1119
+ });
1120
+
1121
+ updateFileExampleEmptyState(controller);
1122
+ });
1123
+ }
1124
+
827
1125
  function formatAutoRefreshRemaining(totalSeconds) {
828
1126
  if (totalSeconds < 60) {
829
1127
  return `${totalSeconds}s`;
@@ -834,14 +1132,36 @@
834
1132
  return `${minutes}m ${seconds}s`;
835
1133
  }
836
1134
 
1135
+ const AUTO_REFRESH_PAUSE_ICON = `
1136
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
1137
+ <path d="M4 3h3v10H4zM9 3h3v10H9z"></path>
1138
+ </svg>`.trim();
1139
+
1140
+ const AUTO_REFRESH_PLAY_ICON = `
1141
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
1142
+ <path d="M4 3.5v9l8-4.5-8-4.5z"></path>
1143
+ </svg>`.trim();
1144
+
837
1145
  function getAutoRefreshParts(root) {
1146
+ const toggle = root.querySelector(".inc-auto-refresh__toggle");
1147
+
1148
+ if (toggle instanceof HTMLElement) {
1149
+ if (!toggle.querySelector(".inc-auto-refresh__toggle-icon")) {
1150
+ const icon = document.createElement("span");
1151
+ icon.className = "inc-auto-refresh__toggle-icon";
1152
+ icon.setAttribute("aria-hidden", "true");
1153
+ toggle.prepend(icon);
1154
+ }
1155
+ }
1156
+
838
1157
  return {
839
1158
  countdown: root.querySelector(".inc-auto-refresh__countdown"),
840
1159
  label: root.querySelector(".inc-auto-refresh__label"),
841
1160
  value: root.querySelector(".inc-auto-refresh__value"),
842
1161
  status: root.querySelector(".inc-auto-refresh__status"),
843
1162
  statusText: root.querySelector(".inc-auto-refresh__status-text"),
844
- toggle: root.querySelector(".inc-auto-refresh__toggle"),
1163
+ toggle,
1164
+ toggleIcon: root.querySelector(".inc-auto-refresh__toggle-icon"),
845
1165
  toggleText: root.querySelector(".inc-auto-refresh__toggle-text"),
846
1166
  };
847
1167
  }
@@ -861,6 +1181,10 @@
861
1181
  if (parts.toggleText) {
862
1182
  parts.toggleText.textContent = actionLabel;
863
1183
  }
1184
+
1185
+ if (parts.toggleIcon instanceof HTMLElement) {
1186
+ parts.toggleIcon.innerHTML = isPaused ? AUTO_REFRESH_PLAY_ICON : AUTO_REFRESH_PAUSE_ICON;
1187
+ }
864
1188
  }
865
1189
 
866
1190
  function renderAutoRefreshCountdown(controller, remainingSeconds) {
@@ -1524,6 +1848,7 @@
1524
1848
  initializeMenus();
1525
1849
  initializeCollapses();
1526
1850
  initializeTabs();
1851
+ initializeFileExamples();
1527
1852
  initializeAutoRefresh();
1528
1853
  attachEventHandlers();
1529
1854
  }