@seedgrid/fe-components 2026.11.1 → 2026.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +1527 -89
  2. package/dist/buttons/SgButton.d.ts +6 -2
  3. package/dist/buttons/SgButton.d.ts.map +1 -1
  4. package/dist/buttons/SgButton.js +2 -2
  5. package/dist/buttons/SgButton.meta.js +3 -3
  6. package/dist/buttons/SgFloatActionButton.d.ts +2 -2
  7. package/dist/buttons/SgFloatActionButton.d.ts.map +1 -1
  8. package/dist/buttons/SgFloatActionButton.js +7 -7
  9. package/dist/buttons/SgFloatActionButton.meta.js +2 -2
  10. package/dist/buttons/SgSplitButton.d.ts +2 -2
  11. package/dist/buttons/SgSplitButton.d.ts.map +1 -1
  12. package/dist/buttons/SgSplitButton.js +4 -4
  13. package/dist/buttons/SgSplitButton.meta.js +3 -3
  14. package/dist/commons/SgAvatar.d.ts +2 -2
  15. package/dist/commons/SgAvatar.d.ts.map +1 -1
  16. package/dist/commons/SgAvatar.js +3 -3
  17. package/dist/commons/SgBadge.d.ts +1 -1
  18. package/dist/commons/SgBadge.d.ts.map +1 -1
  19. package/dist/commons/SgBadge.js +3 -3
  20. package/dist/generated/compiledStyles.d.ts +1 -1
  21. package/dist/generated/compiledStyles.d.ts.map +1 -1
  22. package/dist/generated/compiledStyles.js +1 -1
  23. package/dist/i18n/en-US.d.ts.map +1 -1
  24. package/dist/i18n/en-US.js +55 -0
  25. package/dist/i18n/es.d.ts.map +1 -1
  26. package/dist/i18n/es.js +55 -0
  27. package/dist/i18n/fr.d.ts.map +1 -1
  28. package/dist/i18n/fr.js +55 -0
  29. package/dist/i18n/pt-BR.d.ts.map +1 -1
  30. package/dist/i18n/pt-BR.js +55 -0
  31. package/dist/i18n/pt-PT.d.ts.map +1 -1
  32. package/dist/i18n/pt-PT.js +55 -0
  33. package/dist/index.d.ts +12 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +6 -1
  36. package/dist/inputs/SgAutocomplete.d.ts +1 -1
  37. package/dist/inputs/SgAutocomplete.d.ts.map +1 -1
  38. package/dist/inputs/SgAutocomplete.js +3 -3
  39. package/dist/inputs/SgCameraCapture.d.ts +25 -0
  40. package/dist/inputs/SgCameraCapture.d.ts.map +1 -0
  41. package/dist/inputs/SgCameraCapture.js +114 -0
  42. package/dist/inputs/SgCameraCapture.meta.d.ts +4 -0
  43. package/dist/inputs/SgCameraCapture.meta.d.ts.map +1 -0
  44. package/dist/inputs/SgCameraCapture.meta.js +63 -0
  45. package/dist/inputs/SgCheckboxGroup.d.ts +2 -2
  46. package/dist/inputs/SgCheckboxGroup.d.ts.map +1 -1
  47. package/dist/inputs/SgCheckboxGroup.js +12 -12
  48. package/dist/inputs/SgCombobox.js +5 -5
  49. package/dist/inputs/SgDatatable.js +11 -11
  50. package/dist/inputs/SgDatatable.meta.d.ts.map +1 -1
  51. package/dist/inputs/SgDatatable.meta.js +4 -1
  52. package/dist/inputs/SgFileUpload.d.ts +69 -0
  53. package/dist/inputs/SgFileUpload.d.ts.map +1 -0
  54. package/dist/inputs/SgFileUpload.js +234 -0
  55. package/dist/inputs/SgFileUpload.meta.d.ts +4 -0
  56. package/dist/inputs/SgFileUpload.meta.d.ts.map +1 -0
  57. package/dist/inputs/SgFileUpload.meta.js +71 -0
  58. package/dist/inputs/SgImageUpload.d.ts +20 -0
  59. package/dist/inputs/SgImageUpload.d.ts.map +1 -0
  60. package/dist/inputs/SgImageUpload.js +74 -0
  61. package/dist/inputs/SgImageUpload.meta.d.ts +4 -0
  62. package/dist/inputs/SgImageUpload.meta.d.ts.map +1 -0
  63. package/dist/inputs/SgImageUpload.meta.js +73 -0
  64. package/dist/inputs/SgInputCurrency.d.ts.map +1 -1
  65. package/dist/inputs/SgInputCurrency.js +6 -4
  66. package/dist/inputs/SgInputDate.d.ts.map +1 -1
  67. package/dist/inputs/SgInputDate.js +3 -4
  68. package/dist/inputs/SgInputDateTime.d.ts +29 -0
  69. package/dist/inputs/SgInputDateTime.d.ts.map +1 -0
  70. package/dist/inputs/SgInputDateTime.js +141 -0
  71. package/dist/inputs/SgInputDateTime.meta.d.ts +4 -0
  72. package/dist/inputs/SgInputDateTime.meta.d.ts.map +1 -0
  73. package/dist/inputs/SgInputDateTime.meta.js +73 -0
  74. package/dist/inputs/SgInputNumber.d.ts.map +1 -1
  75. package/dist/inputs/SgInputNumber.js +6 -4
  76. package/dist/inputs/SgInputText.d.ts.map +1 -1
  77. package/dist/inputs/SgInputText.js +6 -4
  78. package/dist/inputs/SgInputTextArea.d.ts.map +1 -1
  79. package/dist/inputs/SgInputTextArea.js +4 -2
  80. package/dist/inputs/SgMultiSelect.meta.js +1 -1
  81. package/dist/inputs/SgMultiSelectChips.meta.js +1 -1
  82. package/dist/inputs/SgMultiSelectDropdown.js +1 -1
  83. package/dist/inputs/SgOrderList.d.ts +3 -3
  84. package/dist/inputs/SgOrderList.d.ts.map +1 -1
  85. package/dist/inputs/SgOrderList.js +13 -13
  86. package/dist/inputs/SgOrderList.meta.js +1 -1
  87. package/dist/inputs/SgPeriodSelector.d.ts +1 -1
  88. package/dist/inputs/SgPeriodSelector.d.ts.map +1 -1
  89. package/dist/inputs/SgPeriodSelector.js +3 -3
  90. package/dist/inputs/SgPickList.d.ts +3 -3
  91. package/dist/inputs/SgPickList.d.ts.map +1 -1
  92. package/dist/inputs/SgPickList.js +28 -28
  93. package/dist/inputs/SgPickList.meta.js +1 -1
  94. package/dist/inputs/SgRadioGroup.d.ts +2 -2
  95. package/dist/inputs/SgRadioGroup.d.ts.map +1 -1
  96. package/dist/inputs/SgRadioGroup.js +5 -5
  97. package/dist/inputs/SgRating.d.ts +1 -1
  98. package/dist/inputs/SgRating.d.ts.map +1 -1
  99. package/dist/inputs/SgRating.js +7 -7
  100. package/dist/inputs/SgRating.meta.js +1 -1
  101. package/dist/inputs/SgSlider.d.ts +1 -1
  102. package/dist/inputs/SgSlider.d.ts.map +1 -1
  103. package/dist/inputs/SgSlider.js +3 -3
  104. package/dist/inputs/SgSlider.meta.js +1 -1
  105. package/dist/inputs/SgStepperInput.d.ts +2 -2
  106. package/dist/inputs/SgStepperInput.d.ts.map +1 -1
  107. package/dist/inputs/SgStepperInput.js +4 -4
  108. package/dist/inputs/SgStepperInput.meta.js +1 -1
  109. package/dist/inputs/SgTextEditor.d.ts +1 -1
  110. package/dist/inputs/SgTextEditor.d.ts.map +1 -1
  111. package/dist/inputs/SgTextEditor.js +14 -14
  112. package/dist/inputs/SgToggleSwitch.meta.js +1 -1
  113. package/dist/inputs/SgYearPartSelector.d.ts +1 -1
  114. package/dist/inputs/SgYearPartSelector.d.ts.map +1 -1
  115. package/dist/inputs/SgYearPartSelector.js +1 -2
  116. package/dist/inputs/SgYearPartSelector.meta.js +2 -2
  117. package/dist/inputs/cameraLogic.d.ts +27 -0
  118. package/dist/inputs/cameraLogic.d.ts.map +1 -0
  119. package/dist/inputs/cameraLogic.js +57 -0
  120. package/dist/inputs/dateTimeLogic.d.ts +28 -0
  121. package/dist/inputs/dateTimeLogic.d.ts.map +1 -0
  122. package/dist/inputs/dateTimeLogic.js +153 -0
  123. package/dist/inputs/fileUploadLogic.d.ts +62 -0
  124. package/dist/inputs/fileUploadLogic.d.ts.map +1 -0
  125. package/dist/inputs/fileUploadLogic.js +254 -0
  126. package/dist/inputs/imageUploadLogic.d.ts +31 -0
  127. package/dist/inputs/imageUploadLogic.d.ts.map +1 -0
  128. package/dist/inputs/imageUploadLogic.js +70 -0
  129. package/dist/inputs/useSgMultiSelect.d.ts +1 -1
  130. package/dist/inputs/useSgMultiSelect.d.ts.map +1 -1
  131. package/dist/inputs/useSgMultiSelect.js +1 -1
  132. package/dist/layout/SgAccordion.d.ts +1 -1
  133. package/dist/layout/SgAccordion.d.ts.map +1 -1
  134. package/dist/layout/SgAccordion.js +3 -3
  135. package/dist/layout/SgBreadcrumb.d.ts +1 -1
  136. package/dist/layout/SgBreadcrumb.d.ts.map +1 -1
  137. package/dist/layout/SgBreadcrumb.js +1 -1
  138. package/dist/layout/SgCard.d.ts +1 -1
  139. package/dist/layout/SgCard.d.ts.map +1 -1
  140. package/dist/layout/SgCard.js +14 -14
  141. package/dist/layout/SgMenu.d.ts +1 -1
  142. package/dist/layout/SgMenu.d.ts.map +1 -1
  143. package/dist/layout/SgMenu.js +15 -15
  144. package/dist/layout/SgPageControl.d.ts +1 -1
  145. package/dist/layout/SgPageControl.d.ts.map +1 -1
  146. package/dist/layout/SgPageControl.js +4 -4
  147. package/dist/layout/SgPageControlPage.meta.js +2 -2
  148. package/dist/layout/SgToolBar.d.ts +1 -1
  149. package/dist/layout/SgToolBar.d.ts.map +1 -1
  150. package/dist/layout/SgToolBar.js +4 -4
  151. package/dist/layout/SgToolbarIconButton.meta.js +2 -2
  152. package/dist/layout/SgTreeView.d.ts +4 -4
  153. package/dist/layout/SgTreeView.d.ts.map +1 -1
  154. package/dist/layout/SgTreeView.js +6 -6
  155. package/dist/menus/SgDockMenu.d.ts +1 -1
  156. package/dist/menus/SgDockMenu.d.ts.map +1 -1
  157. package/dist/menus/SgDockMenu.js +6 -4
  158. package/dist/notifications/SgNotificationBell.d.ts +51 -0
  159. package/dist/notifications/SgNotificationBell.d.ts.map +1 -0
  160. package/dist/notifications/SgNotificationBell.js +348 -0
  161. package/dist/notifications/SgNotificationBell.meta.d.ts +4 -0
  162. package/dist/notifications/SgNotificationBell.meta.d.ts.map +1 -0
  163. package/dist/notifications/SgNotificationBell.meta.js +75 -0
  164. package/dist/notifications/notificationBellLogic.d.ts +148 -0
  165. package/dist/notifications/notificationBellLogic.d.ts.map +1 -0
  166. package/dist/notifications/notificationBellLogic.js +244 -0
  167. package/dist/overlay/SgConfirmationDialog.d.ts +1 -1
  168. package/dist/overlay/SgConfirmationDialog.d.ts.map +1 -1
  169. package/dist/overlay/SgConfirmationDialog.js +1 -1
  170. package/dist/overlay/SgPopup.d.ts +1 -1
  171. package/dist/overlay/SgPopup.d.ts.map +1 -1
  172. package/dist/overlay/SgPopup.js +1 -1
  173. package/dist/realtime/SgRealtimeProvider.d.ts +5 -0
  174. package/dist/realtime/SgRealtimeProvider.d.ts.map +1 -1
  175. package/dist/realtime/SgRealtimeProvider.js +31 -8
  176. package/dist/sandbox.cjs +77 -66
  177. package/dist/style.css +1 -1
  178. package/package.json +3 -3
@@ -11,7 +11,7 @@ function deriveInitialValue(source) {
11
11
  // Internal component — plain function, no forwardRef, safe for self-reference inside Controller
12
12
  function SgCheckboxGroupBase(props, imperativeRef) {
13
13
  const i18n = useComponentsI18n();
14
- const { id, title, source, value: controlledValue, orientation = "vertical", selectionStyle = "checkbox", iconOnly = false, disabled = false, readOnly = false, required = false, onChange, showCheckAll = false, checkAllLabel, checkAllIcon, name, control, register, rules, error, className = "", style, optionClassName = "", groupBoxProps = {} } = props;
14
+ const { id, title, source, value: controlledValue, orientation = "vertical", selectionStyle = "checkbox", iconOnly = false, enabled = true, readOnly = false, required = false, onChange, showCheckAll = false, checkAllLabel, checkAllIcon, name, control, register, rules, error, className = "", style, optionClassName = "", groupBoxProps = {} } = props;
15
15
  const resolvedCheckAllLabel = checkAllLabel ?? t(i18n, "components.checkboxgroup.checkAll") ?? "Selecionar todos";
16
16
  const resolvedGroupBoxTitle = (() => {
17
17
  const baseTitle = groupBoxProps.title ?? title ?? "";
@@ -24,7 +24,7 @@ function SgCheckboxGroupBase(props, imperativeRef) {
24
24
  const currentValue = isControlled ? controlledValue : internalValue;
25
25
  const isHighlightSelection = selectionStyle === "highlight";
26
26
  const handleChange = (optionValue) => {
27
- if (disabled || readOnly)
27
+ if (!enabled || readOnly)
28
28
  return;
29
29
  const next = currentValue.includes(optionValue)
30
30
  ? currentValue.filter((v) => v !== optionValue)
@@ -34,9 +34,9 @@ function SgCheckboxGroupBase(props, imperativeRef) {
34
34
  onChange?.(next);
35
35
  };
36
36
  const handleCheckAll = () => {
37
- if (disabled || readOnly)
37
+ if (!enabled || readOnly)
38
38
  return;
39
- const enabledValues = source.filter((opt) => !opt.disabled).map((opt) => opt.value);
39
+ const enabledValues = source.filter((opt) => opt.enabled !== false).map((opt) => opt.value);
40
40
  const next = currentValue.length === enabledValues.length ? [] : enabledValues;
41
41
  if (!isControlled)
42
42
  setInternalValue(next);
@@ -50,7 +50,7 @@ function SgCheckboxGroupBase(props, imperativeRef) {
50
50
  onChange?.(values);
51
51
  },
52
52
  checkAll: () => {
53
- const enabledValues = source.filter((opt) => !opt.disabled).map((opt) => opt.value);
53
+ const enabledValues = source.filter((opt) => opt.enabled !== false).map((opt) => opt.value);
54
54
  if (!isControlled)
55
55
  setInternalValue(enabledValues);
56
56
  onChange?.(enabledValues);
@@ -62,7 +62,7 @@ function SgCheckboxGroupBase(props, imperativeRef) {
62
62
  }
63
63
  }));
64
64
  // Check all state
65
- const enabledValues = source.filter((opt) => !opt.disabled).map((opt) => opt.value);
65
+ const enabledValues = source.filter((opt) => opt.enabled !== false).map((opt) => opt.value);
66
66
  const checkedEnabledCount = enabledValues.filter((v) => currentValue.includes(v)).length;
67
67
  const allChecked = enabledValues.length > 0 && checkedEnabledCount === enabledValues.length;
68
68
  const someChecked = checkedEnabledCount > 0 && !allChecked;
@@ -74,7 +74,7 @@ function SgCheckboxGroupBase(props, imperativeRef) {
74
74
  }, [someChecked]);
75
75
  const renderOption = (option, index) => {
76
76
  const isSelected = currentValue.includes(option.value);
77
- const isDisabled = disabled || option.disabled;
77
+ const isDisabled = !enabled || (option.enabled === false);
78
78
  const inputClassName = isHighlightSelection
79
79
  ? "sr-only"
80
80
  : "w-4 h-4 rounded text-[rgb(var(--sg-primary-500))] border-[rgb(var(--sg-border))] focus:ring-[rgb(var(--sg-ring))] focus:ring-2 cursor-pointer disabled:cursor-not-allowed";
@@ -112,13 +112,13 @@ function SgCheckboxGroupBase(props, imperativeRef) {
112
112
  };
113
113
  const checkAllNode = showCheckAll ? (_jsxs("div", { className: `${isHighlightSelection ? "px-3 pt-1 pb-2" : "pb-2"}`, children: [_jsxs("label", { className: `
114
114
  inline-flex items-center gap-2 select-none font-medium
115
- ${disabled ? "opacity-50 cursor-not-allowed" : readOnly ? "cursor-default" : "cursor-pointer"}
116
- `, children: [_jsx("input", { ref: checkAllRef, type: "checkbox", checked: allChecked, disabled: disabled || readOnly, onChange: handleCheckAll, className: "w-4 h-4 rounded text-[rgb(var(--sg-primary-500))] border-[rgb(var(--sg-border))] focus:ring-[rgb(var(--sg-ring))] focus:ring-2 cursor-pointer disabled:cursor-not-allowed" }), checkAllIcon ? (_jsx("span", { className: "flex items-center justify-center", children: checkAllIcon })) : null, _jsx("span", { className: "text-sm text-[rgb(var(--sg-text))]", children: resolvedCheckAllLabel })] }), _jsx("hr", { className: "mt-2 border-[rgb(var(--sg-border))]" })] })) : null;
115
+ ${!enabled ? "opacity-50 cursor-not-allowed" : readOnly ? "cursor-default" : "cursor-pointer"}
116
+ `, children: [_jsx("input", { ref: checkAllRef, type: "checkbox", checked: allChecked, disabled: !enabled || readOnly, onChange: handleCheckAll, className: "w-4 h-4 rounded text-[rgb(var(--sg-primary-500))] border-[rgb(var(--sg-border))] focus:ring-[rgb(var(--sg-ring))] focus:ring-2 cursor-pointer disabled:cursor-not-allowed" }), checkAllIcon ? (_jsx("span", { className: "flex items-center justify-center", children: checkAllIcon })) : null, _jsx("span", { className: "text-sm text-[rgb(var(--sg-text))]", children: resolvedCheckAllLabel })] }), _jsx("hr", { className: "mt-2 border-[rgb(var(--sg-border))]" })] })) : null;
117
117
  const resolvedRules = mergeRequiredRule(rules, required, t(i18n, "components.inputs.required"));
118
118
  const groupContent = (_jsxs(_Fragment, { children: [checkAllNode, _jsx("div", { className: `
119
119
  flex ${isHighlightSelection ? "gap-0" : "gap-4"}
120
120
  ${orientation === "vertical" ? "flex-col" : "flex-row flex-wrap"}
121
- `, role: "group", "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, "aria-required": required || undefined, children: source.map((option, index) => renderOption(option, index)) })] }));
121
+ `, role: "group", "aria-disabled": !enabled || undefined, "aria-readonly": readOnly || undefined, "aria-required": required || undefined, children: source.map((option, index) => renderOption(option, index)) })] }));
122
122
  // React Hook Form — Controller
123
123
  if (control && name) {
124
124
  return (_jsx(Controller, { name: name, control: control, rules: resolvedRules, render: ({ field, fieldState }) => SgCheckboxGroupBase({
@@ -139,9 +139,9 @@ function SgCheckboxGroupBase(props, imperativeRef) {
139
139
  flex ${isHighlightSelection ? "gap-0" : "gap-4"}
140
140
  ${orientation === "vertical" ? "flex-col" : "flex-row flex-wrap"}
141
141
  ${showCheckAll && !isHighlightSelection ? "mt-1" : ""}
142
- `, role: "group", "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, "aria-required": required || undefined, children: source.map((option, index) => {
142
+ `, role: "group", "aria-disabled": !enabled || undefined, "aria-readonly": readOnly || undefined, "aria-required": required || undefined, children: source.map((option, index) => {
143
143
  const isSelected = currentValue.includes(option.value);
144
- const isDisabled = disabled || option.disabled;
144
+ const isDisabled = !enabled || (option.enabled === false);
145
145
  return (_jsxs("label", { className: `
146
146
  inline-flex items-center gap-2 cursor-pointer select-none
147
147
  ${isDisabled ? "opacity-50 cursor-not-allowed" : ""}
@@ -47,7 +47,7 @@ function findTypeAheadMatchIndex(entries, query, startIndex) {
47
47
  for (let offset = 1; offset <= entries.length; offset += 1) {
48
48
  const index = (startIndex + offset + entries.length) % entries.length;
49
49
  const entry = entries[index];
50
- if (!entry || entry.item.disabled)
50
+ if (!entry || (entry.item.enabled === false))
51
51
  continue;
52
52
  const label = entry.item.label?.toLowerCase() ?? "";
53
53
  if (label.startsWith(normalized))
@@ -203,7 +203,7 @@ function SgComboboxBase(props) {
203
203
  }, [resolvedBorderRadius]);
204
204
  const selectIndex = React.useCallback((index) => {
205
205
  const entry = entries[index];
206
- if (!entry || entry.item.disabled)
206
+ if (!entry || (entry.item.enabled === false))
207
207
  return;
208
208
  setSelectedValue(entry.item.id);
209
209
  setLastSelectedLabel(entry.item.label);
@@ -237,7 +237,7 @@ function SgComboboxBase(props) {
237
237
  if (nextIndex < 0)
238
238
  return;
239
239
  const nextEntry = availableEntries[nextIndex];
240
- if (!nextEntry || nextEntry.item.disabled)
240
+ if (!nextEntry || (nextEntry.item.enabled === false))
241
241
  return;
242
242
  setSelectedValue(nextEntry.item.id);
243
243
  setLastSelectedLabel(nextEntry.item.label);
@@ -402,13 +402,13 @@ function SgComboboxBase(props) {
402
402
  } }), renderValue && displayedValue ? (_jsx("div", { className: "pointer-events-none absolute inset-y-0 left-3 right-10 z-10 flex items-center overflow-hidden", children: renderValue(selectedVisualItem) })) : null, open && !isDisabled && typeof document !== "undefined"
403
403
  ? createPortal(_jsxs("div", { ref: dropdownRef, className: "z-[1100] overflow-hidden rounded-md border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-lg", style: dropdownStyle, children: [_jsx("div", { className: "max-h-64 overflow-auto", children: loading ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: loadingText })) : entries.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyText })) : groupedEntries ? (groupedEntries.map(({ group, list }) => (_jsxs("div", { className: "border-b border-border last:border-b-0", children: [(renderGroupHeader ? renderGroupHeader(group) : (group || null)) ? (_jsx("div", { className: "px-3 py-1 text-xs font-semibold text-muted-foreground", children: renderGroupHeader ? renderGroupHeader(group) : group })) : null, list.map(({ entry, index }) => {
404
404
  const isActive = activeIndex === index;
405
- return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${entry.item.disabled ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
405
+ return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${(entry.item.enabled === false) ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
406
406
  event.preventDefault();
407
407
  ignoreBlurRef.current = true;
408
408
  }, onClick: () => selectIndex(index), children: [renderItem ? renderItem(entry.item, isActive) : entry.item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(entry.item) })) : null] }, entry.item.id));
409
409
  })] }, group || "default")))) : (entries.map((entry, index) => {
410
410
  const isActive = activeIndex === index;
411
- return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${entry.item.disabled ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
411
+ return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${(entry.item.enabled === false) ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
412
412
  event.preventDefault();
413
413
  ignoreBlurRef.current = true;
414
414
  }, onClick: () => selectIndex(index), children: [renderItem ? renderItem(entry.item, isActive) : entry.item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(entry.item) })) : null] }, entry.item.id));
@@ -739,10 +739,10 @@ function SgDatatableBase(props, imperativeRef) {
739
739
  commitSelection(next);
740
740
  }, [selectionMode, currentSelection, dataKey, commitSelection]);
741
741
  const resolvedColumnManagerLabel = columnManagerLabel ??
742
- resolveMessage(t(i18n, "components.datatable.columns"), "components.datatable.columns", "Colunas");
743
- const restoreColumnsLabel = resolveMessage(t(i18n, "components.datatable.restoreColumns"), "components.datatable.restoreColumns", "Restaurar padrão");
744
- const showAllColumnsLabel = resolveMessage(t(i18n, "components.datatable.showAllColumns"), "components.datatable.showAllColumns", "Mostrar todas");
745
- const dragHandleLabel = resolveMessage(t(i18n, "components.datatable.dragToReorder"), "components.datatable.dragToReorder", "Arrastar para reordenar");
742
+ resolveMessage(t(i18n, "components.datatable.columns"), "components.datatable.columns", "Columns");
743
+ const restoreColumnsLabel = resolveMessage(t(i18n, "components.datatable.restoreColumns"), "components.datatable.restoreColumns", "Restore default");
744
+ const showAllColumnsLabel = resolveMessage(t(i18n, "components.datatable.showAllColumns"), "components.datatable.showAllColumns", "Show all");
745
+ const dragHandleLabel = resolveMessage(t(i18n, "components.datatable.dragToReorder"), "components.datatable.dragToReorder", "Drag to reorder");
746
746
  const toggleColumnVisibility = React.useCallback((columnKey, forceVisible) => {
747
747
  setHiddenColumnsMap((prev) => {
748
748
  const isCurrentlyHidden = Boolean(prev[columnKey]);
@@ -806,10 +806,10 @@ function SgDatatableBase(props, imperativeRef) {
806
806
  const cardHeaderLabel = (column) => typeof column.header === "string" || typeof column.header === "number"
807
807
  ? String(column.header)
808
808
  : column.field ?? "";
809
- const sortByLabel = resolveMessage(t(i18n, "components.datatable.sortBy"), "components.datatable.sortBy", "Ordenar por");
810
- const sortNoneLabel = resolveMessage(t(i18n, "components.datatable.sortNone"), "components.datatable.sortNone", "Sem ordem");
811
- const sortDirectionLabel = resolveMessage(t(i18n, "components.datatable.sortDirection"), "components.datatable.sortDirection", "Inverter ordem");
812
- const filtersPanelLabel = resolveMessage(t(i18n, "components.datatable.filters"), "components.datatable.filters", "Filtros");
809
+ const sortByLabel = resolveMessage(t(i18n, "components.datatable.sortBy"), "components.datatable.sortBy", "Sort by");
810
+ const sortNoneLabel = resolveMessage(t(i18n, "components.datatable.sortNone"), "components.datatable.sortNone", "No order");
811
+ const sortDirectionLabel = resolveMessage(t(i18n, "components.datatable.sortDirection"), "components.datatable.sortDirection", "Reverse order");
812
+ const filtersPanelLabel = resolveMessage(t(i18n, "components.datatable.filters"), "components.datatable.filters", "Filters");
813
813
  const resolvedTitle = (groupBoxProps.title ?? title ?? "").trim() || " ";
814
814
  return (_jsx("div", { ref: rootRef, className: className, style: style, children: _jsx(SgGroupBox, { ...groupBoxProps, title: resolvedTitle, children: _jsxs("div", { className: "space-y-3", children: [shouldShowToolbar ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [showGlobalFilter ? (_jsx("input", { value: currentGlobalFilter, onChange: (event) => commitFilters(currentFilters, event.target.value), placeholder: globalFilterLabel, className: "min-w-[220px] flex-1 rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-3 py-2 text-sm text-[rgb(var(--sg-input-fg,var(--sg-text)))] placeholder:text-[rgb(var(--sg-input-placeholder,var(--sg-muted)))] outline-none focus:ring-2 focus:ring-[rgb(var(--sg-ring))]" })) : null, showClearFiltersButton ? (_jsx(SgButton, { size: "sm", appearance: "outline", onClick: clearFilters, children: clearFiltersLabel })) : null] })) : null, _jsxs("div", { className: "relative", ref: columnManagerRef, children: [showColumnManager ? (_jsx("div", { className: "mb-2 flex items-center justify-start", children: _jsx("button", { ref: columnManagerButtonRef, type: "button", "aria-label": resolvedColumnManagerLabel, title: resolvedColumnManagerLabel, onClick: () => setIsColumnManagerOpen((prev) => !prev), className: "inline-flex h-9 w-9 items-center justify-center rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] text-[rgb(var(--sg-primary-600))] shadow-sm transition hover:bg-[rgb(var(--sg-primary-50))] hover:text-[rgb(var(--sg-primary-700))]", children: _jsx(SlidersHorizontal, { size: 18 }) }) })) : null, showColumnManager && isColumnManagerOpen && columnManagerPosition && typeof document !== "undefined"
815
815
  ? createPortal(_jsxs("div", { ref: columnManagerPanelRef, className: "fixed z-[1400] w-[300px] rounded-xl border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] shadow-xl", style: {
@@ -850,7 +850,7 @@ function SgDatatableBase(props, imperativeRef) {
850
850
  }, className: "min-w-0 flex-1 rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-2 py-2 text-sm text-[rgb(var(--sg-input-fg,var(--sg-text)))]", children: [_jsx("option", { value: "", children: sortNoneLabel }), cardSortColumns.map((column, columnIndex) => {
851
851
  const key = (column.sortField ?? column.field);
852
852
  return (_jsx("option", { value: key, children: cardHeaderLabel(column) }, `sort-${column.columnId ?? key}-${columnIndex}`));
853
- })] }), _jsx(SgButton, { size: "sm", appearance: "outline", iconOnly: true, "aria-label": sortDirectionLabel, title: sortDirectionLabel, disabled: !currentSortField || currentSortOrder === 0, onClick: () => {
853
+ })] }), _jsx(SgButton, { size: "sm", appearance: "outline", iconOnly: true, "aria-label": sortDirectionLabel, title: sortDirectionLabel, enabled: !(!currentSortField || currentSortOrder === 0), onClick: () => {
854
854
  if (!currentSortField)
855
855
  return;
856
856
  commitSort(currentSortField, currentSortOrder === 1 ? -1 : 1);
@@ -916,14 +916,14 @@ function SgDatatableBase(props, imperativeRef) {
916
916
  })) }), hasFooter ? (_jsx("tfoot", { children: _jsx("tr", { className: "bg-[rgb(var(--sg-primary-50))]", children: visibleColumns.map((column, columnIndex) => {
917
917
  const footerValue = typeof column.footer === "function" ? column.footer(sortedRows) : column.footer;
918
918
  return (_jsx("td", { className: cn("px-3 py-2 font-semibold text-[rgb(var(--sg-text))]", resolveAlignmentClass(column.align), showGridlines ? "border-t border-r border-[rgb(var(--sg-border))] last:border-r-0" : "border-t border-[rgb(var(--sg-border))]", column.className), style: { width: toCssSize(column.width), minWidth: toCssSize(column.minWidth) }, children: footerValue ?? null }, `foot-${column.columnId ?? column.field ?? columnIndex}`));
919
- }) }) })) : null] }) }))] }), infiniteActive ? (_jsx("div", { ref: sentinelRef, "data-sg-infinite-sentinel": "true", role: "status", "aria-live": "polite", className: "px-4 py-3 text-center text-xs text-[rgb(var(--sg-muted))]", children: infiniteHasMore ? (loading ? loadingMoreLabel : "") : pagedRows.length > 0 ? endOfListLabel : "" })) : null, paginator && !infiniteActive ? (_jsxs("div", { ref: paginatorRef, className: "flex flex-wrap items-center gap-1.5", children: [_jsxs("nav", { "aria-label": t(i18n, "components.datatable.pagination"), className: "flex flex-wrap items-center gap-1.5", children: [_jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": firstLabel, title: firstLabel, disabled: currentPage <= 1, onClick: () => goToPage(1), leftIcon: _jsx(ChevronFirst, { size: 16 }) }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": prevLabel, title: prevLabel, disabled: currentPage <= 1, onClick: () => goToPage(currentPage - 1), leftIcon: _jsx(ChevronLeft, { size: 16 }) }), pageTokens.map((token, tokenIndex) => {
919
+ }) }) })) : null] }) }))] }), infiniteActive ? (_jsx("div", { ref: sentinelRef, "data-sg-infinite-sentinel": "true", role: "status", "aria-live": "polite", className: "px-4 py-3 text-center text-xs text-[rgb(var(--sg-muted))]", children: infiniteHasMore ? (loading ? loadingMoreLabel : "") : pagedRows.length > 0 ? endOfListLabel : "" })) : null, paginator && !infiniteActive ? (_jsxs("div", { ref: paginatorRef, className: "flex flex-wrap items-center gap-1.5", children: [_jsxs("nav", { "aria-label": t(i18n, "components.datatable.pagination"), className: "flex flex-wrap items-center gap-1.5", children: [_jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": firstLabel, title: firstLabel, enabled: !(currentPage <= 1), onClick: () => goToPage(1), leftIcon: _jsx(ChevronFirst, { size: 16 }) }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": prevLabel, title: prevLabel, enabled: !(currentPage <= 1), onClick: () => goToPage(currentPage - 1), leftIcon: _jsx(ChevronLeft, { size: 16 }) }), pageTokens.map((token, tokenIndex) => {
920
920
  if (token.type === "ellipsis") {
921
921
  const ellipsisLabel = token.direction === "backward" ? jumpBackwardLabel : jumpForwardLabel;
922
922
  return (_jsx(SgButton, { size: "sm", appearance: "ghost", shape: "default", iconOnly: true, "aria-label": ellipsisLabel, title: ellipsisLabel, onClick: () => goToPage(token.target), leftIcon: _jsx(MoreHorizontal, { size: 16 }) }, `ellipsis-${token.direction}-${tokenIndex}`));
923
923
  }
924
924
  const isCurrent = token.page === currentPage;
925
925
  return (_jsx(SgButton, { size: "sm", appearance: isCurrent ? "solid" : "outline", "aria-label": t(i18n, "components.datatable.page", { page: token.page }), "aria-current": isCurrent ? "page" : undefined, onClick: () => goToPage(token.page), children: String(token.page) }, `page-${token.page}`));
926
- }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": nextLabel, title: nextLabel, disabled: currentPage >= pageCount, onClick: () => goToPage(currentPage + 1), leftIcon: _jsx(ChevronRight, { size: 16 }) }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": lastLabel, title: lastLabel, disabled: currentPage >= pageCount, onClick: () => goToPage(pageCount), leftIcon: _jsx(ChevronLast, { size: 16 }) })] }), _jsx("span", { className: "min-w-[180px] text-xs text-[rgb(var(--sg-muted))] md:ml-auto", children: pageReport }), rowsPerPageOptions && rowsPerPageOptions.length > 0 ? (_jsxs("label", { className: "ml-auto inline-flex items-center gap-2 text-xs text-[rgb(var(--sg-muted))] md:ml-0", children: [_jsx("span", { children: rowsPerPageLabel }), _jsx("select", { value: currentRows, onChange: (event) => {
926
+ }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": nextLabel, title: nextLabel, enabled: !(currentPage >= pageCount), onClick: () => goToPage(currentPage + 1), leftIcon: _jsx(ChevronRight, { size: 16 }) }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": lastLabel, title: lastLabel, enabled: !(currentPage >= pageCount), onClick: () => goToPage(pageCount), leftIcon: _jsx(ChevronLast, { size: 16 }) })] }), _jsx("span", { className: "min-w-[180px] text-xs text-[rgb(var(--sg-muted))] md:ml-auto", children: pageReport }), rowsPerPageOptions && rowsPerPageOptions.length > 0 ? (_jsxs("label", { className: "ml-auto inline-flex items-center gap-2 text-xs text-[rgb(var(--sg-muted))] md:ml-0", children: [_jsx("span", { children: rowsPerPageLabel }), _jsx("select", { value: currentRows, onChange: (event) => {
927
927
  const nextRows = Number(event.target.value) || currentRows;
928
928
  commitPage(0, nextRows);
929
929
  }, className: "rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-2 py-1 text-xs text-[rgb(var(--sg-input-fg,var(--sg-text)))]", children: rowsPerPageOptions.map((option) => (_jsx("option", { value: option, children: option }, option))) })] })) : null] })) : null] }) }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"SgDatatable.meta.d.ts","sourceRoot":"","sources":["../../src/inputs/SgDatatable.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAiCpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAoBrB,CAAC"}
1
+ {"version":3,"file":"SgDatatable.meta.d.ts","sourceRoot":"","sources":["../../src/inputs/SgDatatable.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAoCpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAoBrB,CAAC"}
@@ -13,7 +13,10 @@ export const sgMeta = {
13
13
  fieldSemantics: ["tabularData", "dataGrid", "listing", "collectionView"],
14
14
  props: [
15
15
  { name: "id", type: "string", description: "Identificador da tabela usado para DOM e persistencia do layout de colunas.", semanticRole: "data", bindable: true },
16
- { name: "columns", type: "SgDatatableColumn[]", description: "Definicao das colunas da tabela.", semanticRole: "data", bindable: true },
16
+ { name: "columns", type: "SgDatatableColumn[]", description: "Definicao das colunas da tabela. column.filterPlaceholder vence o dicionario (components.datatable.columnFilterPlaceholder).", semanticRole: "data", bindable: true },
17
+ { name: "emptyMessage", type: "string", default: "(dicionario)", description: "Mensagem exibida quando nao ha registros. Vence o dicionario (components.datatable.empty).", semanticRole: "appearance", bindable: true },
18
+ { name: "globalFilterPlaceholder", type: "string", default: "(dicionario)", description: "Placeholder da busca global. Vence o dicionario (components.datatable.globalFilterPlaceholder).", semanticRole: "appearance", bindable: true },
19
+ { name: "columnManagerLabel", type: "string", default: "(dicionario)", description: "Titulo do painel de colunas. Vence o dicionario (components.datatable.columns).", semanticRole: "appearance", bindable: true },
17
20
  { name: "rows", type: "number", description: "Linhas por pagina quando a paginacao esta ativa (os dados vao em value).", semanticRole: "behavior", bindable: true },
18
21
  { name: "selectionMode", type: "\"single\" | \"multiple\"", description: "Modo de selecao de linhas.", semanticRole: "behavior", bindable: true },
19
22
  { name: "loading", type: "boolean", default: false, description: "Indica estado de carregamento.", semanticRole: "appearance", bindable: true },
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { type RhfFieldProps } from "../rhf";
3
+ import { type FileErrorCode, type MediaType, type SizeLimit } from "./fileUploadLogic";
4
+ /** Arquivo ja salvo no servidor (sem bytes): entra na lista como valor inicial ou controlado. */
5
+ export type SgSavedFile = {
6
+ name: string;
7
+ url: string;
8
+ size?: number;
9
+ type?: string;
10
+ };
11
+ /** Item da lista de arquivos: um `File` escolhido agora ou um arquivo ja salvo (`url`). */
12
+ export type SgFileValue = File | SgSavedFile;
13
+ /** Recusa de um arquivo por uma regra de quem usa o componente (ex.: dimensao de imagem). */
14
+ export type SgFileRejection = {
15
+ code: FileErrorCode;
16
+ message: string;
17
+ };
18
+ export type SgFileUploadProps = RhfFieldProps & {
19
+ id: string;
20
+ label?: string;
21
+ labelText?: string;
22
+ /** Arquivos controlados (sempre lista; um arquivo = lista de um). */
23
+ value?: SgFileValue[] | null;
24
+ /** Arquivos iniciais (campo nao controlado). */
25
+ defaultValue?: SgFileValue[];
26
+ /** Lista de arquivos validos apos escolher ou remover. O envio ao servidor e do host. */
27
+ onChange?: (files: SgFileValue[]) => void;
28
+ /** Arquivo recusado: codigo, mensagem ja traduzida e nome do arquivo. Os demais seguem validos. */
29
+ onError?: (code: FileErrorCode, message: string, fileName: string) => void;
30
+ /** Tipos aceitos (`image`, `pdf`, `xlsx`...). O tipo e conferido pelo conteudo do arquivo. */
31
+ mediaType?: MediaType[];
32
+ /** Tipos extras em texto livre (`.pdf`, `image/png`, `image/*`), separados por virgula; soma-se ao mediaType. */
33
+ accept?: string;
34
+ /** Quantos arquivos o campo guarda (padrao 1). Com 1, escolher outro substitui; maior que 1 liga a selecao multipla. */
35
+ maxFiles?: number;
36
+ /** Tamanho maximo de cada arquivo: bytes (numero) ou texto com unidade ("500KB", "1.5MB", "1GB"). */
37
+ maxSizePerFile?: SizeLimit;
38
+ /** Tamanho maximo da soma dos arquivos (mesmo formato). */
39
+ maxSizeTotal?: SizeLimit;
40
+ hintText?: string;
41
+ required?: boolean;
42
+ requiredMessage?: string;
43
+ enabled?: boolean;
44
+ readOnly?: boolean;
45
+ className?: string;
46
+ onEnter?: () => void;
47
+ onExit?: () => void;
48
+ /** Regra extra, assincrona, por arquivo (depois da conferencia de tipo). E como o SgImageUpload confere dimensao. */
49
+ extraCheck?: (file: File, info: {
50
+ mime: string;
51
+ }) => Promise<SgFileRejection | null>;
52
+ /** Miniatura de cada arquivo (sem ela, a lista usa o icone do tipo). */
53
+ thumbnailBuilder?: (file: SgFileValue, size: number) => React.ReactNode;
54
+ /** Lado da miniatura em px (padrao 96). */
55
+ thumbnailSize?: number;
56
+ /** Chaves de i18n do texto da area vazia (um arquivo / varios). */
57
+ dropTextKey?: string;
58
+ dropManyTextKey?: string;
59
+ /** Texto dos tipos aceitos na mensagem de tipo recusado (padrao: mediaType/accept). */
60
+ typeNames?: string;
61
+ /** Valor do atributo `accept` do seletor do navegador (padrao: derivado de mediaType/accept). */
62
+ pickerAccept?: string;
63
+ /** Prefixo do `data-sg` dos elementos internos (`sg-file`, `sg-image`). */
64
+ keyPrefix?: string;
65
+ };
66
+ /** Atributo `accept` do seletor: grupos viram `image/*`, formatos viram MIME + extensoes; `all` libera tudo. */
67
+ export declare function pickerAcceptFor(mediaType?: MediaType[], accept?: string): string | undefined;
68
+ export declare function SgFileUpload(props: SgFileUploadProps): import("react/jsx-runtime").JSX.Element;
69
+ //# sourceMappingURL=SgFileUpload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SgFileUpload.d.ts","sourceRoot":"","sources":["../../src/inputs/SgFileUpload.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE/D,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAE3B,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF,2FAA2F;AAC3F,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,WAAW,CAAC;AAE7C,6FAA6F;AAC7F,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,KAAK,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC7B,gDAAgD;IAChD,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC1C,mGAAmG;IACnG,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3E,8FAA8F;IAC9F,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB,iHAAiH;IACjH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wHAAwH;IACxH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qGAAqG;IACrG,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,2DAA2D;IAC3D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,qHAAqH;IACrH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACrF,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACxE,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAgCF,gHAAgH;AAChH,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAe5F;AA4SD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CA8BpD"}
@@ -0,0 +1,234 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { Controller } from "react-hook-form";
5
+ import { File as FileIcon, FileArchive, FileAudio, FileImage, FileSpreadsheet, FileText, FileVideo, Presentation, Upload, X } from "lucide-react";
6
+ import { resolveFieldError } from "../rhf";
7
+ import { t, useComponentsI18n } from "../i18n";
8
+ import { MEDIA_TYPES, formatFileSize, matchesMedia, parseSize, planAdd, resolveMime } from "./fileUploadLogic";
9
+ const HEAD_BYTES = 256;
10
+ function isNative(file) {
11
+ return typeof File !== "undefined" && file instanceof File;
12
+ }
13
+ function sizeOf(file) {
14
+ return isNative(file) ? file.size : file.size ?? 0;
15
+ }
16
+ function typeOf(file) {
17
+ return file.type ?? "";
18
+ }
19
+ async function readHead(file) {
20
+ try {
21
+ const slice = file.slice(0, HEAD_BYTES);
22
+ if (typeof slice.arrayBuffer === "function")
23
+ return new Uint8Array(await slice.arrayBuffer());
24
+ // Ambientes sem Blob.arrayBuffer (testes em jsdom, navegadores antigos).
25
+ return await new Promise((resolve) => {
26
+ const reader = new FileReader();
27
+ reader.onload = () => resolve(new Uint8Array(reader.result));
28
+ reader.onerror = () => resolve(new Uint8Array(0));
29
+ reader.readAsArrayBuffer(slice);
30
+ });
31
+ }
32
+ catch {
33
+ return new Uint8Array(0);
34
+ }
35
+ }
36
+ /** Atributo `accept` do seletor: grupos viram `image/*`, formatos viram MIME + extensoes; `all` libera tudo. */
37
+ export function pickerAcceptFor(mediaType, accept) {
38
+ const tokens = new Set();
39
+ for (const m of mediaType ?? []) {
40
+ if (m === "all")
41
+ return undefined;
42
+ const info = MEDIA_TYPES[m];
43
+ if (!info)
44
+ continue;
45
+ if (info.group)
46
+ tokens.add(`${info.group}*`);
47
+ if (info.mime)
48
+ tokens.add(info.mime);
49
+ for (const ext of info.extensions)
50
+ tokens.add(`.${ext}`);
51
+ }
52
+ for (const token of (accept ?? "").split(",")) {
53
+ const trimmed = token.trim();
54
+ if (trimmed)
55
+ tokens.add(trimmed);
56
+ }
57
+ return tokens.size === 0 ? undefined : Array.from(tokens).join(",");
58
+ }
59
+ function iconFor(mime) {
60
+ const cls = "size-5 shrink-0";
61
+ if (mime.startsWith("image/"))
62
+ return _jsx(FileImage, { className: cls, "aria-hidden": "true" });
63
+ if (mime.startsWith("video/"))
64
+ return _jsx(FileVideo, { className: cls, "aria-hidden": "true" });
65
+ if (mime.startsWith("audio/"))
66
+ return _jsx(FileAudio, { className: cls, "aria-hidden": "true" });
67
+ if (mime === "application/pdf")
68
+ return _jsx(FileText, { className: cls, "aria-hidden": "true" });
69
+ if (mime.includes("spreadsheet") || mime === "application/vnd.ms-excel" || mime === "text/csv") {
70
+ return _jsx(FileSpreadsheet, { className: cls, "aria-hidden": "true" });
71
+ }
72
+ if (mime.includes("presentation") || mime === "application/vnd.ms-powerpoint") {
73
+ return _jsx(Presentation, { className: cls, "aria-hidden": "true" });
74
+ }
75
+ if (mime.includes("zip"))
76
+ return _jsx(FileArchive, { className: cls, "aria-hidden": "true" });
77
+ if (mime.startsWith("text/") || mime.includes("word") || mime === "application/msword") {
78
+ return _jsx(FileText, { className: cls, "aria-hidden": "true" });
79
+ }
80
+ return _jsx(FileIcon, { className: cls, "aria-hidden": "true" });
81
+ }
82
+ function SgFileUploadBase(props) {
83
+ const i18n = useComponentsI18n();
84
+ const { id, label, labelText, value, defaultValue, onChange, onError, mediaType, accept, maxFiles, maxSizePerFile, maxSizeTotal, hintText, required, enabled = true, readOnly, className, onEnter, onExit, extraCheck, thumbnailBuilder, thumbnailSize = 96, dropTextKey, dropManyTextKey, typeNames, pickerAccept, keyPrefix = "sg-file", error } = props;
85
+ const max = Math.max(1, Math.floor(maxFiles ?? 1));
86
+ const multi = max > 1;
87
+ const controlled = value !== undefined;
88
+ const [inner, setInner] = React.useState(defaultValue ?? []);
89
+ const current = controlled ? value ?? [] : inner;
90
+ const currentRef = React.useRef(current);
91
+ currentRef.current = current;
92
+ const [internalError, setInternalError] = React.useState(null);
93
+ const [loading, setLoading] = React.useState(false);
94
+ const [dragging, setDragging] = React.useState(false);
95
+ const inputRef = React.useRef(null);
96
+ const locked = Boolean(!enabled || readOnly);
97
+ const text = label ?? labelText ?? "";
98
+ const typesText = () => {
99
+ if (typeNames)
100
+ return typeNames;
101
+ const names = [...(mediaType ?? []), ...((accept ?? "").trim() ? [(accept ?? "").trim()] : [])];
102
+ return names.length === 0 ? "all" : names.join(", ");
103
+ };
104
+ const messageFor = (code) => {
105
+ switch (code) {
106
+ case "type":
107
+ return t(i18n, "components.inputs.fileupload.type", { types: typesText() });
108
+ case "size":
109
+ return t(i18n, "components.inputs.fileupload.size", { max: formatFileSize(parseSize(maxSizePerFile) ?? 0, i18n.locale) });
110
+ case "total":
111
+ return t(i18n, "components.inputs.fileupload.total", { max: formatFileSize(parseSize(maxSizeTotal) ?? 0, i18n.locale) });
112
+ case "count":
113
+ return t(i18n, "components.inputs.fileupload.count", { max: String(max) });
114
+ default:
115
+ return ""; // dimension e aspect so chegam por extraCheck, que traz a propria mensagem.
116
+ }
117
+ };
118
+ const commit = (files, errorText = null) => {
119
+ setInternalError(errorText);
120
+ if (!controlled)
121
+ setInner(files);
122
+ onChange?.(files);
123
+ };
124
+ const addFiles = async (picked) => {
125
+ const rejected = [];
126
+ const survivors = [];
127
+ if (extraCheck)
128
+ setLoading(true);
129
+ for (const file of picked) {
130
+ const head = await readHead(file);
131
+ const mime = resolveMime(file.name, file.type, head);
132
+ if (matchesMedia(file.name, mime, mediaType, accept) && extraCheck) {
133
+ const rejection = await extraCheck(file, { mime });
134
+ if (rejection) {
135
+ rejected.push({ file, code: rejection.code, message: rejection.message });
136
+ continue;
137
+ }
138
+ }
139
+ survivors.push({ file, head });
140
+ }
141
+ if (extraCheck)
142
+ setLoading(false);
143
+ const existing = currentRef.current;
144
+ const plan = planAdd(existing.map(sizeOf), survivors.map(({ file, head }) => ({ name: file.name, type: file.type, size: file.size, head })), { mediaType, accept, maxFiles: max, maxSizePerFile, maxSizeTotal });
145
+ for (const r of plan.rejected) {
146
+ const survivor = survivors[r.index];
147
+ if (survivor)
148
+ rejected.push({ file: survivor.file, code: r.code, message: messageFor(r.code) });
149
+ }
150
+ const lines = rejected.map((r) => t(i18n, "components.inputs.fileupload.file", { name: r.file.name, message: r.message }));
151
+ const errorText = lines.length === 0 ? null : lines.join("\n");
152
+ for (const r of rejected)
153
+ onError?.(r.code, r.message, r.file.name);
154
+ if (plan.accepted.length === 0) {
155
+ setInternalError(errorText);
156
+ return;
157
+ }
158
+ const added = plan.accepted.map((index) => survivors[index]?.file).filter((f) => Boolean(f));
159
+ commit(plan.replace ? added : [...existing, ...added], errorText);
160
+ };
161
+ const onPick = (event) => {
162
+ const files = Array.from(event.currentTarget.files ?? []);
163
+ event.currentTarget.value = ""; // permite escolher o mesmo arquivo de novo
164
+ if (files.length > 0)
165
+ void addFiles(files);
166
+ };
167
+ const onDrop = (event) => {
168
+ event.preventDefault();
169
+ setDragging(false);
170
+ if (locked)
171
+ return;
172
+ const files = Array.from(event.dataTransfer.files ?? []);
173
+ if (files.length > 0)
174
+ void addFiles(multi ? files : files.slice(0, 1));
175
+ };
176
+ const open = () => {
177
+ if (locked || loading)
178
+ return;
179
+ onEnter?.();
180
+ inputRef.current?.click();
181
+ };
182
+ const remove = (index) => {
183
+ commit(current.filter((_, i) => i !== index));
184
+ };
185
+ const shownError = resolveFieldError(error, internalError ?? undefined);
186
+ const hasError = Boolean(shownError);
187
+ const full = current.length >= max;
188
+ // Um arquivo so: a area some quando ha arquivo (tocar em "Trocar" escolhe outro). Varios: itens + area enquanto couber.
189
+ const showZone = multi ? !full : current.length === 0;
190
+ const dropText = multi
191
+ ? t(i18n, dropManyTextKey ?? "components.inputs.fileupload.dropMany", { max: String(max) })
192
+ : t(i18n, dropTextKey ?? "components.inputs.fileupload.drop");
193
+ const removeLabel = t(i18n, "components.inputs.fileupload.remove");
194
+ const acceptAttr = pickerAccept ?? pickerAcceptFor(mediaType, accept);
195
+ const renderItem = (file, index) => {
196
+ const sizeText = formatFileSize(sizeOf(file), i18n.locale);
197
+ const removeButton = locked ? null : (_jsx("button", { type: "button", onClick: () => remove(index), "aria-label": removeLabel, "data-sg": `${keyPrefix}-remove-${id}-${index}`, className: "inline-flex size-6 shrink-0 items-center justify-center rounded-full hover:bg-muted/60", children: _jsx(X, { className: "size-4", "aria-hidden": "true" }) }));
198
+ if (thumbnailBuilder) {
199
+ return (_jsxs("div", { "data-sg": `${keyPrefix}-item-${id}-${index}`, style: { width: thumbnailSize }, children: [_jsxs("div", { className: "relative overflow-hidden rounded-md", style: { width: thumbnailSize, height: thumbnailSize }, children: [thumbnailBuilder(file, thumbnailSize), removeButton ? _jsx("span", { className: "absolute right-0 top-0 rounded-full bg-background/85", children: removeButton }) : null] }), _jsx("div", { className: "mt-1 truncate text-xs", children: file.name }), _jsx("div", { className: "text-[10px] text-muted-foreground", children: sizeText })] }, `${file.name}-${index}`));
200
+ }
201
+ const mime = resolveMime(file.name, typeOf(file), new Uint8Array(0));
202
+ return (_jsxs("div", { "data-sg": `${keyPrefix}-item-${id}-${index}`, className: "flex items-center gap-2 rounded-md border border-border px-2 py-1", children: [iconFor(mime), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm", children: file.name }), _jsx("div", { className: "text-[10px] text-muted-foreground", children: sizeText })] }), removeButton] }, `${file.name}-${index}`));
203
+ };
204
+ return (_jsxs("div", { className: ["w-full", className].filter(Boolean).join(" "), children: [text ? (_jsxs("label", { htmlFor: id, className: "mb-1 block text-xs font-medium text-foreground/70", children: [text, required ? _jsx("span", { "aria-hidden": "true", children: " *" }) : null] })) : null, _jsx("input", { ref: inputRef, id: id, type: "file", multiple: multi, accept: acceptAttr, className: "sr-only", disabled: locked, required: required && current.length === 0, "aria-invalid": hasError || undefined, "aria-describedby": hasError ? `${id}-error` : hintText ? `${id}-hint` : undefined, onChange: onPick, onFocus: () => onEnter?.(), onBlur: () => onExit?.() }), current.length > 0 ? (_jsx("div", { className: ["mb-2", thumbnailBuilder ? "flex flex-wrap gap-2" : "flex flex-col gap-1.5"].join(" "), "data-sg": `${keyPrefix}-list-${id}`, children: current.map(renderItem) })) : null, showZone || loading ? (_jsx("div", { role: "button", tabIndex: locked ? -1 : 0, "aria-disabled": locked || undefined, "aria-label": text || dropText, "data-sg": `${keyPrefix}-open-${id}`, "data-dragging": dragging || undefined, onClick: open, onKeyDown: (event) => {
205
+ if (event.key === "Enter" || event.key === " ") {
206
+ event.preventDefault();
207
+ open();
208
+ }
209
+ }, onDragOver: (event) => {
210
+ event.preventDefault();
211
+ if (!locked)
212
+ setDragging(true);
213
+ }, onDragLeave: () => setDragging(false), onDrop: onDrop, className: [
214
+ "relative flex min-h-24 w-full items-center justify-center overflow-hidden rounded-md border border-dashed bg-[var(--sg-input-bg,hsl(var(--background)))] text-sm shadow-sm outline-none transition-colors",
215
+ hasError ? "border-[hsl(var(--destructive))]" : dragging ? "border-[hsl(var(--primary))] bg-muted/40" : "border-border",
216
+ locked ? "cursor-not-allowed opacity-60" : "cursor-pointer focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary)/0.4)]"
217
+ ].join(" "), children: loading ? (_jsx("span", { className: "text-muted-foreground", children: t(i18n, "components.inputs.fileupload.loading") })) : (_jsxs("span", { className: "flex flex-col items-center gap-2 px-4 py-4 text-center text-muted-foreground", children: [_jsx(Upload, { className: "size-6", "aria-hidden": "true" }), dropText] })) })) : null, !multi && current.length > 0 && !locked && !loading ? (_jsx("button", { type: "button", onClick: open, "data-sg": `${keyPrefix}-change-${id}`, className: "mt-1 inline-flex items-center gap-1 rounded px-2 py-1 text-xs text-primary hover:bg-muted/40", children: t(i18n, "components.inputs.imageupload.change") })) : null, hasError ? (_jsx("p", { id: `${id}-error`, role: "alert", className: "mt-1 whitespace-pre-line text-xs text-[hsl(var(--destructive))]", children: shownError })) : hintText ? (_jsx("p", { id: `${id}-hint`, className: "mt-1 text-xs text-muted-foreground", children: hintText })) : null] }));
218
+ }
219
+ export function SgFileUpload(props) {
220
+ const { control, name, rules, required, requiredMessage, onChange, error } = props;
221
+ const i18n = useComponentsI18n();
222
+ if (control && name) {
223
+ const resolvedRules = {
224
+ ...(required ? { required: requiredMessage ?? t(i18n, "components.inputs.required") } : {}),
225
+ ...rules
226
+ };
227
+ return (_jsx(Controller, { name: name, control: control, rules: resolvedRules, render: ({ field, fieldState }) => (_jsx(SgFileUploadBase, { ...props, control: undefined, value: Array.isArray(field.value) ? field.value : [], onChange: (files) => {
228
+ // Lista vazia vira null no formulario, para o "obrigatorio" acusar o campo sem arquivo.
229
+ field.onChange(files.length === 0 ? null : files);
230
+ onChange?.(files);
231
+ }, error: resolveFieldError(error, fieldState.error?.message) })) }));
232
+ }
233
+ return _jsx(SgFileUploadBase, { ...props });
234
+ }
@@ -0,0 +1,4 @@
1
+ import type { SgAiHintsV0, SgMetaV0 } from "../ai-meta/types";
2
+ export declare const sgMeta: SgMetaV0;
3
+ export declare const aiHints: SgAiHintsV0;
4
+ //# sourceMappingURL=SgFileUpload.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SgFileUpload.meta.d.ts","sourceRoot":"","sources":["../../src/inputs/SgFileUpload.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAmDpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}