@customafk/lunas-ui 0.2.79 → 0.2.81

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 (165) hide show
  1. package/dist/{analysis-panel-Bg0lCfWv.cjs → analysis-panel-DYF2CmAH.cjs} +2 -2
  2. package/dist/{analysis-panel-Bg0lCfWv.cjs.map → analysis-panel-DYF2CmAH.cjs.map} +1 -1
  3. package/dist/{analysis-panel-Cop-l4lx.mjs → analysis-panel-_s1fgcZC.mjs} +2 -2
  4. package/dist/{analysis-panel-Cop-l4lx.mjs.map → analysis-panel-_s1fgcZC.mjs.map} +1 -1
  5. package/dist/{badge-BPiU1Ss2.d.cts → badge-C_KLKwiS.d.cts} +2 -2
  6. package/dist/{badge-BPiU1Ss2.d.mts → badge-C_KLKwiS.d.mts} +2 -2
  7. package/dist/{badge-display-B0BKsU9T.mjs → badge-display-D6d7wTEE.mjs} +2 -2
  8. package/dist/{badge-display-B0BKsU9T.mjs.map → badge-display-D6d7wTEE.mjs.map} +1 -1
  9. package/dist/{badge-display-Dv2UTvzp.cjs → badge-display-DWzSl3rs.cjs} +2 -2
  10. package/dist/{badge-display-Dv2UTvzp.cjs.map → badge-display-DWzSl3rs.cjs.map} +1 -1
  11. package/dist/{boolean-display-CPX7H39m.cjs → boolean-display-C5GIuWnc.cjs} +2 -2
  12. package/dist/{boolean-display-CPX7H39m.cjs.map → boolean-display-C5GIuWnc.cjs.map} +1 -1
  13. package/dist/{boolean-display-CMX5AupA.mjs → boolean-display-Wko_5Jd5.mjs} +2 -2
  14. package/dist/{boolean-display-CMX5AupA.mjs.map → boolean-display-Wko_5Jd5.mjs.map} +1 -1
  15. package/dist/{button-DQLIL8K2.d.cts → button-CnKmz4qM.d.cts} +4 -4
  16. package/dist/{button-DQLIL8K2.d.mts → button-CnKmz4qM.d.mts} +4 -4
  17. package/dist/{calendar-5VVBPn4F.d.mts → calendar-BT6-Fo3T.d.mts} +2 -2
  18. package/dist/{calendar-DHcoDDIc.d.cts → calendar-D_QmR9VE.d.cts} +2 -2
  19. package/dist/{carousel-D0jTbSoW.d.cts → carousel-BBI4xPdK.d.mts} +2 -2
  20. package/dist/{carousel-qH6YsFKK.d.mts → carousel-B_IXlBdH.d.cts} +2 -2
  21. package/dist/{currency-display-CanjOswc.mjs → currency-display-BGkpGtN_.mjs} +2 -2
  22. package/dist/{currency-display-CanjOswc.mjs.map → currency-display-BGkpGtN_.mjs.map} +1 -1
  23. package/dist/{currency-display-xxK62yo4.cjs → currency-display-Bbh5InhV.cjs} +2 -2
  24. package/dist/{currency-display-xxK62yo4.cjs.map → currency-display-Bbh5InhV.cjs.map} +1 -1
  25. package/dist/{date-display-DMPhFq8v.cjs → date-display-D38qvsh2.cjs} +2 -2
  26. package/dist/{date-display-DMPhFq8v.cjs.map → date-display-D38qvsh2.cjs.map} +1 -1
  27. package/dist/{date-display-YE8NIIrl.mjs → date-display-DXyb-Ai2.mjs} +2 -2
  28. package/dist/{date-display-YE8NIIrl.mjs.map → date-display-DXyb-Ai2.mjs.map} +1 -1
  29. package/dist/{description-display-B4KoH6ql.mjs → description-display-BwBMrRAi.mjs} +2 -2
  30. package/dist/{description-display-B4KoH6ql.mjs.map → description-display-BwBMrRAi.mjs.map} +1 -1
  31. package/dist/{description-display-jmAaeMBD.cjs → description-display-DIeHy7ll.cjs} +2 -2
  32. package/dist/{description-display-jmAaeMBD.cjs.map → description-display-DIeHy7ll.cjs.map} +1 -1
  33. package/dist/detail-dialog-BCM0iEXQ.cjs +2 -0
  34. package/dist/detail-dialog-BCM0iEXQ.cjs.map +1 -0
  35. package/dist/detail-dialog-DwfTi9Fl.mjs +2 -0
  36. package/dist/detail-dialog-DwfTi9Fl.mjs.map +1 -0
  37. package/dist/dialogs/detail-dialog/components/sidebar.d.cts +2 -2
  38. package/dist/dialogs/detail-dialog/components/sidebar.d.mts +2 -2
  39. package/dist/dialogs/detail-dialog/index.cjs +1 -1
  40. package/dist/dialogs/detail-dialog/index.d.cts +1 -1
  41. package/dist/dialogs/detail-dialog/index.d.mts +1 -1
  42. package/dist/dialogs/detail-dialog/index.mjs +1 -1
  43. package/dist/{email-display-D_k7AUmp.mjs → email-display-Be3b-kDL.mjs} +2 -2
  44. package/dist/{email-display-D_k7AUmp.mjs.map → email-display-Be3b-kDL.mjs.map} +1 -1
  45. package/dist/{email-display-CzvqQMhG.cjs → email-display-_FRE2qcO.cjs} +2 -2
  46. package/dist/{email-display-CzvqQMhG.cjs.map → email-display-_FRE2qcO.cjs.map} +1 -1
  47. package/dist/features/descriptions/index.d.cts +1 -1
  48. package/dist/features/descriptions/index.d.mts +1 -1
  49. package/dist/features/file-icons/index.cjs +2 -0
  50. package/dist/features/file-icons/index.cjs.map +1 -0
  51. package/dist/features/file-icons/index.d.cts +34 -0
  52. package/dist/features/file-icons/index.d.mts +34 -0
  53. package/dist/features/file-icons/index.mjs +2 -0
  54. package/dist/features/file-icons/index.mjs.map +1 -0
  55. package/dist/features/tables/index.cjs +1 -1
  56. package/dist/features/tables/index.d.cts +1 -1
  57. package/dist/features/tables/index.d.mts +1 -1
  58. package/dist/features/tables/index.mjs +1 -1
  59. package/dist/filter-C2wE49q7.mjs +2 -0
  60. package/dist/{filter-BCNjqiBx.mjs.map → filter-C2wE49q7.mjs.map} +1 -1
  61. package/dist/filter-z7p4lRp8.cjs +2 -0
  62. package/dist/{filter-B5EnnL9I.cjs.map → filter-z7p4lRp8.cjs.map} +1 -1
  63. package/dist/{flex-CWo0w9K1.d.cts → flex-CIzZIICK.d.cts} +2 -2
  64. package/dist/{flex-CWo0w9K1.d.mts → flex-CIzZIICK.d.mts} +2 -2
  65. package/dist/{index-CLx89ATZ.d.cts → index-BSj8azta.d.cts} +2 -2
  66. package/dist/{index-BOifmu0V.d.cts → index-BXqx1kOJ.d.cts} +4 -2
  67. package/dist/{index-BOifmu0V.d.mts → index-BXqx1kOJ.d.mts} +4 -2
  68. package/dist/{index-BMuVmm7f.d.mts → index-CEmVk1zP.d.mts} +2 -2
  69. package/dist/{index-CFgFCg9H.d.mts → index-CjiDR-94.d.mts} +16 -15
  70. package/dist/{index-BapooVnN.d.cts → index-DqAbPipJ.d.cts} +16 -15
  71. package/dist/index.cjs +1 -1
  72. package/dist/index.d.cts +14 -14
  73. package/dist/index.d.mts +14 -14
  74. package/dist/index.mjs +1 -1
  75. package/dist/{item-CtLZgUB6.d.cts → item-BxUBO7Hd.d.cts} +2 -2
  76. package/dist/{item-Brbp9t6f.d.mts → item-ChW41yKF.d.mts} +2 -2
  77. package/dist/layouts/flex.d.cts +1 -1
  78. package/dist/layouts/flex.d.mts +1 -1
  79. package/dist/{list-display-DPxZ_Jbf.cjs → list-display-BvKGl7uq.cjs} +2 -2
  80. package/dist/{list-display-DPxZ_Jbf.cjs.map → list-display-BvKGl7uq.cjs.map} +1 -1
  81. package/dist/{list-display-M43N3kle.mjs → list-display-CWQvIMUj.mjs} +2 -2
  82. package/dist/{list-display-M43N3kle.mjs.map → list-display-CWQvIMUj.mjs.map} +1 -1
  83. package/dist/{name-display-pWhLLS05.mjs → name-display-CnTRVqX4.mjs} +2 -2
  84. package/dist/{name-display-pWhLLS05.mjs.map → name-display-CnTRVqX4.mjs.map} +1 -1
  85. package/dist/{name-display-BJ1wPUnS.cjs → name-display-DfOtYp7d.cjs} +2 -2
  86. package/dist/{name-display-BJ1wPUnS.cjs.map → name-display-DfOtYp7d.cjs.map} +1 -1
  87. package/dist/{pagination-BVA_yrBO.d.mts → pagination-AxRzmAtZ.d.cts} +2 -2
  88. package/dist/{pagination-DqJlXNY0.d.cts → pagination-D12pR8_5.d.mts} +2 -2
  89. package/dist/{paragraph-CRo5eC7H.d.mts → paragraph-C5HE9u0Q.d.mts} +2 -2
  90. package/dist/{paragraph-CkCNIsvz.d.cts → paragraph-DJlHIDmk.d.cts} +2 -2
  91. package/dist/{permalink-CMcI__V4.cjs → permalink-29QzG9Xe.cjs} +2 -2
  92. package/dist/{permalink-CMcI__V4.cjs.map → permalink-29QzG9Xe.cjs.map} +1 -1
  93. package/dist/{permalink-BaMvnKIz.mjs → permalink-DhqOATLQ.mjs} +2 -2
  94. package/dist/{permalink-BaMvnKIz.mjs.map → permalink-DhqOATLQ.mjs.map} +1 -1
  95. package/dist/{phone-number-F9I9sNLK.cjs → phone-number-C2k8llhf.cjs} +2 -2
  96. package/dist/{phone-number-F9I9sNLK.cjs.map → phone-number-C2k8llhf.cjs.map} +1 -1
  97. package/dist/{phone-number-ao2oCmrO.mjs → phone-number-CQXrATQO.mjs} +2 -2
  98. package/dist/{phone-number-ao2oCmrO.mjs.map → phone-number-CQXrATQO.mjs.map} +1 -1
  99. package/dist/{progress-display-F8xXJ3T1.cjs → progress-display-B4WVzdJw.cjs} +2 -2
  100. package/dist/{progress-display-F8xXJ3T1.cjs.map → progress-display-B4WVzdJw.cjs.map} +1 -1
  101. package/dist/{progress-display-atwRFD6e.mjs → progress-display-DZb1qjfY.mjs} +2 -2
  102. package/dist/{progress-display-atwRFD6e.mjs.map → progress-display-DZb1qjfY.mjs.map} +1 -1
  103. package/dist/{resizable-B61Fbl57.d.cts → resizable-D61lvcIL.d.cts} +2 -2
  104. package/dist/{resizable-B61Fbl57.d.mts → resizable-D61lvcIL.d.mts} +2 -2
  105. package/dist/{statistic-CJVc9sNs.mjs → statistic-DoNUSe3e.mjs} +2 -2
  106. package/dist/{statistic-CJVc9sNs.mjs.map → statistic-DoNUSe3e.mjs.map} +1 -1
  107. package/dist/{statistic-B9OqfhOl.cjs → statistic-wnnjHsKj.cjs} +2 -2
  108. package/dist/{statistic-B9OqfhOl.cjs.map → statistic-wnnjHsKj.cjs.map} +1 -1
  109. package/dist/{status-display-DInGGgiP.mjs → status-display-CcihiYsH.mjs} +2 -2
  110. package/dist/{status-display-DInGGgiP.mjs.map → status-display-CcihiYsH.mjs.map} +1 -1
  111. package/dist/{status-display-DpKsYvZJ.cjs → status-display-DJ8fFqyX.cjs} +2 -2
  112. package/dist/{status-display-DpKsYvZJ.cjs.map → status-display-DJ8fFqyX.cjs.map} +1 -1
  113. package/dist/{summary-bar-DX_sOiuL.mjs → summary-bar-C4loKj1L.mjs} +2 -2
  114. package/dist/{summary-bar-DX_sOiuL.mjs.map → summary-bar-C4loKj1L.mjs.map} +1 -1
  115. package/dist/{summary-bar-DKZh-VSB.cjs → summary-bar-DK8glsr4.cjs} +2 -2
  116. package/dist/{summary-bar-DKZh-VSB.cjs.map → summary-bar-DK8glsr4.cjs.map} +1 -1
  117. package/dist/{tables-7_KgoyS-.mjs → tables-BTBtN1di.mjs} +2 -2
  118. package/dist/{tables-7_KgoyS-.mjs.map → tables-BTBtN1di.mjs.map} +1 -1
  119. package/dist/{tables-CS3NhgTc.cjs → tables-BzP8Cnvh.cjs} +2 -2
  120. package/dist/{tables-CS3NhgTc.cjs.map → tables-BzP8Cnvh.cjs.map} +1 -1
  121. package/dist/{toggle-v1PgAvdG.d.cts → toggle-CqTmiPDi.d.cts} +2 -2
  122. package/dist/{toggle-v1PgAvdG.d.mts → toggle-CqTmiPDi.d.mts} +2 -2
  123. package/dist/{toggle-group-DM5tS0Kt.d.cts → toggle-group--bJylFDV.d.cts} +2 -2
  124. package/dist/{toggle-group-_PBz44Im.d.mts → toggle-group-l_Mi7NRg.d.mts} +2 -2
  125. package/dist/tooltip-BKcU3bMc.mjs +4 -0
  126. package/dist/tooltip-BKcU3bMc.mjs.map +1 -0
  127. package/dist/tooltip-CLjCwnTD.cjs +4 -0
  128. package/dist/tooltip-CLjCwnTD.cjs.map +1 -0
  129. package/dist/typography/paragraph.d.cts +1 -1
  130. package/dist/typography/paragraph.d.mts +1 -1
  131. package/dist/ui/badge.d.cts +1 -1
  132. package/dist/ui/badge.d.mts +1 -1
  133. package/dist/ui/button.d.cts +1 -1
  134. package/dist/ui/button.d.mts +1 -1
  135. package/dist/ui/calendar.d.cts +1 -1
  136. package/dist/ui/calendar.d.mts +1 -1
  137. package/dist/ui/carousel.d.cts +1 -1
  138. package/dist/ui/carousel.d.mts +1 -1
  139. package/dist/ui/file-uploader.cjs +2 -2
  140. package/dist/ui/file-uploader.cjs.map +1 -1
  141. package/dist/ui/file-uploader.mjs +2 -2
  142. package/dist/ui/file-uploader.mjs.map +1 -1
  143. package/dist/ui/item.d.cts +1 -1
  144. package/dist/ui/item.d.mts +1 -1
  145. package/dist/ui/pagination.d.cts +1 -1
  146. package/dist/ui/pagination.d.mts +1 -1
  147. package/dist/ui/resizable.d.cts +1 -1
  148. package/dist/ui/resizable.d.mts +1 -1
  149. package/dist/ui/status.d.cts +1 -1
  150. package/dist/ui/status.d.mts +1 -1
  151. package/dist/ui/toggle-group.d.cts +1 -1
  152. package/dist/ui/toggle-group.d.mts +1 -1
  153. package/dist/ui/toggle.d.cts +1 -1
  154. package/dist/ui/toggle.d.mts +1 -1
  155. package/package.json +5 -1
  156. package/dist/detail-dialog-Ct0vAjFe.mjs +0 -2
  157. package/dist/detail-dialog-Ct0vAjFe.mjs.map +0 -1
  158. package/dist/detail-dialog-uN5mrCa7.cjs +0 -2
  159. package/dist/detail-dialog-uN5mrCa7.cjs.map +0 -1
  160. package/dist/filter-B5EnnL9I.cjs +0 -2
  161. package/dist/filter-BCNjqiBx.mjs +0 -2
  162. package/dist/tooltip-BcQG6Zw9.cjs +0 -4
  163. package/dist/tooltip-BcQG6Zw9.cjs.map +0 -1
  164. package/dist/tooltip-C6flMQgc.mjs +0 -4
  165. package/dist/tooltip-C6flMQgc.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { n as toggleVariants } from "./toggle-v1PgAvdG.cjs";
1
+ import { n as toggleVariants } from "./toggle-CqTmiPDi.cjs";
2
2
  import { VariantProps } from "class-variance-authority";
3
3
  import { ToggleGroup } from "radix-ui";
4
4
  //#region packages/components/ui/toggle-group.d.ts
@@ -21,4 +21,4 @@ declare function ToggleGroup$1({ className, variant, size, children, ...props }:
21
21
  declare function ToggleGroupItem({ className, children, variant, size, ...props }: React.ComponentProps<typeof ToggleGroup.Item> & VariantProps<typeof toggleVariants>): import("react").JSX.Element;
22
22
  //#endregion
23
23
  export { ToggleGroupItem as n, ToggleGroup$1 as t };
24
- //# sourceMappingURL=toggle-group-DM5tS0Kt.d.cts.map
24
+ //# sourceMappingURL=toggle-group--bJylFDV.d.cts.map
@@ -1,4 +1,4 @@
1
- import { n as toggleVariants } from "./toggle-v1PgAvdG.mjs";
1
+ import { n as toggleVariants } from "./toggle-CqTmiPDi.mjs";
2
2
  import { VariantProps } from "class-variance-authority";
3
3
  import { ToggleGroup } from "radix-ui";
4
4
  //#region packages/components/ui/toggle-group.d.ts
@@ -21,4 +21,4 @@ declare function ToggleGroup$1({ className, variant, size, children, ...props }:
21
21
  declare function ToggleGroupItem({ className, children, variant, size, ...props }: React.ComponentProps<typeof ToggleGroup.Item> & VariantProps<typeof toggleVariants>): import("react").JSX.Element;
22
22
  //#endregion
23
23
  export { ToggleGroupItem as n, ToggleGroup$1 as t };
24
- //# sourceMappingURL=toggle-group-_PBz44Im.d.mts.map
24
+ //# sourceMappingURL=toggle-group-l_Mi7NRg.d.mts.map
@@ -0,0 +1,4 @@
1
+ import{Y as e,Z as t,q as n}from"./tables-BTBtN1di.mjs";import{Input as r}from"./ui/input.mjs";import{ArrowRightIcon as i,BarChart2Icon as a,CirclePlus as o,CopyPlusIcon as s,DownloadIcon as c,LayoutDashboardIcon as l,RefreshCwIcon as u,SearchIcon as d}from"lucide-react";import{useCallback as f}from"react";import{cn as p}from"@customafk/react-toolkit/utils";import{jsx as m,jsxs as h}from"react/jsx-runtime";import{useDebounceCallback as g}from"@customafk/react-toolkit/hooks/useDebounceCallback";function _(e){if(e==null)return``;if(typeof e==`number`)return String(e);if(typeof e==`boolean`)return e?`true`:`false`;let t=String(e);return t.includes(`,`)||t.includes(`"`)||t.includes(`
2
+ `)||t.includes(`\r`)?`"${t.replace(/"/g,`""`)}"`:t}function v(e){if(e.length===0)return``;let t=e[0].map(e=>_(e.label)),n=e.map(e=>e.map(e=>_(e.value)).join(`,`));return[t.join(`,`),...n].join(`
3
+ `)}function y(e,t){let n=v(e);if(!n)return;let r=new Blob([n],{type:`text/csv;charset=utf-8;`}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=`${t}.csv`,a.click(),URL.revokeObjectURL(i)}const b=({onSearch:e,onChange:t,...n})=>{let a=g(t=>{e?.(t)},500);return h(`div`,{className:`relative w-full max-w-80 flex-1`,children:[m(r,{...n,size:`lg`,type:`search`,placeholder:`Tìm kiếm...`,className:`flex-1 ps-9 pe-9`,onChange:e=>{t?.(e),a(e.target.value??``)}}),m(`div`,{className:`pointer-events-none absolute inset-y-0 inset-s-0 flex items-center justify-center ps-3 text-text-positive-weak peer-disabled:opacity-50`,children:m(d,{size:16})}),m(`button`,{className:`absolute inset-y-0 inset-e-0 flex h-full w-9 items-center justify-center rounded-e-md text-text-positive-weak outline-none transition-[color,box-shadow] hover:text-text-positive focus:z-10 focus-visible:border focus-visible:border-primary-strong focus-visible:ring-[3px] focus-visible:ring-primary-weak disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50`,"aria-label":`Submit search`,type:`submit`,children:m(i,{size:16,"aria-hidden":`true`})})]})},x=({children:e,disabled:t,onClick:n,className:r,...i})=>m(`button`,{type:`button`,disabled:t,className:p(`flex cursor-pointer items-center gap-x-1 rounded-sm border border-border bg-background p-2.5 text-sm text-text-positive-weak outline-none transition-all hover:shadow-card focus:border-border-emphasis focus:bg-muted-muted active:border-border-emphasis active:bg-muted-muted active:text-text-positive disabled:pointer-events-none disabled:cursor-default disabled:opacity-60 [&_svg]:size-3.5`,r),onClick:n,...i,children:e}),S=({onCreate:r,onCreateMultiple:i,onDownload:d,onRefresh:p})=>{let{table:g,csvData:_,csvFileName:v,title:b,showAnalysisPanel:S,summary:C}=e(),{isOpen:w,toggle:T}=n(),{isOpen:E,toggle:D}=t(),O=f(()=>{if(d){d();return}if(!_||_.length===0)return;let e=g.getSelectedRowModel().flatRows;y(e.length>0?e.map(e=>_[e.index]).filter(Boolean):_,v||b)},[d,_,v,g,b]),k=!!(d||_&&_.length>0);return h(`div`,{className:`flex [&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none`,children:[m(x,{disabled:!r,"aria-label":`Tạo mới`,onClick:e=>{r?.(),e.stopPropagation(),e.preventDefault()},children:m(o,{})}),m(x,{disabled:!i,"aria-label":`Tạo nhiều`,onClick:e=>{i?.(),e.stopPropagation(),e.preventDefault()},children:m(s,{})}),m(x,{disabled:!p,"aria-label":`Làm mới`,onClick:e=>{p?.(),e.stopPropagation(),e.preventDefault()},children:m(u,{})}),m(x,{disabled:!k,"aria-label":`Tải xuống`,onClick:e=>{O(),e.stopPropagation(),e.preventDefault()},children:m(c,{})}),C&&C.length>0&&m(x,{onClick:e=>{D(),e.stopPropagation(),e.preventDefault()},"aria-pressed":E,"aria-label":E?`Ẩn tổng quan`:`Hiện tổng quan`,className:E?`bg-muted-muted text-text-positive`:void 0,children:m(l,{})}),S&&m(x,{onClick:e=>{T(),e.stopPropagation(),e.preventDefault()},"aria-pressed":w,className:w?`bg-muted-muted text-text-positive`:void 0,children:m(a,{})})]})},C=({children:t})=>{let{title:n,description:r,headerActions:i}=e();return h(`div`,{"data-slot":`table-tooltip`,className:`relative flex w-full flex-col gap-2 px-2 py-0 text-sm`,children:[h(`div`,{className:`flex w-full items-start justify-between gap-4`,children:[h(`div`,{className:`flex flex-col gap-0.5`,children:[m(`h3`,{className:`font-semibold text-base text-text-positive`,children:n}),!!r&&m(`p`,{className:`text-muted-foreground text-sm`,children:r})]}),!!i&&m(`div`,{className:`flex shrink-0 items-center gap-2`,children:i})]}),m(`div`,{className:`flex w-full flex-1 justify-between gap-x-2`,children:t})]})};C.displayName=`UITableTooltip`;export{C as UITableTooltip,S as UITableTooltipActions,b as UITableTooltipFilter};
4
+ //# sourceMappingURL=tooltip-BKcU3bMc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip-BKcU3bMc.mjs","names":[],"sources":["../packages/components/features/tables/utils/csv.ts","../packages/components/features/tables/components/table/tooltip.tsx"],"sourcesContent":["'use client';\n\nimport type { CsvCell } from '../types';\n\nfunction formatCell(value: CsvCell['value']): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n const str = String(value);\n if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n}\n\nexport function buildCsvContent(rows: CsvCell[][]): string {\n if (rows.length === 0) return '';\n const headers = rows[0].map(cell => formatCell(cell.label));\n const dataRows = rows.map(row => row.map(cell => formatCell(cell.value)).join(','));\n return [headers.join(','), ...dataRows].join('\\n');\n}\n\nexport function downloadCsv(rows: CsvCell[][], fileName: string): void {\n const content = buildCsvContent(rows);\n if (!content) return;\n const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `${fileName}.csv`;\n link.click();\n URL.revokeObjectURL(url);\n}\n","/**\n * @file tooltip.tsx\n * Toolbar components rendered above the table: a debounced search input,\n * an action button group (create / refresh / download), and the outer\n * toolbar shell that displays the table title.\n */\nimport { useCallback } from 'react';\n\nimport { ArrowRightIcon, BarChart2Icon, CirclePlus, CopyPlusIcon, DownloadIcon, LayoutDashboardIcon, RefreshCwIcon, SearchIcon } from 'lucide-react';\n\nimport { useDebounceCallback } from '@customafk/react-toolkit/hooks/useDebounceCallback';\nimport { cn } from '@customafk/react-toolkit/utils';\n\nimport { Input } from '@/components/ui/input';\n\nimport { useUITableAnalysisContext, useUITableContext, useUITableSummaryContext } from '../../hooks/use-context';\nimport { downloadCsv } from '../../utils/csv';\n\n/**\n * Debounced search input rendered inside the table toolbar.\n *\n * Fires `onSearch` 500 ms after the user stops typing and delegates all other\n * native input events via the spread `props`.\n *\n * @example\n * ```tsx\n * import { UITableTooltipFilter } from '@customafk/lunas-ui/features/tables';\n *\n * <UITableTooltipFilter\n * placeholder=\"Search users…\"\n * onSearch={value => setQuery(value)}\n * />\n * ```\n */\nexport const UITableTooltipFilter: React.FC<\n Omit<React.ComponentProps<typeof Input>, 'className'> & {\n onSearch?: (value: string) => void;\n }\n> = ({ onSearch, onChange, ...props }) => {\n const debouncedSearch = useDebounceCallback((value: string) => {\n onSearch?.(value);\n }, 500);\n return (\n <div className=\"relative w-full max-w-80 flex-1\">\n <Input\n {...props}\n size=\"lg\"\n type=\"search\"\n placeholder=\"Tìm kiếm...\"\n className=\"flex-1 ps-9 pe-9\"\n onChange={e => {\n onChange?.(e);\n debouncedSearch(e.target.value ?? '');\n }}\n />\n <div className=\"pointer-events-none absolute inset-y-0 inset-s-0 flex items-center justify-center ps-3 text-text-positive-weak peer-disabled:opacity-50\">\n <SearchIcon size={16} />\n </div>\n <button\n className=\"absolute inset-y-0 inset-e-0 flex h-full w-9 items-center justify-center rounded-e-md text-text-positive-weak outline-none transition-[color,box-shadow] hover:text-text-positive focus:z-10 focus-visible:border focus-visible:border-primary-strong focus-visible:ring-[3px] focus-visible:ring-primary-weak disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\"\n aria-label=\"Submit search\"\n type=\"submit\"\n >\n <ArrowRightIcon size={16} aria-hidden=\"true\" />\n </button>\n </div>\n );\n};\n\nconst ActionButton: React.FC<React.PropsWithChildren<React.ComponentProps<'button'>>> = ({ children, disabled, onClick, className, ...props }) => {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'flex cursor-pointer items-center gap-x-1 rounded-sm border border-border bg-background p-2.5 text-sm text-text-positive-weak outline-none transition-all hover:shadow-card focus:border-border-emphasis focus:bg-muted-muted active:border-border-emphasis active:bg-muted-muted active:text-text-positive disabled:pointer-events-none disabled:cursor-default disabled:opacity-60 [&_svg]:size-3.5',\n className\n )}\n onClick={onClick}\n {...props}\n >\n {children}\n </button>\n );\n};\n\n/**\n * Grouped action buttons (create, refresh, download) displayed in the table\n * toolbar.\n *\n * Each button is disabled automatically when the corresponding handler prop is\n * omitted, so only the actions relevant to a given table need to be provided.\n * A toggle for `UITableSummaryBar` appears automatically when `summary` is\n * non-empty, and a toggle for `UITableAnalysisPanel` appears when\n * `showAnalysisPanel` is `true` (both set on `UITableProvider`).\n * `onCreateMultiple` renders a separate \"bulk create\" button next to the\n * single-item create button; omit it to hide the button entirely.\n *\n * @example\n * ```tsx\n * import { UITableTooltipActions } from '@customafk/lunas-ui/features/tables';\n *\n * <UITableTooltipActions\n * onCreate={() => setOpenCreate(true)}\n * onCreateMultiple={() => setOpenBulkCreate(true)}\n * onRefresh={() => refetch()}\n * />\n * ```\n */\nexport const UITableTooltipActions: React.FC<{\n onCreate?: () => void;\n onCreateMultiple?: () => void;\n onRefresh?: () => void;\n onDownload?: () => void;\n}> = ({ onCreate, onCreateMultiple, onDownload, onRefresh }) => {\n const { table, csvData, csvFileName, title, showAnalysisPanel, summary } = useUITableContext();\n const { isOpen: isAnalysisOpen, toggle: toggleAnalysis } = useUITableAnalysisContext();\n const { isOpen: isSummaryOpen, toggle: toggleSummary } = useUITableSummaryContext();\n\n const handleDownload = useCallback(() => {\n if (onDownload) {\n onDownload();\n return;\n }\n if (!csvData || csvData.length === 0) return;\n const selectedRows = table.getSelectedRowModel().flatRows;\n const rows = selectedRows.length > 0 ? selectedRows.map(row => csvData[row.index]).filter(Boolean) : csvData;\n downloadCsv(rows, csvFileName || title);\n }, [onDownload, csvData, csvFileName, table, title]);\n\n const isDownloadEnabled = !!(onDownload || (csvData && csvData.length > 0));\n\n return (\n <div className=\"flex [&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none\">\n <ActionButton\n disabled={!onCreate}\n aria-label=\"Tạo mới\"\n onClick={e => {\n onCreate?.();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <CirclePlus />\n </ActionButton>\n <ActionButton\n disabled={!onCreateMultiple}\n aria-label=\"Tạo nhiều\"\n onClick={e => {\n onCreateMultiple?.();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <CopyPlusIcon />\n </ActionButton>\n <ActionButton\n disabled={!onRefresh}\n aria-label=\"Làm mới\"\n onClick={e => {\n onRefresh?.();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <RefreshCwIcon />\n </ActionButton>\n <ActionButton\n disabled={!isDownloadEnabled}\n aria-label=\"Tải xuống\"\n onClick={e => {\n handleDownload();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <DownloadIcon />\n </ActionButton>\n {summary && summary.length > 0 && (\n <ActionButton\n onClick={e => {\n toggleSummary();\n e.stopPropagation();\n e.preventDefault();\n }}\n aria-pressed={isSummaryOpen}\n aria-label={isSummaryOpen ? 'Ẩn tổng quan' : 'Hiện tổng quan'}\n className={isSummaryOpen ? 'bg-muted-muted text-text-positive' : undefined}\n >\n <LayoutDashboardIcon />\n </ActionButton>\n )}\n {showAnalysisPanel && (\n <ActionButton\n onClick={e => {\n toggleAnalysis();\n e.stopPropagation();\n e.preventDefault();\n }}\n aria-pressed={isAnalysisOpen}\n className={isAnalysisOpen ? 'bg-muted-muted text-text-positive' : undefined}\n >\n <BarChart2Icon />\n </ActionButton>\n )}\n </div>\n );\n};\n\n/**\n * Outer toolbar shell for the UITable component.\n *\n * Reads the table `title` from `UITableContext` and renders it as a heading\n * above the `children` slot, which typically contains a\n * `UITableTooltipFilter` and/or `UITableTooltipActions`.\n *\n * @example\n * ```tsx\n * import {\n * UITableTooltip,\n * UITableTooltipFilter,\n * UITableTooltipActions,\n * } from '@customafk/lunas-ui/features/tables';\n *\n * <UITableTooltip>\n * <UITableTooltipFilter onSearch={setQuery} />\n * <UITableTooltipActions onCreate={handleCreate} onRefresh={refetch} />\n * </UITableTooltip>\n * ```\n */\nexport const UITableTooltip: React.FC<React.PropsWithChildren> = ({ children }) => {\n const { title, description, headerActions } = useUITableContext();\n return (\n <div data-slot=\"table-tooltip\" className=\"relative flex w-full flex-col gap-2 px-2 py-0 text-sm\">\n <div className=\"flex w-full items-start justify-between gap-4\">\n <div className=\"flex flex-col gap-0.5\">\n <h3 className=\"font-semibold text-base text-text-positive\">{title}</h3>\n {!!description && <p className=\"text-muted-foreground text-sm\">{description}</p>}\n </div>\n {!!headerActions && <div className=\"flex shrink-0 items-center gap-2\">{headerActions}</div>}\n </div>\n <div className=\"flex w-full flex-1 justify-between gap-x-2\">{children}</div>\n </div>\n );\n};\nUITableTooltip.displayName = 'UITableTooltip';\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ const e=require("./tables-BzP8Cnvh.cjs"),t=require("./ui/input.cjs");let n=require("lucide-react"),r=require("react"),i=require("@customafk/react-toolkit/utils"),a=require("react/jsx-runtime"),o=require("@customafk/react-toolkit/hooks/useDebounceCallback");function s(e){if(e==null)return``;if(typeof e==`number`)return String(e);if(typeof e==`boolean`)return e?`true`:`false`;let t=String(e);return t.includes(`,`)||t.includes(`"`)||t.includes(`
2
+ `)||t.includes(`\r`)?`"${t.replace(/"/g,`""`)}"`:t}function c(e){if(e.length===0)return``;let t=e[0].map(e=>s(e.label)),n=e.map(e=>e.map(e=>s(e.value)).join(`,`));return[t.join(`,`),...n].join(`
3
+ `)}function l(e,t){let n=c(e);if(!n)return;let r=new Blob([n],{type:`text/csv;charset=utf-8;`}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=`${t}.csv`,a.click(),URL.revokeObjectURL(i)}const u=({onSearch:e,onChange:r,...i})=>{let s=(0,o.useDebounceCallback)(t=>{e?.(t)},500);return(0,a.jsxs)(`div`,{className:`relative w-full max-w-80 flex-1`,children:[(0,a.jsx)(t.Input,{...i,size:`lg`,type:`search`,placeholder:`Tìm kiếm...`,className:`flex-1 ps-9 pe-9`,onChange:e=>{r?.(e),s(e.target.value??``)}}),(0,a.jsx)(`div`,{className:`pointer-events-none absolute inset-y-0 inset-s-0 flex items-center justify-center ps-3 text-text-positive-weak peer-disabled:opacity-50`,children:(0,a.jsx)(n.SearchIcon,{size:16})}),(0,a.jsx)(`button`,{className:`absolute inset-y-0 inset-e-0 flex h-full w-9 items-center justify-center rounded-e-md text-text-positive-weak outline-none transition-[color,box-shadow] hover:text-text-positive focus:z-10 focus-visible:border focus-visible:border-primary-strong focus-visible:ring-[3px] focus-visible:ring-primary-weak disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50`,"aria-label":`Submit search`,type:`submit`,children:(0,a.jsx)(n.ArrowRightIcon,{size:16,"aria-hidden":`true`})})]})},d=({children:e,disabled:t,onClick:n,className:r,...o})=>(0,a.jsx)(`button`,{type:`button`,disabled:t,className:(0,i.cn)(`flex cursor-pointer items-center gap-x-1 rounded-sm border border-border bg-background p-2.5 text-sm text-text-positive-weak outline-none transition-all hover:shadow-card focus:border-border-emphasis focus:bg-muted-muted active:border-border-emphasis active:bg-muted-muted active:text-text-positive disabled:pointer-events-none disabled:cursor-default disabled:opacity-60 [&_svg]:size-3.5`,r),onClick:n,...o,children:e}),f=({onCreate:t,onCreateMultiple:i,onDownload:o,onRefresh:s})=>{let{table:c,csvData:u,csvFileName:f,title:p,showAnalysisPanel:m,summary:h}=e.Y(),{isOpen:g,toggle:_}=e.q(),{isOpen:v,toggle:y}=e.Z(),b=(0,r.useCallback)(()=>{if(o){o();return}if(!u||u.length===0)return;let e=c.getSelectedRowModel().flatRows;l(e.length>0?e.map(e=>u[e.index]).filter(Boolean):u,f||p)},[o,u,f,c,p]),x=!!(o||u&&u.length>0);return(0,a.jsxs)(`div`,{className:`flex [&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none`,children:[(0,a.jsx)(d,{disabled:!t,"aria-label":`Tạo mới`,onClick:e=>{t?.(),e.stopPropagation(),e.preventDefault()},children:(0,a.jsx)(n.CirclePlus,{})}),(0,a.jsx)(d,{disabled:!i,"aria-label":`Tạo nhiều`,onClick:e=>{i?.(),e.stopPropagation(),e.preventDefault()},children:(0,a.jsx)(n.CopyPlusIcon,{})}),(0,a.jsx)(d,{disabled:!s,"aria-label":`Làm mới`,onClick:e=>{s?.(),e.stopPropagation(),e.preventDefault()},children:(0,a.jsx)(n.RefreshCwIcon,{})}),(0,a.jsx)(d,{disabled:!x,"aria-label":`Tải xuống`,onClick:e=>{b(),e.stopPropagation(),e.preventDefault()},children:(0,a.jsx)(n.DownloadIcon,{})}),h&&h.length>0&&(0,a.jsx)(d,{onClick:e=>{y(),e.stopPropagation(),e.preventDefault()},"aria-pressed":v,"aria-label":v?`Ẩn tổng quan`:`Hiện tổng quan`,className:v?`bg-muted-muted text-text-positive`:void 0,children:(0,a.jsx)(n.LayoutDashboardIcon,{})}),m&&(0,a.jsx)(d,{onClick:e=>{_(),e.stopPropagation(),e.preventDefault()},"aria-pressed":g,className:g?`bg-muted-muted text-text-positive`:void 0,children:(0,a.jsx)(n.BarChart2Icon,{})})]})},p=({children:t})=>{let{title:n,description:r,headerActions:i}=e.Y();return(0,a.jsxs)(`div`,{"data-slot":`table-tooltip`,className:`relative flex w-full flex-col gap-2 px-2 py-0 text-sm`,children:[(0,a.jsxs)(`div`,{className:`flex w-full items-start justify-between gap-4`,children:[(0,a.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,a.jsx)(`h3`,{className:`font-semibold text-base text-text-positive`,children:n}),!!r&&(0,a.jsx)(`p`,{className:`text-muted-foreground text-sm`,children:r})]}),!!i&&(0,a.jsx)(`div`,{className:`flex shrink-0 items-center gap-2`,children:i})]}),(0,a.jsx)(`div`,{className:`flex w-full flex-1 justify-between gap-x-2`,children:t})]})};p.displayName=`UITableTooltip`,exports.UITableTooltip=p,exports.UITableTooltipActions=f,exports.UITableTooltipFilter=u;
4
+ //# sourceMappingURL=tooltip-CLjCwnTD.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip-CLjCwnTD.cjs","names":["Input","SearchIcon","ArrowRightIcon","useUITableContext","useUITableAnalysisContext","useUITableSummaryContext","CirclePlus","CopyPlusIcon","RefreshCwIcon","DownloadIcon","LayoutDashboardIcon","BarChart2Icon"],"sources":["../packages/components/features/tables/utils/csv.ts","../packages/components/features/tables/components/table/tooltip.tsx"],"sourcesContent":["'use client';\n\nimport type { CsvCell } from '../types';\n\nfunction formatCell(value: CsvCell['value']): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n const str = String(value);\n if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n}\n\nexport function buildCsvContent(rows: CsvCell[][]): string {\n if (rows.length === 0) return '';\n const headers = rows[0].map(cell => formatCell(cell.label));\n const dataRows = rows.map(row => row.map(cell => formatCell(cell.value)).join(','));\n return [headers.join(','), ...dataRows].join('\\n');\n}\n\nexport function downloadCsv(rows: CsvCell[][], fileName: string): void {\n const content = buildCsvContent(rows);\n if (!content) return;\n const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `${fileName}.csv`;\n link.click();\n URL.revokeObjectURL(url);\n}\n","/**\n * @file tooltip.tsx\n * Toolbar components rendered above the table: a debounced search input,\n * an action button group (create / refresh / download), and the outer\n * toolbar shell that displays the table title.\n */\nimport { useCallback } from 'react';\n\nimport { ArrowRightIcon, BarChart2Icon, CirclePlus, CopyPlusIcon, DownloadIcon, LayoutDashboardIcon, RefreshCwIcon, SearchIcon } from 'lucide-react';\n\nimport { useDebounceCallback } from '@customafk/react-toolkit/hooks/useDebounceCallback';\nimport { cn } from '@customafk/react-toolkit/utils';\n\nimport { Input } from '@/components/ui/input';\n\nimport { useUITableAnalysisContext, useUITableContext, useUITableSummaryContext } from '../../hooks/use-context';\nimport { downloadCsv } from '../../utils/csv';\n\n/**\n * Debounced search input rendered inside the table toolbar.\n *\n * Fires `onSearch` 500 ms after the user stops typing and delegates all other\n * native input events via the spread `props`.\n *\n * @example\n * ```tsx\n * import { UITableTooltipFilter } from '@customafk/lunas-ui/features/tables';\n *\n * <UITableTooltipFilter\n * placeholder=\"Search users…\"\n * onSearch={value => setQuery(value)}\n * />\n * ```\n */\nexport const UITableTooltipFilter: React.FC<\n Omit<React.ComponentProps<typeof Input>, 'className'> & {\n onSearch?: (value: string) => void;\n }\n> = ({ onSearch, onChange, ...props }) => {\n const debouncedSearch = useDebounceCallback((value: string) => {\n onSearch?.(value);\n }, 500);\n return (\n <div className=\"relative w-full max-w-80 flex-1\">\n <Input\n {...props}\n size=\"lg\"\n type=\"search\"\n placeholder=\"Tìm kiếm...\"\n className=\"flex-1 ps-9 pe-9\"\n onChange={e => {\n onChange?.(e);\n debouncedSearch(e.target.value ?? '');\n }}\n />\n <div className=\"pointer-events-none absolute inset-y-0 inset-s-0 flex items-center justify-center ps-3 text-text-positive-weak peer-disabled:opacity-50\">\n <SearchIcon size={16} />\n </div>\n <button\n className=\"absolute inset-y-0 inset-e-0 flex h-full w-9 items-center justify-center rounded-e-md text-text-positive-weak outline-none transition-[color,box-shadow] hover:text-text-positive focus:z-10 focus-visible:border focus-visible:border-primary-strong focus-visible:ring-[3px] focus-visible:ring-primary-weak disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\"\n aria-label=\"Submit search\"\n type=\"submit\"\n >\n <ArrowRightIcon size={16} aria-hidden=\"true\" />\n </button>\n </div>\n );\n};\n\nconst ActionButton: React.FC<React.PropsWithChildren<React.ComponentProps<'button'>>> = ({ children, disabled, onClick, className, ...props }) => {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'flex cursor-pointer items-center gap-x-1 rounded-sm border border-border bg-background p-2.5 text-sm text-text-positive-weak outline-none transition-all hover:shadow-card focus:border-border-emphasis focus:bg-muted-muted active:border-border-emphasis active:bg-muted-muted active:text-text-positive disabled:pointer-events-none disabled:cursor-default disabled:opacity-60 [&_svg]:size-3.5',\n className\n )}\n onClick={onClick}\n {...props}\n >\n {children}\n </button>\n );\n};\n\n/**\n * Grouped action buttons (create, refresh, download) displayed in the table\n * toolbar.\n *\n * Each button is disabled automatically when the corresponding handler prop is\n * omitted, so only the actions relevant to a given table need to be provided.\n * A toggle for `UITableSummaryBar` appears automatically when `summary` is\n * non-empty, and a toggle for `UITableAnalysisPanel` appears when\n * `showAnalysisPanel` is `true` (both set on `UITableProvider`).\n * `onCreateMultiple` renders a separate \"bulk create\" button next to the\n * single-item create button; omit it to hide the button entirely.\n *\n * @example\n * ```tsx\n * import { UITableTooltipActions } from '@customafk/lunas-ui/features/tables';\n *\n * <UITableTooltipActions\n * onCreate={() => setOpenCreate(true)}\n * onCreateMultiple={() => setOpenBulkCreate(true)}\n * onRefresh={() => refetch()}\n * />\n * ```\n */\nexport const UITableTooltipActions: React.FC<{\n onCreate?: () => void;\n onCreateMultiple?: () => void;\n onRefresh?: () => void;\n onDownload?: () => void;\n}> = ({ onCreate, onCreateMultiple, onDownload, onRefresh }) => {\n const { table, csvData, csvFileName, title, showAnalysisPanel, summary } = useUITableContext();\n const { isOpen: isAnalysisOpen, toggle: toggleAnalysis } = useUITableAnalysisContext();\n const { isOpen: isSummaryOpen, toggle: toggleSummary } = useUITableSummaryContext();\n\n const handleDownload = useCallback(() => {\n if (onDownload) {\n onDownload();\n return;\n }\n if (!csvData || csvData.length === 0) return;\n const selectedRows = table.getSelectedRowModel().flatRows;\n const rows = selectedRows.length > 0 ? selectedRows.map(row => csvData[row.index]).filter(Boolean) : csvData;\n downloadCsv(rows, csvFileName || title);\n }, [onDownload, csvData, csvFileName, table, title]);\n\n const isDownloadEnabled = !!(onDownload || (csvData && csvData.length > 0));\n\n return (\n <div className=\"flex [&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none\">\n <ActionButton\n disabled={!onCreate}\n aria-label=\"Tạo mới\"\n onClick={e => {\n onCreate?.();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <CirclePlus />\n </ActionButton>\n <ActionButton\n disabled={!onCreateMultiple}\n aria-label=\"Tạo nhiều\"\n onClick={e => {\n onCreateMultiple?.();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <CopyPlusIcon />\n </ActionButton>\n <ActionButton\n disabled={!onRefresh}\n aria-label=\"Làm mới\"\n onClick={e => {\n onRefresh?.();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <RefreshCwIcon />\n </ActionButton>\n <ActionButton\n disabled={!isDownloadEnabled}\n aria-label=\"Tải xuống\"\n onClick={e => {\n handleDownload();\n e.stopPropagation();\n e.preventDefault();\n }}\n >\n <DownloadIcon />\n </ActionButton>\n {summary && summary.length > 0 && (\n <ActionButton\n onClick={e => {\n toggleSummary();\n e.stopPropagation();\n e.preventDefault();\n }}\n aria-pressed={isSummaryOpen}\n aria-label={isSummaryOpen ? 'Ẩn tổng quan' : 'Hiện tổng quan'}\n className={isSummaryOpen ? 'bg-muted-muted text-text-positive' : undefined}\n >\n <LayoutDashboardIcon />\n </ActionButton>\n )}\n {showAnalysisPanel && (\n <ActionButton\n onClick={e => {\n toggleAnalysis();\n e.stopPropagation();\n e.preventDefault();\n }}\n aria-pressed={isAnalysisOpen}\n className={isAnalysisOpen ? 'bg-muted-muted text-text-positive' : undefined}\n >\n <BarChart2Icon />\n </ActionButton>\n )}\n </div>\n );\n};\n\n/**\n * Outer toolbar shell for the UITable component.\n *\n * Reads the table `title` from `UITableContext` and renders it as a heading\n * above the `children` slot, which typically contains a\n * `UITableTooltipFilter` and/or `UITableTooltipActions`.\n *\n * @example\n * ```tsx\n * import {\n * UITableTooltip,\n * UITableTooltipFilter,\n * UITableTooltipActions,\n * } from '@customafk/lunas-ui/features/tables';\n *\n * <UITableTooltip>\n * <UITableTooltipFilter onSearch={setQuery} />\n * <UITableTooltipActions onCreate={handleCreate} onRefresh={refetch} />\n * </UITableTooltip>\n * ```\n */\nexport const UITableTooltip: React.FC<React.PropsWithChildren> = ({ children }) => {\n const { title, description, headerActions } = useUITableContext();\n return (\n <div data-slot=\"table-tooltip\" className=\"relative flex w-full flex-col gap-2 px-2 py-0 text-sm\">\n <div className=\"flex w-full items-start justify-between gap-4\">\n <div className=\"flex flex-col gap-0.5\">\n <h3 className=\"font-semibold text-base text-text-positive\">{title}</h3>\n {!!description && <p className=\"text-muted-foreground text-sm\">{description}</p>}\n </div>\n {!!headerActions && <div className=\"flex shrink-0 items-center gap-2\">{headerActions}</div>}\n </div>\n <div className=\"flex w-full flex-1 justify-between gap-x-2\">{children}</div>\n </div>\n );\n};\nUITableTooltip.displayName = 'UITableTooltip';\n"],"mappings":"iQAIA,SAAS,EAAW,EAAiC,CACnD,GAAI,GAAU,KAA6B,MAAO,GAClD,GAAI,OAAO,GAAU,SAAU,OAAO,OAAO,CAAK,EAClD,GAAI,OAAO,GAAU,UAAW,OAAO,EAAQ,OAAS,QACxD,IAAM,EAAM,OAAO,CAAK,EAIxB,OAHI,EAAI,SAAS,GAAG,GAAK,EAAI,SAAS,GAAG,GAAK,EAAI,SAAS;CAAI,GAAK,EAAI,SAAS,IAAI,EAC5E,IAAI,EAAI,QAAQ,KAAM,IAAI,EAAE,GAE9B,CACT,CAEA,SAAgB,EAAgB,EAA2B,CACzD,GAAI,EAAK,SAAW,EAAG,MAAO,GAC9B,IAAM,EAAU,EAAK,EAAE,CAAC,IAAI,GAAQ,EAAW,EAAK,KAAK,CAAC,EACpD,EAAW,EAAK,IAAI,GAAO,EAAI,IAAI,GAAQ,EAAW,EAAK,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAClF,MAAO,CAAC,EAAQ,KAAK,GAAG,EAAG,GAAG,CAAQ,CAAC,CAAC,KAAK;CAAI,CACnD,CAEA,SAAgB,EAAY,EAAmB,EAAwB,CACrE,IAAM,EAAU,EAAgB,CAAI,EACpC,GAAI,CAAC,EAAS,OACd,IAAM,EAAO,IAAI,KAAK,CAAC,CAAO,EAAG,CAAE,KAAM,yBAA0B,CAAC,EAC9D,EAAM,IAAI,gBAAgB,CAAI,EAC9B,EAAO,SAAS,cAAc,GAAG,EACvC,EAAK,KAAO,EACZ,EAAK,SAAW,GAAG,EAAS,MAC5B,EAAK,MAAM,EACX,IAAI,gBAAgB,CAAG,CACzB,CCEA,MAAa,GAIR,CAAE,WAAU,WAAU,GAAG,KAAY,CACxC,IAAM,GAAA,EAAA,EAAA,oBAAA,CAAuC,GAAkB,CAC7D,IAAW,CAAK,CAClB,EAAG,GAAG,EACN,OACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,kCAAf,SAAA,EACE,EAAA,EAAA,IAAA,CAACA,EAAAA,MAAD,CACE,GAAI,EACJ,KAAK,KACL,KAAK,SACL,YAAY,cACZ,UAAU,mBACV,SAAU,GAAK,CACb,IAAW,CAAC,EACZ,EAAgB,EAAE,OAAO,OAAS,EAAE,CACtC,CACD,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,0IACb,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,WAAD,CAAY,KAAM,EAAK,CAAA,CACpB,CAAA,GACL,EAAA,EAAA,IAAA,CAAC,SAAD,CACE,UAAU,8XACV,aAAW,gBACX,KAAK,SAEL,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,eAAD,CAAgB,KAAM,GAAI,cAAY,MAAQ,CAAA,CACxC,CAAA,CACL,GAET,EAEM,GAAmF,CAAE,WAAU,WAAU,UAAS,YAAW,GAAG,MAElI,EAAA,EAAA,IAAA,CAAC,SAAD,CACE,KAAK,SACK,WACV,WAAA,EAAA,EAAA,GAAA,CACE,uYACA,CACF,EACS,UACT,GAAI,EAEH,UACK,CAAA,EA2BC,GAKP,CAAE,WAAU,mBAAkB,aAAY,eAAgB,CAC9D,GAAM,CAAE,QAAO,UAAS,cAAa,QAAO,oBAAmB,WAAYC,EAAAA,EAAkB,EACvF,CAAE,OAAQ,EAAgB,OAAQ,GAAmBC,EAAAA,EAA0B,EAC/E,CAAE,OAAQ,EAAe,OAAQ,GAAkBC,EAAAA,EAAyB,EAE5E,GAAA,EAAA,EAAA,YAAA,KAAmC,CACvC,GAAI,EAAY,CACd,EAAW,EACX,MACF,CACA,GAAI,CAAC,GAAW,EAAQ,SAAW,EAAG,OACtC,IAAM,EAAe,EAAM,oBAAoB,CAAC,CAAC,SAEjD,EADa,EAAa,OAAS,EAAI,EAAa,IAAI,GAAO,EAAQ,EAAI,MAAM,CAAC,CAAC,OAAO,OAAO,EAAI,EACnF,GAAe,CAAK,CACxC,EAAG,CAAC,EAAY,EAAS,EAAa,EAAO,CAAK,CAAC,EAE7C,EAAoB,CAAC,EAAE,GAAe,GAAW,EAAQ,OAAS,GAExE,OACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,uHAAf,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,SAAU,CAAC,EACX,aAAW,UACX,QAAS,GAAK,CACZ,IAAW,EACX,EAAE,gBAAgB,EAClB,EAAE,eAAe,CACnB,EAEA,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,WAAD,CAAa,CAAA,CACD,CAAA,GACd,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,SAAU,CAAC,EACX,aAAW,YACX,QAAS,GAAK,CACZ,IAAmB,EACnB,EAAE,gBAAgB,EAClB,EAAE,eAAe,CACnB,EAEA,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,aAAD,CAAe,CAAA,CACH,CAAA,GACd,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,SAAU,CAAC,EACX,aAAW,UACX,QAAS,GAAK,CACZ,IAAY,EACZ,EAAE,gBAAgB,EAClB,EAAE,eAAe,CACnB,EAEA,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,cAAD,CAAgB,CAAA,CACJ,CAAA,GACd,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,SAAU,CAAC,EACX,aAAW,YACX,QAAS,GAAK,CACZ,EAAe,EACf,EAAE,gBAAgB,EAClB,EAAE,eAAe,CACnB,EAEA,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,aAAD,CAAe,CAAA,CACH,CAAA,EACb,GAAW,EAAQ,OAAS,IAC3B,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,QAAS,GAAK,CACZ,EAAc,EACd,EAAE,gBAAgB,EAClB,EAAE,eAAe,CACnB,EACA,eAAc,EACd,aAAY,EAAgB,eAAiB,iBAC7C,UAAW,EAAgB,oCAAsC,IAAA,GAEjE,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,oBAAD,CAAsB,CAAA,CACV,CAAA,EAEf,IACC,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,QAAS,GAAK,CACZ,EAAe,EACf,EAAE,gBAAgB,EAClB,EAAE,eAAe,CACnB,EACA,eAAc,EACd,UAAW,EAAiB,oCAAsC,IAAA,GAElE,UAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,cAAD,CAAgB,CAAA,CACJ,CAAA,CAEb,GAET,EAuBa,GAAqD,CAAE,cAAe,CACjF,GAAM,CAAE,QAAO,cAAa,iBAAkBR,EAAAA,EAAkB,EAChE,OACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,YAAU,gBAAgB,UAAU,wDAAzC,SAAA,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,gDAAf,SAAA,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,wBAAf,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAU,6CAA8C,SAAA,CAAU,CAAA,EACrE,CAAC,CAAC,IAAe,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,gCAAiC,SAAA,CAAe,CAAA,CAC5E,CACJ,CAAA,EAAA,CAAC,CAAC,IAAiB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,mCAAoC,SAAA,CAAmB,CAAA,CACvF,CACL,CAAA,GAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,6CAA8C,UAAc,CAAA,CACxE,GAET,EACA,EAAe,YAAc"}
@@ -1,2 +1,2 @@
1
- import { n as ParagraphProps, r as paragraphVariants, t as Paragraph } from "../paragraph-CkCNIsvz.cjs";
1
+ import { n as ParagraphProps, r as paragraphVariants, t as Paragraph } from "../paragraph-DJlHIDmk.cjs";
2
2
  export { Paragraph, ParagraphProps, paragraphVariants };
@@ -1,2 +1,2 @@
1
- import { n as ParagraphProps, r as paragraphVariants, t as Paragraph } from "../paragraph-CRo5eC7H.mjs";
1
+ import { n as ParagraphProps, r as paragraphVariants, t as Paragraph } from "../paragraph-C5HE9u0Q.mjs";
2
2
  export { Paragraph, ParagraphProps, paragraphVariants };
@@ -1,2 +1,2 @@
1
- import { n as BadgeProps, r as badgeVariants, t as Badge } from "../badge-BPiU1Ss2.cjs";
1
+ import { n as BadgeProps, r as badgeVariants, t as Badge } from "../badge-C_KLKwiS.cjs";
2
2
  export { Badge, BadgeProps, badgeVariants };
@@ -1,2 +1,2 @@
1
- import { n as BadgeProps, r as badgeVariants, t as Badge } from "../badge-BPiU1Ss2.mjs";
1
+ import { n as BadgeProps, r as badgeVariants, t as Badge } from "../badge-C_KLKwiS.mjs";
2
2
  export { Badge, BadgeProps, badgeVariants };
@@ -1,2 +1,2 @@
1
- import { n as ButtonProps, t as Button } from "../button-DQLIL8K2.cjs";
1
+ import { n as ButtonProps, t as Button } from "../button-CnKmz4qM.cjs";
2
2
  export { Button, ButtonProps };
@@ -1,2 +1,2 @@
1
- import { n as ButtonProps, t as Button } from "../button-DQLIL8K2.mjs";
1
+ import { n as ButtonProps, t as Button } from "../button-CnKmz4qM.mjs";
2
2
  export { Button, ButtonProps };
@@ -1,2 +1,2 @@
1
- import { n as CalendarDayButton, t as Calendar } from "../calendar-DHcoDDIc.cjs";
1
+ import { n as CalendarDayButton, t as Calendar } from "../calendar-D_QmR9VE.cjs";
2
2
  export { Calendar, CalendarDayButton };
@@ -1,2 +1,2 @@
1
- import { n as CalendarDayButton, t as Calendar } from "../calendar-5VVBPn4F.mjs";
1
+ import { n as CalendarDayButton, t as Calendar } from "../calendar-BT6-Fo3T.mjs";
2
2
  export { Calendar, CalendarDayButton };
@@ -1,2 +1,2 @@
1
- import { a as CarouselNext, i as CarouselItem, n as CarouselApi, o as CarouselPrevious, r as CarouselContent, t as Carousel } from "../carousel-D0jTbSoW.cjs";
1
+ import { a as CarouselNext, i as CarouselItem, n as CarouselApi, o as CarouselPrevious, r as CarouselContent, t as Carousel } from "../carousel-B_IXlBdH.cjs";
2
2
  export { Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious };
@@ -1,2 +1,2 @@
1
- import { a as CarouselNext, i as CarouselItem, n as CarouselApi, o as CarouselPrevious, r as CarouselContent, t as Carousel } from "../carousel-qH6YsFKK.mjs";
1
+ import { a as CarouselNext, i as CarouselItem, n as CarouselApi, o as CarouselPrevious, r as CarouselContent, t as Carousel } from "../carousel-BBI4xPdK.mjs";
2
2
  export { Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious };
@@ -1,3 +1,3 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rolldown-runtime-CMqjfN_6.cjs"),t=require("./button.cjs"),n=require("./progress.cjs");let r=require("lucide-react"),i=require("react"),a=require("@customafk/react-toolkit/utils"),o=require("react/jsx-runtime"),s=require("react-dropzone");s=e.t(s,1);let c=require("@customafk/react-toolkit/hooks/useControllableState"),l=require("sonner");function u(e){let{value:t,onValueChange:n,onUpload:u,progresses:p,accept:m={"image/*":[]},maxSize:h=1024*1024*2,maxFileCount:g=1,multiple:_=!1,disabled:v=!1,showText:y=!0,iconSize:b,className:x,...S}=e,[C,w]=(0,c.useControllableState)({prop:t,onChange:n}),T=(0,i.useCallback)((e,t)=>{if(!_&&g===1&&e.length>1){l.toast.error(`Cannot upload more than 1 file at a time`);return}if((C?.length??0)+e.length>g){l.toast.error(`Cannot upload more than ${g} files`);return}let n=e.map(e=>Object.assign(e,{preview:URL.createObjectURL(e)})),r=C?[...C,...n]:n;if(w(r),t.length>0&&t.forEach(({file:e})=>{l.toast.error(`File ${e.name} was rejected`)}),u&&r.length>0&&r.length<=g){let e=r.length>0?`${r.length} files`:`file`;u(r).then(()=>{w([]),(0,l.toast)(`${e} uploaded`)})}},[C,g,_,u,w]);function E(e){if(!C)return;f(C[e])&&URL.revokeObjectURL(C[e].preview);let t=C.filter((t,n)=>n!==e);w(t),n?.(t)}(0,i.useEffect)(()=>()=>{C&&C.forEach(e=>{f(e)&&URL.revokeObjectURL(e.preview)})},[]);let D=v||(C?.length??0)>=g;return(0,o.jsxs)(`div`,{className:`relative flex w-full flex-col gap-6`,children:[(0,o.jsx)(s.default,{onDrop:T,accept:m,maxSize:h,maxFiles:g,multiple:g>1||_,disabled:D,children:({getRootProps:e,getInputProps:t,isDragActive:n})=>(0,o.jsxs)(`div`,{...e(),className:(0,a.cn)(`group`,`border-border-weak`,`hover:bg-border-weak/20`,`relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition`,`ring-offset-background`,`focus-visible:ring-primary-weak`,`focus-visible:ring-2`,`focus-visible:ring-offset-2`,`focus-visible:outline-hidden`,n&&`border-border-primary-strong bg-border-weak/40`,D&&`pointer-events-none opacity-60`,x),...S,children:[(0,o.jsx)(`input`,{...t()}),n?(0,o.jsxs)(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[(0,o.jsx)(`div`,{className:`rounded-full border border-dashed p-3`,children:(0,o.jsx)(r.Upload,{className:`text-text-positive-muted size-7`,"aria-hidden":`true`})}),(0,o.jsx)(`p`,{className:`text-text-positive-muted font-medium`,children:`Drop the files here`})]}):(0,o.jsxs)(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[(0,o.jsx)(`div`,{className:`rounded-full border border-dashed p-3`,children:(0,o.jsx)(r.Upload,{size:b||24,className:`text-muted-foreground`,"aria-hidden":`true`})}),y&&(0,o.jsxs)(`div`,{className:`flex flex-col gap-px`,children:[(0,o.jsxs)(`p`,{className:`text-text-positive-weak text-xs font-medium`,children:[`Drag `,`'n'`,` drop files here, or click to select files`]}),(0,o.jsxs)(`p`,{className:`text-text-positive-muted text-xs`,children:[`You can upload`,g>1?` ${g===1/0?`multiple`:g}
2
- files (up to ${(0,a.formatBytes)(h)} each)`:` a file with ${(0,a.formatBytes)(h)}`]})]})]})]})}),C?.length?(0,o.jsxs)(`div`,{className:`h-fit w-full overflow-y-auto`,children:[(0,o.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2.5`,children:[(0,o.jsxs)(`div`,{className:`text-text-positive-weak flex items-center gap-1`,children:[(0,o.jsx)(r.Image,{size:16,"aria-hidden":`true`}),(0,o.jsxs)(`p`,{className:`text-sm`,children:[`Ảnh đã tải lên (`,C.length,`)`]})]}),(0,o.jsxs)(`button`,{className:`hover:text-text-positive text-text-positive-muted flex cursor-pointer items-center gap-1 transition-colors duration-200`,onClick:()=>{C&&(C.forEach(e=>{f(e)&&URL.revokeObjectURL(e.preview)}),w([]),n?.([]))},children:[(0,o.jsx)(r.Trash2,{size:16,"aria-hidden":`true`}),(0,o.jsx)(`p`,{className:`text-sm`,children:`Xóa tất cả`})]})]}),(0,o.jsx)(`div`,{className:`flex flex-col gap-2.5`,children:C?.map((e,t)=>(0,o.jsx)(d,{file:e,onRemove:()=>E(t),progress:p?.[e.name]},t))})]}):null]})}function d({file:e,progress:i,onRemove:s}){return(0,o.jsxs)(`div`,{className:`border-border-weak hover:border-border relative flex items-center gap-4 rounded-md border py-2 pr-3 pl-2 shadow-xs transition-colors`,children:[(0,o.jsxs)(`div`,{className:`flex flex-1 gap-2.5`,children:[f(e)?(0,o.jsx)(p,{file:e}):null,(0,o.jsxs)(`div`,{className:`flex w-full flex-col gap-2`,children:[(0,o.jsxs)(`div`,{className:`flex flex-col gap-y-0.5`,children:[(0,o.jsx)(`p`,{className:`text-text-positive line-clamp-1 text-sm`,children:e.name}),(0,o.jsx)(`p`,{className:`text-text-positive-muted text-xs`,children:(0,a.formatBytes)(e.size)})]}),i?(0,o.jsx)(n.Progress,{value:i}):null]})]}),(0,o.jsx)(`div`,{className:`flex items-center gap-2`,children:(0,o.jsxs)(t.Button,{type:`button`,variant:`ghost`,color:`secondary`,size:`icon`,className:`size-7`,onClick:s,children:[(0,o.jsx)(r.Trash2,{size:16,className:`text-text-positive-muted`,"aria-hidden":`true`}),(0,o.jsx)(`span`,{className:`sr-only`,children:`Remove file`})]})})]})}function f(e){return`preview`in e&&typeof e.preview==`string`}function p({file:e}){return e.type.startsWith(`image/`)?(0,o.jsx)(`img`,{src:e.preview,className:`border-border aspect-square size-10 shrink-0 rounded border object-cover object-top shadow-sm`}):(0,o.jsx)(r.FileText,{className:`text-text-positive-muted size-10 border`,"aria-hidden":`true`})}exports.FileUploader=u;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rolldown-runtime-CMqjfN_6.cjs"),t=require("./button.cjs"),n=require("./progress.cjs"),r=require("../features/file-icons/index.cjs");let i=require("lucide-react"),a=require("react"),o=require("@customafk/react-toolkit/utils"),s=require("react/jsx-runtime"),c=require("sonner"),l=require("@customafk/react-toolkit/hooks/useControllableState"),u=require("react-dropzone");u=e.t(u,1);function d(e){let{value:t,onValueChange:n,onUpload:r,progresses:d,accept:m={"image/*":[]},maxSize:h=1024*1024*2,maxFileCount:g=1,multiple:_=!1,disabled:v=!1,showText:y=!0,iconSize:b,className:x,...S}=e,[C,w]=(0,l.useControllableState)({prop:t,onChange:n}),T=(0,a.useCallback)((e,t)=>{if(!_&&g===1&&e.length>1){c.toast.error(`Cannot upload more than 1 file at a time`);return}if((C?.length??0)+e.length>g){c.toast.error(`Cannot upload more than ${g} files`);return}let n=e.map(e=>Object.assign(e,{preview:URL.createObjectURL(e)})),i=C?[...C,...n]:n;if(w(i),t.length>0&&t.forEach(({file:e})=>{c.toast.error(`File ${e.name} was rejected`)}),r&&i.length>0&&i.length<=g){let e=i.length>0?`${i.length} files`:`file`;r(i).then(()=>{w([]),(0,c.toast)(`${e} uploaded`)})}},[C,g,_,r,w]);function E(e){if(!C)return;p(C[e])&&URL.revokeObjectURL(C[e].preview);let t=C.filter((t,n)=>n!==e);w(t),n?.(t)}(0,a.useEffect)(()=>()=>{C&&C.forEach(e=>{p(e)&&URL.revokeObjectURL(e.preview)})},[]);let D=v||(C?.length??0)>=g;return(0,s.jsxs)(`div`,{className:`relative flex w-full flex-col gap-6`,children:[(0,s.jsx)(u.default,{onDrop:T,accept:m,maxSize:h,maxFiles:g,multiple:g>1||_,disabled:D,children:({getRootProps:e,getInputProps:t,isDragActive:n})=>(0,s.jsxs)(`div`,{...e(),className:(0,o.cn)(`group`,`border-border-weak`,`hover:bg-border-weak/20`,`relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition`,`ring-offset-background`,`focus-visible:ring-primary-weak`,`focus-visible:ring-2`,`focus-visible:ring-offset-2`,`focus-visible:outline-hidden`,n&&`border-border-primary-strong bg-border-weak/40`,D&&`pointer-events-none opacity-60`,x),...S,children:[(0,s.jsx)(`input`,{...t()}),n?(0,s.jsxs)(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[(0,s.jsx)(`div`,{className:`rounded-full border border-dashed p-3`,children:(0,s.jsx)(i.Upload,{className:`size-7 text-text-positive-muted`,"aria-hidden":`true`})}),(0,s.jsx)(`p`,{className:`font-medium text-text-positive-muted`,children:`Drop the files here`})]}):(0,s.jsxs)(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[(0,s.jsx)(`div`,{className:`rounded-full border border-dashed p-3`,children:(0,s.jsx)(i.Upload,{size:b||24,className:`text-muted-foreground`,"aria-hidden":`true`})}),!!y&&(0,s.jsxs)(`div`,{className:`flex flex-col gap-px`,children:[(0,s.jsxs)(`p`,{className:`font-medium text-text-positive-weak text-xs`,children:[`Drag `,`'n'`,` drop files here, or click to select files`]}),(0,s.jsxs)(`p`,{className:`text-text-positive-muted text-xs`,children:[`You can upload`,g>1?` ${g===1/0?`multiple`:g}
2
+ files (up to ${(0,o.formatBytes)(h)} each)`:` a file with ${(0,o.formatBytes)(h)}`]})]})]})]})}),C?.length?(0,s.jsxs)(`div`,{className:`h-fit w-full overflow-y-auto`,children:[(0,s.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2.5`,children:[(0,s.jsxs)(`div`,{className:`flex items-center gap-1 text-text-positive-weak`,children:[(0,s.jsx)(i.Image,{size:16,"aria-hidden":`true`}),(0,s.jsxs)(`p`,{className:`text-sm`,children:[`Ảnh đã tải lên (`,C.length,`)`]})]}),(0,s.jsxs)(`button`,{className:`flex cursor-pointer items-center gap-1 text-text-positive-muted transition-colors duration-200 hover:text-text-positive`,onClick:()=>{C&&(C.forEach(e=>{p(e)&&URL.revokeObjectURL(e.preview)}),w([]),n?.([]))},children:[(0,s.jsx)(i.Trash2,{size:16,"aria-hidden":`true`}),(0,s.jsx)(`p`,{className:`text-sm`,children:`Xóa tất cả`})]})]}),(0,s.jsx)(`div`,{className:`flex flex-col gap-2.5`,children:C?.map((e,t)=>(0,s.jsx)(f,{file:e,onRemove:()=>E(t),progress:d?.[e.name]},`${t.toString()}`))})]}):null]})}function f({file:e,progress:r,onRemove:a}){return(0,s.jsxs)(`div`,{className:`relative flex items-center gap-4 rounded border border-border-weak py-2 pr-3 pl-2 shadow-xs transition-colors hover:border-border`,children:[(0,s.jsxs)(`div`,{className:`flex flex-1 gap-2.5`,children:[p(e)?(0,s.jsx)(h,{file:e}):null,(0,s.jsxs)(`div`,{className:`flex w-full flex-col gap-2`,children:[(0,s.jsxs)(`div`,{className:`flex flex-col gap-y-0.5`,children:[(0,s.jsx)(`p`,{className:`line-clamp-1 text-sm text-text-positive`,children:e.name}),(0,s.jsx)(`p`,{className:`text-text-positive-muted text-xs`,children:(0,o.formatBytes)(e.size)})]}),r?(0,s.jsx)(n.Progress,{value:r}):null]})]}),(0,s.jsx)(`div`,{className:`flex items-center gap-2`,children:(0,s.jsxs)(t.Button,{type:`button`,variant:`ghost`,color:`danger`,size:`icon`,className:`size-7`,onClick:a,children:[(0,s.jsx)(i.Trash2,{size:16,"aria-hidden":`true`}),(0,s.jsx)(`span`,{className:`sr-only`,children:`Remove file`})]})})]})}function p(e){return`preview`in e&&typeof e.preview==`string`}const m={"application/pdf":r.PdfFileIcon,"text/csv":r.CsvFileIcon,"application/msword":r.WordFileIcon,"application/vnd.openxmlformats-officedocument.wordprocessingml.document":r.WordFileIcon,"application/vnd.ms-excel":r.ExcelFileIcon,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":r.ExcelFileIcon,"application/zip":r.ZipFileIcon,"application/x-zip-compressed":r.ZipFileIcon};function h({file:e}){return e.type.startsWith(`image/`)?(0,s.jsx)(`img`,{src:e.preview,className:`aspect-square size-10 shrink-0 rounded border border-border object-cover object-top shadow-sm`}):e.type.startsWith(`audio/`)?(0,s.jsx)(r.AudioFileIcon,{size:40,className:`size-10 shrink-0`,"aria-hidden":`true`}):e.type.startsWith(`video/`)?(0,s.jsx)(r.VideoFileIcon,{size:40,className:`size-10 shrink-0`,"aria-hidden":`true`}):(0,s.jsx)(m[e.type]??r.UnknownFileIcon,{size:40,className:`size-10 shrink-0`,"aria-hidden":`true`})}exports.FileUploader=d;
3
3
  //# sourceMappingURL=file-uploader.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-uploader.cjs","names":["Dropzone","Upload","Image","Trash2","Progress","Button","FileText"],"sources":["../../packages/components/ui/file-uploader.tsx"],"sourcesContent":["'use client';\nimport { useCallback, useEffect } from 'react';\nimport Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone';\nimport { useControllableState } from '@customafk/react-toolkit/hooks/useControllableState';\nimport { cn, formatBytes } from '@customafk/react-toolkit/utils';\n\nimport { FileText, Image, Trash2, Upload } from 'lucide-react';\nimport { toast } from 'sonner';\n\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\n\n/**\n * Props for the FileUploader component.\n *\n * @example\n * ```tsx\n * import { FileUploader } from '@customafk/lunas-ui/ui/file-uploader';\n *\n * <FileUploader\n * accept={{ 'image/*': [] }}\n * maxSize={1024 * 1024 * 5}\n * maxFileCount={3}\n * multiple\n * onUpload={async (files) => { await uploadFiles(files); }}\n * />\n * ```\n */\ninterface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Value of the uploader.\n * @type File[]\n * @default undefined\n * @example value={files}\n */\n value?: File[];\n\n /**\n * Function to be called when the value changes.\n * @type (files: File[]) => void\n * @default undefined\n * @example onValueChange={(files) => setFiles(files)}\n */\n onValueChange?: (files: File[]) => void;\n\n /**\n * Function to be called when files are uploaded.\n * @type (files: File[]) => Promise<void>\n * @default undefined\n * @example onUpload={(files) => uploadFiles(files)}\n */\n onUpload?: (files: File[]) => Promise<void>;\n\n /**\n * Progress of the uploaded files.\n * @type Record<string, number> | undefined\n * @default undefined\n * @example progresses={{ \"file1.png\": 50 }}\n */\n progresses?: Record<string, number>;\n\n /**\n * Accepted file types for the uploader.\n * @type { [key: string]: string[]}\n * @default\n * ```ts\n * { \"image/*\": [] }\n * ```\n * @example accept={[\"image/png\", \"image/jpeg\"]}\n */\n accept?: DropzoneProps['accept'];\n\n /**\n * Maximum file size for the uploader.\n * @type number | undefined\n * @default 1024 * 1024 * 2 // 2MB\n * @example maxSize={1024 * 1024 * 2} // 2MB\n */\n maxSize?: DropzoneProps['maxSize'];\n\n /**\n * Maximum number of files for the uploader.\n * @type number | undefined\n * @default 1\n * @example maxFileCount={4}\n */\n maxFileCount?: DropzoneProps['maxFiles'];\n\n /**\n * Whether the uploader should accept multiple files.\n * @type boolean\n * @default false\n * @example multiple\n */\n multiple?: boolean;\n\n /**\n * Whether the uploader is disabled.\n * @type boolean\n * @default false\n * @example disabled\n */\n disabled?: boolean;\n\n /**\n * Whether to show the instructional drag-and-drop text inside the drop zone.\n * @default true\n */\n showText?: boolean;\n /** Size in pixels for the upload icon. */\n iconSize?: number;\n}\n\n/**\n * A drag-and-drop file upload zone that manages file selection, validation, preview generation, and optional async upload with progress tracking.\n */\nexport function FileUploader(props: FileUploaderProps) {\n const {\n value: valueProp,\n onValueChange,\n onUpload,\n progresses,\n accept = {\n 'image/*': [],\n },\n maxSize = 1024 * 1024 * 2,\n maxFileCount = 1,\n multiple = false,\n disabled = false,\n showText = true,\n iconSize,\n className,\n ...dropzoneProps\n } = props;\n\n const [files, setFiles] = useControllableState({\n prop: valueProp,\n onChange: onValueChange,\n });\n\n const onDrop = useCallback(\n (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {\n if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {\n toast.error('Cannot upload more than 1 file at a time');\n\n return;\n }\n\n if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {\n toast.error(`Cannot upload more than ${maxFileCount} files`);\n return;\n }\n\n const newFiles = acceptedFiles.map(file =>\n Object.assign(file, {\n preview: URL.createObjectURL(file),\n })\n );\n\n const updatedFiles = files ? [...files, ...newFiles] : newFiles;\n\n setFiles(updatedFiles);\n\n if (rejectedFiles.length > 0) {\n rejectedFiles.forEach(({ file }) => {\n toast.error(`File ${file.name} was rejected`);\n });\n }\n\n if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {\n const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;\n\n onUpload(updatedFiles).then(() => {\n setFiles([]);\n toast(`${target} uploaded`);\n });\n }\n },\n\n [files, maxFileCount, multiple, onUpload, setFiles]\n );\n\n function onRemove(index: number) {\n if (!files) return;\n\n if (isFileWithPreview(files[index])) {\n URL.revokeObjectURL(files[index].preview);\n }\n\n const newFiles = files.filter((_, i) => i !== index);\n setFiles(newFiles);\n onValueChange?.(newFiles);\n }\n\n // Revoke preview url when component unmounts\n useEffect(() => {\n return () => {\n if (!files) return;\n files.forEach(file => {\n if (isFileWithPreview(file)) {\n URL.revokeObjectURL(file.preview);\n }\n });\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;\n\n return (\n <div className=\"relative flex w-full flex-col gap-6\">\n <Dropzone onDrop={onDrop} accept={accept} maxSize={maxSize} maxFiles={maxFileCount} multiple={maxFileCount > 1 || multiple} disabled={isDisabled}>\n {({ getRootProps, getInputProps, isDragActive }) => (\n <div\n {...getRootProps()}\n className={cn(\n 'group',\n 'border-border-weak',\n 'hover:bg-border-weak/20',\n 'relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition',\n 'ring-offset-background',\n 'focus-visible:ring-primary-weak',\n 'focus-visible:ring-2',\n 'focus-visible:ring-offset-2',\n 'focus-visible:outline-hidden',\n isDragActive && 'border-border-primary-strong bg-border-weak/40',\n isDisabled && 'pointer-events-none opacity-60',\n className\n )}\n {...dropzoneProps}\n >\n <input {...getInputProps()} />\n {isDragActive ? (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload className=\"text-text-positive-muted size-7\" aria-hidden=\"true\" />\n </div>\n <p className=\"text-text-positive-muted font-medium\">Drop the files here</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload size={iconSize || 24} className=\"text-muted-foreground\" aria-hidden=\"true\" />\n </div>\n {showText && (\n <div className=\"flex flex-col gap-px\">\n <p className=\"text-text-positive-weak text-xs font-medium\">Drag {`'n'`} drop files here, or click to select files</p>\n <p className=\"text-text-positive-muted text-xs\">\n You can upload\n {maxFileCount > 1\n ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}\n files (up to ${formatBytes(maxSize)} each)`\n : ` a file with ${formatBytes(maxSize)}`}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </Dropzone>\n {files?.length ? (\n <div className=\"h-fit w-full overflow-y-auto\">\n <div className=\"flex items-center justify-between px-3 py-2.5\">\n <div className=\"text-text-positive-weak flex items-center gap-1\">\n <Image size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Ảnh đã tải lên ({files.length})</p>\n </div>\n <button\n className=\"hover:text-text-positive text-text-positive-muted flex cursor-pointer items-center gap-1 transition-colors duration-200\"\n onClick={() => {\n if (!files) return;\n files.forEach(file => {\n if (!isFileWithPreview(file)) return;\n URL.revokeObjectURL(file.preview);\n });\n setFiles([]);\n onValueChange?.([]);\n }}\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Xóa tất cả</p>\n </button>\n </div>\n <div className=\"flex flex-col gap-2.5\">\n {files?.map((file, index) => (\n <FileCard key={index} file={file} onRemove={() => onRemove(index)} progress={progresses?.[file.name]} />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface FileCardProps {\n file: File;\n onRemove: () => void;\n progress?: number;\n}\n\nfunction FileCard({ file, progress, onRemove }: FileCardProps) {\n return (\n <div className=\"border-border-weak hover:border-border relative flex items-center gap-4 rounded-md border py-2 pr-3 pl-2 shadow-xs transition-colors\">\n <div className=\"flex flex-1 gap-2.5\">\n {isFileWithPreview(file) ? <FilePreview file={file} /> : null}\n <div className=\"flex w-full flex-col gap-2\">\n <div className=\"flex flex-col gap-y-0.5\">\n <p className=\"text-text-positive line-clamp-1 text-sm\">{file.name}</p>\n <p className=\"text-text-positive-muted text-xs\">{formatBytes(file.size)}</p>\n </div>\n {progress ? <Progress value={progress} /> : null}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" color=\"secondary\" size=\"icon\" className=\"size-7\" onClick={onRemove}>\n <Trash2 size={16} className=\"text-text-positive-muted\" aria-hidden=\"true\" />\n <span className=\"sr-only\">Remove file</span>\n </Button>\n </div>\n </div>\n );\n}\n\nfunction isFileWithPreview(file: File): file is File & { preview: string } {\n return 'preview' in file && typeof file.preview === 'string';\n}\n\ninterface FilePreviewProps {\n file: File & { preview: string };\n}\n\nfunction FilePreview({ file }: FilePreviewProps) {\n if (file.type.startsWith('image/')) {\n return <img src={file.preview} className=\"border-border aspect-square size-10 shrink-0 rounded border object-cover object-top shadow-sm\" />;\n }\n\n return <FileText className=\"text-text-positive-muted size-10 border\" aria-hidden=\"true\" />;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-uploader.cjs","names":["Dropzone","Upload","Image","Trash2","Progress","Button","PdfFileIcon","CsvFileIcon","WordFileIcon","ExcelFileIcon","ZipFileIcon","AudioFileIcon","VideoFileIcon","UnknownFileIcon"],"sources":["../../packages/components/ui/file-uploader.tsx"],"sourcesContent":["'use client';\nimport { useCallback, useEffect } from 'react';\n\nimport { Image, Trash2, Upload } from 'lucide-react';\nimport { toast } from 'sonner';\n\nimport { useControllableState } from '@customafk/react-toolkit/hooks/useControllableState';\nimport { cn, formatBytes } from '@customafk/react-toolkit/utils';\n\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\nimport {\n AudioFileIcon,\n CsvFileIcon,\n ExcelFileIcon,\n type FileIconProps,\n PdfFileIcon,\n UnknownFileIcon,\n VideoFileIcon,\n WordFileIcon,\n ZipFileIcon,\n} from '@/components/features/file-icons';\n\nimport Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone';\n\n/**\n * Props for the FileUploader component.\n *\n * @example\n * ```tsx\n * import { FileUploader } from '@customafk/lunas-ui/ui/file-uploader';\n *\n * <FileUploader\n * accept={{ 'image/*': [] }}\n * maxSize={1024 * 1024 * 5}\n * maxFileCount={3}\n * multiple\n * onUpload={async (files) => { await uploadFiles(files); }}\n * />\n * ```\n */\ninterface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Value of the uploader.\n * @type File[]\n * @default undefined\n * @example value={files}\n */\n value?: File[];\n\n /**\n * Function to be called when the value changes.\n * @type (files: File[]) => void\n * @default undefined\n * @example onValueChange={(files) => setFiles(files)}\n */\n onValueChange?: (files: File[]) => void;\n\n /**\n * Function to be called when files are uploaded.\n * @type (files: File[]) => Promise<void>\n * @default undefined\n * @example onUpload={(files) => uploadFiles(files)}\n */\n onUpload?: (files: File[]) => Promise<void>;\n\n /**\n * Progress of the uploaded files.\n * @type Record<string, number> | undefined\n * @default undefined\n * @example progresses={{ \"file1.png\": 50 }}\n */\n progresses?: Record<string, number>;\n\n /**\n * Accepted file types for the uploader.\n * @type { [key: string]: string[]}\n * @default\n * ```ts\n * { \"image/*\": [] }\n * ```\n * @example accept={[\"image/png\", \"image/jpeg\"]}\n */\n accept?: DropzoneProps['accept'];\n\n /**\n * Maximum file size for the uploader.\n * @type number | undefined\n * @default 1024 * 1024 * 2 // 2MB\n * @example maxSize={1024 * 1024 * 2} // 2MB\n */\n maxSize?: DropzoneProps['maxSize'];\n\n /**\n * Maximum number of files for the uploader.\n * @type number | undefined\n * @default 1\n * @example maxFileCount={4}\n */\n maxFileCount?: DropzoneProps['maxFiles'];\n\n /**\n * Whether the uploader should accept multiple files.\n * @type boolean\n * @default false\n * @example multiple\n */\n multiple?: boolean;\n\n /**\n * Whether the uploader is disabled.\n * @type boolean\n * @default false\n * @example disabled\n */\n disabled?: boolean;\n\n /**\n * Whether to show the instructional drag-and-drop text inside the drop zone.\n * @default true\n */\n showText?: boolean;\n /** Size in pixels for the upload icon. */\n iconSize?: number;\n}\n\n/**\n * A drag-and-drop file upload zone that manages file selection, validation, preview generation, and optional async upload with progress tracking.\n */\nexport function FileUploader(props: FileUploaderProps) {\n const {\n value: valueProp,\n onValueChange,\n onUpload,\n progresses,\n accept = {\n 'image/*': [],\n },\n maxSize = 1024 * 1024 * 2,\n maxFileCount = 1,\n multiple = false,\n disabled = false,\n showText = true,\n iconSize,\n className,\n ...dropzoneProps\n } = props;\n\n const [files, setFiles] = useControllableState({\n prop: valueProp,\n onChange: onValueChange,\n });\n\n const onDrop = useCallback(\n (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {\n if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {\n toast.error('Cannot upload more than 1 file at a time');\n\n return;\n }\n\n if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {\n toast.error(`Cannot upload more than ${maxFileCount} files`);\n return;\n }\n\n const newFiles = acceptedFiles.map(file =>\n Object.assign(file, {\n preview: URL.createObjectURL(file),\n })\n );\n\n const updatedFiles = files ? [...files, ...newFiles] : newFiles;\n\n setFiles(updatedFiles);\n\n if (rejectedFiles.length > 0) {\n rejectedFiles.forEach(({ file }) => {\n toast.error(`File ${file.name} was rejected`);\n });\n }\n\n if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {\n const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;\n\n onUpload(updatedFiles).then(() => {\n setFiles([]);\n toast(`${target} uploaded`);\n });\n }\n },\n\n [files, maxFileCount, multiple, onUpload, setFiles]\n );\n\n function onRemove(index: number) {\n if (!files) return;\n\n if (isFileWithPreview(files[index])) {\n URL.revokeObjectURL(files[index].preview);\n }\n\n const newFiles = files.filter((_, i) => i !== index);\n setFiles(newFiles);\n onValueChange?.(newFiles);\n }\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to run this effect on unmount, so we don't need to include any dependencies.\n useEffect(() => {\n return () => {\n if (!files) return;\n files.forEach(file => {\n if (isFileWithPreview(file)) {\n URL.revokeObjectURL(file.preview);\n }\n });\n };\n }, []);\n\n const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;\n\n return (\n <div className=\"relative flex w-full flex-col gap-6\">\n <Dropzone onDrop={onDrop} accept={accept} maxSize={maxSize} maxFiles={maxFileCount} multiple={maxFileCount > 1 || multiple} disabled={isDisabled}>\n {({ getRootProps, getInputProps, isDragActive }) => (\n <div\n {...getRootProps()}\n className={cn(\n 'group',\n 'border-border-weak',\n 'hover:bg-border-weak/20',\n 'relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition',\n 'ring-offset-background',\n 'focus-visible:ring-primary-weak',\n 'focus-visible:ring-2',\n 'focus-visible:ring-offset-2',\n 'focus-visible:outline-hidden',\n isDragActive && 'border-border-primary-strong bg-border-weak/40',\n isDisabled && 'pointer-events-none opacity-60',\n className\n )}\n {...dropzoneProps}\n >\n <input {...getInputProps()} />\n {isDragActive ? (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload className=\"size-7 text-text-positive-muted\" aria-hidden=\"true\" />\n </div>\n <p className=\"font-medium text-text-positive-muted\">Drop the files here</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload size={iconSize || 24} className=\"text-muted-foreground\" aria-hidden=\"true\" />\n </div>\n {!!showText && (\n <div className=\"flex flex-col gap-px\">\n <p className=\"font-medium text-text-positive-weak text-xs\">Drag {`'n'`} drop files here, or click to select files</p>\n <p className=\"text-text-positive-muted text-xs\">\n You can upload\n {maxFileCount > 1\n ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}\n files (up to ${formatBytes(maxSize)} each)`\n : ` a file with ${formatBytes(maxSize)}`}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </Dropzone>\n {files?.length ? (\n <div className=\"h-fit w-full overflow-y-auto\">\n <div className=\"flex items-center justify-between px-3 py-2.5\">\n <div className=\"flex items-center gap-1 text-text-positive-weak\">\n <Image size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Ảnh đã tải lên ({files.length})</p>\n </div>\n <button\n className=\"flex cursor-pointer items-center gap-1 text-text-positive-muted transition-colors duration-200 hover:text-text-positive\"\n onClick={() => {\n if (!files) return;\n files.forEach(file => {\n if (!isFileWithPreview(file)) return;\n URL.revokeObjectURL(file.preview);\n });\n setFiles([]);\n onValueChange?.([]);\n }}\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Xóa tất cả</p>\n </button>\n </div>\n <div className=\"flex flex-col gap-2.5\">\n {files?.map((file, index) => (\n <FileCard key={`${index.toString()}`} file={file} onRemove={() => onRemove(index)} progress={progresses?.[file.name]} />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface FileCardProps {\n file: File;\n onRemove: () => void;\n progress?: number;\n}\n\nfunction FileCard({ file, progress, onRemove }: FileCardProps) {\n return (\n <div className=\"relative flex items-center gap-4 rounded border border-border-weak py-2 pr-3 pl-2 shadow-xs transition-colors hover:border-border\">\n <div className=\"flex flex-1 gap-2.5\">\n {isFileWithPreview(file) ? <FilePreview file={file} /> : null}\n <div className=\"flex w-full flex-col gap-2\">\n <div className=\"flex flex-col gap-y-0.5\">\n <p className=\"line-clamp-1 text-sm text-text-positive\">{file.name}</p>\n <p className=\"text-text-positive-muted text-xs\">{formatBytes(file.size)}</p>\n </div>\n {progress ? <Progress value={progress} /> : null}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" color=\"danger\" size=\"icon\" className=\"size-7\" onClick={onRemove}>\n <Trash2 size={16} aria-hidden=\"true\" />\n <span className=\"sr-only\">Remove file</span>\n </Button>\n </div>\n </div>\n );\n}\n\nfunction isFileWithPreview(file: File): file is File & { preview: string } {\n return 'preview' in file && typeof file.preview === 'string';\n}\n\ninterface FilePreviewProps {\n file: File & { preview: string };\n}\n\nconst MIME_TYPE_ICON_MAP: Record<string, React.FC<FileIconProps>> = {\n 'application/pdf': PdfFileIcon,\n 'text/csv': CsvFileIcon,\n 'application/msword': WordFileIcon,\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': WordFileIcon,\n 'application/vnd.ms-excel': ExcelFileIcon,\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ExcelFileIcon,\n 'application/zip': ZipFileIcon,\n 'application/x-zip-compressed': ZipFileIcon,\n};\n\nfunction FilePreview({ file }: FilePreviewProps) {\n if (file.type.startsWith('image/')) {\n return <img src={file.preview} className=\"aspect-square size-10 shrink-0 rounded border border-border object-cover object-top shadow-sm\" />;\n }\n\n if (file.type.startsWith('audio/')) {\n return <AudioFileIcon size={40} className=\"size-10 shrink-0\" aria-hidden=\"true\" />;\n }\n\n if (file.type.startsWith('video/')) {\n return <VideoFileIcon size={40} className=\"size-10 shrink-0\" aria-hidden=\"true\" />;\n }\n\n const Icon = MIME_TYPE_ICON_MAP[file.type] ?? UnknownFileIcon;\n\n return <Icon size={40} className=\"size-10 shrink-0\" aria-hidden=\"true\" />;\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- "use client";import{Button as e}from"./button.mjs";import{Progress as t}from"./progress.mjs";import{FileText as n,Image as r,Trash2 as i,Upload as a}from"lucide-react";import{useCallback as o,useEffect as s}from"react";import{cn as c,formatBytes as l}from"@customafk/react-toolkit/utils";import{jsx as u,jsxs as d}from"react/jsx-runtime";import f from"react-dropzone";import{useControllableState as p}from"@customafk/react-toolkit/hooks/useControllableState";import{toast as m}from"sonner";function h(e){let{value:t,onValueChange:n,onUpload:h,progresses:v,accept:y={"image/*":[]},maxSize:b=1024*1024*2,maxFileCount:x=1,multiple:S=!1,disabled:C=!1,showText:w=!0,iconSize:T,className:E,...D}=e,[O,k]=p({prop:t,onChange:n}),A=o((e,t)=>{if(!S&&x===1&&e.length>1){m.error(`Cannot upload more than 1 file at a time`);return}if((O?.length??0)+e.length>x){m.error(`Cannot upload more than ${x} files`);return}let n=e.map(e=>Object.assign(e,{preview:URL.createObjectURL(e)})),r=O?[...O,...n]:n;if(k(r),t.length>0&&t.forEach(({file:e})=>{m.error(`File ${e.name} was rejected`)}),h&&r.length>0&&r.length<=x){let e=r.length>0?`${r.length} files`:`file`;h(r).then(()=>{k([]),m(`${e} uploaded`)})}},[O,x,S,h,k]);function j(e){if(!O)return;_(O[e])&&URL.revokeObjectURL(O[e].preview);let t=O.filter((t,n)=>n!==e);k(t),n?.(t)}s(()=>()=>{O&&O.forEach(e=>{_(e)&&URL.revokeObjectURL(e.preview)})},[]);let M=C||(O?.length??0)>=x;return d(`div`,{className:`relative flex w-full flex-col gap-6`,children:[u(f,{onDrop:A,accept:y,maxSize:b,maxFiles:x,multiple:x>1||S,disabled:M,children:({getRootProps:e,getInputProps:t,isDragActive:n})=>d(`div`,{...e(),className:c(`group`,`border-border-weak`,`hover:bg-border-weak/20`,`relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition`,`ring-offset-background`,`focus-visible:ring-primary-weak`,`focus-visible:ring-2`,`focus-visible:ring-offset-2`,`focus-visible:outline-hidden`,n&&`border-border-primary-strong bg-border-weak/40`,M&&`pointer-events-none opacity-60`,E),...D,children:[u(`input`,{...t()}),n?d(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[u(`div`,{className:`rounded-full border border-dashed p-3`,children:u(a,{className:`text-text-positive-muted size-7`,"aria-hidden":`true`})}),u(`p`,{className:`text-text-positive-muted font-medium`,children:`Drop the files here`})]}):d(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[u(`div`,{className:`rounded-full border border-dashed p-3`,children:u(a,{size:T||24,className:`text-muted-foreground`,"aria-hidden":`true`})}),w&&d(`div`,{className:`flex flex-col gap-px`,children:[d(`p`,{className:`text-text-positive-weak text-xs font-medium`,children:[`Drag `,`'n'`,` drop files here, or click to select files`]}),d(`p`,{className:`text-text-positive-muted text-xs`,children:[`You can upload`,x>1?` ${x===1/0?`multiple`:x}
2
- files (up to ${l(b)} each)`:` a file with ${l(b)}`]})]})]})]})}),O?.length?d(`div`,{className:`h-fit w-full overflow-y-auto`,children:[d(`div`,{className:`flex items-center justify-between px-3 py-2.5`,children:[d(`div`,{className:`text-text-positive-weak flex items-center gap-1`,children:[u(r,{size:16,"aria-hidden":`true`}),d(`p`,{className:`text-sm`,children:[`Ảnh đã tải lên (`,O.length,`)`]})]}),d(`button`,{className:`hover:text-text-positive text-text-positive-muted flex cursor-pointer items-center gap-1 transition-colors duration-200`,onClick:()=>{O&&(O.forEach(e=>{_(e)&&URL.revokeObjectURL(e.preview)}),k([]),n?.([]))},children:[u(i,{size:16,"aria-hidden":`true`}),u(`p`,{className:`text-sm`,children:`Xóa tất cả`})]})]}),u(`div`,{className:`flex flex-col gap-2.5`,children:O?.map((e,t)=>u(g,{file:e,onRemove:()=>j(t),progress:v?.[e.name]},t))})]}):null]})}function g({file:n,progress:r,onRemove:a}){return d(`div`,{className:`border-border-weak hover:border-border relative flex items-center gap-4 rounded-md border py-2 pr-3 pl-2 shadow-xs transition-colors`,children:[d(`div`,{className:`flex flex-1 gap-2.5`,children:[_(n)?u(v,{file:n}):null,d(`div`,{className:`flex w-full flex-col gap-2`,children:[d(`div`,{className:`flex flex-col gap-y-0.5`,children:[u(`p`,{className:`text-text-positive line-clamp-1 text-sm`,children:n.name}),u(`p`,{className:`text-text-positive-muted text-xs`,children:l(n.size)})]}),r?u(t,{value:r}):null]})]}),u(`div`,{className:`flex items-center gap-2`,children:d(e,{type:`button`,variant:`ghost`,color:`secondary`,size:`icon`,className:`size-7`,onClick:a,children:[u(i,{size:16,className:`text-text-positive-muted`,"aria-hidden":`true`}),u(`span`,{className:`sr-only`,children:`Remove file`})]})})]})}function _(e){return`preview`in e&&typeof e.preview==`string`}function v({file:e}){return e.type.startsWith(`image/`)?u(`img`,{src:e.preview,className:`border-border aspect-square size-10 shrink-0 rounded border object-cover object-top shadow-sm`}):u(n,{className:`text-text-positive-muted size-10 border`,"aria-hidden":`true`})}export{h as FileUploader};
1
+ "use client";import{Button as e}from"./button.mjs";import{Progress as t}from"./progress.mjs";import{AudioFileIcon as n,CsvFileIcon as r,ExcelFileIcon as i,PdfFileIcon as a,UnknownFileIcon as o,VideoFileIcon as s,WordFileIcon as c,ZipFileIcon as l}from"../features/file-icons/index.mjs";import{Image as u,Trash2 as d,Upload as f}from"lucide-react";import{useCallback as p,useEffect as m}from"react";import{cn as h,formatBytes as g}from"@customafk/react-toolkit/utils";import{jsx as _,jsxs as v}from"react/jsx-runtime";import{toast as y}from"sonner";import{useControllableState as b}from"@customafk/react-toolkit/hooks/useControllableState";import x from"react-dropzone";function S(e){let{value:t,onValueChange:n,onUpload:r,progresses:i,accept:a={"image/*":[]},maxSize:o=1024*1024*2,maxFileCount:s=1,multiple:c=!1,disabled:l=!1,showText:S=!0,iconSize:T,className:E,...D}=e,[O,k]=b({prop:t,onChange:n}),A=p((e,t)=>{if(!c&&s===1&&e.length>1){y.error(`Cannot upload more than 1 file at a time`);return}if((O?.length??0)+e.length>s){y.error(`Cannot upload more than ${s} files`);return}let n=e.map(e=>Object.assign(e,{preview:URL.createObjectURL(e)})),i=O?[...O,...n]:n;if(k(i),t.length>0&&t.forEach(({file:e})=>{y.error(`File ${e.name} was rejected`)}),r&&i.length>0&&i.length<=s){let e=i.length>0?`${i.length} files`:`file`;r(i).then(()=>{k([]),y(`${e} uploaded`)})}},[O,s,c,r,k]);function j(e){if(!O)return;w(O[e])&&URL.revokeObjectURL(O[e].preview);let t=O.filter((t,n)=>n!==e);k(t),n?.(t)}m(()=>()=>{O&&O.forEach(e=>{w(e)&&URL.revokeObjectURL(e.preview)})},[]);let M=l||(O?.length??0)>=s;return v(`div`,{className:`relative flex w-full flex-col gap-6`,children:[_(x,{onDrop:A,accept:a,maxSize:o,maxFiles:s,multiple:s>1||c,disabled:M,children:({getRootProps:e,getInputProps:t,isDragActive:n})=>v(`div`,{...e(),className:h(`group`,`border-border-weak`,`hover:bg-border-weak/20`,`relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition`,`ring-offset-background`,`focus-visible:ring-primary-weak`,`focus-visible:ring-2`,`focus-visible:ring-offset-2`,`focus-visible:outline-hidden`,n&&`border-border-primary-strong bg-border-weak/40`,M&&`pointer-events-none opacity-60`,E),...D,children:[_(`input`,{...t()}),n?v(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[_(`div`,{className:`rounded-full border border-dashed p-3`,children:_(f,{className:`size-7 text-text-positive-muted`,"aria-hidden":`true`})}),_(`p`,{className:`font-medium text-text-positive-muted`,children:`Drop the files here`})]}):v(`div`,{className:`flex flex-col items-center justify-center gap-4 sm:px-5`,children:[_(`div`,{className:`rounded-full border border-dashed p-3`,children:_(f,{size:T||24,className:`text-muted-foreground`,"aria-hidden":`true`})}),!!S&&v(`div`,{className:`flex flex-col gap-px`,children:[v(`p`,{className:`font-medium text-text-positive-weak text-xs`,children:[`Drag `,`'n'`,` drop files here, or click to select files`]}),v(`p`,{className:`text-text-positive-muted text-xs`,children:[`You can upload`,s>1?` ${s===1/0?`multiple`:s}
2
+ files (up to ${g(o)} each)`:` a file with ${g(o)}`]})]})]})]})}),O?.length?v(`div`,{className:`h-fit w-full overflow-y-auto`,children:[v(`div`,{className:`flex items-center justify-between px-3 py-2.5`,children:[v(`div`,{className:`flex items-center gap-1 text-text-positive-weak`,children:[_(u,{size:16,"aria-hidden":`true`}),v(`p`,{className:`text-sm`,children:[`Ảnh đã tải lên (`,O.length,`)`]})]}),v(`button`,{className:`flex cursor-pointer items-center gap-1 text-text-positive-muted transition-colors duration-200 hover:text-text-positive`,onClick:()=>{O&&(O.forEach(e=>{w(e)&&URL.revokeObjectURL(e.preview)}),k([]),n?.([]))},children:[_(d,{size:16,"aria-hidden":`true`}),_(`p`,{className:`text-sm`,children:`Xóa tất cả`})]})]}),_(`div`,{className:`flex flex-col gap-2.5`,children:O?.map((e,t)=>_(C,{file:e,onRemove:()=>j(t),progress:i?.[e.name]},`${t.toString()}`))})]}):null]})}function C({file:n,progress:r,onRemove:i}){return v(`div`,{className:`relative flex items-center gap-4 rounded border border-border-weak py-2 pr-3 pl-2 shadow-xs transition-colors hover:border-border`,children:[v(`div`,{className:`flex flex-1 gap-2.5`,children:[w(n)?_(E,{file:n}):null,v(`div`,{className:`flex w-full flex-col gap-2`,children:[v(`div`,{className:`flex flex-col gap-y-0.5`,children:[_(`p`,{className:`line-clamp-1 text-sm text-text-positive`,children:n.name}),_(`p`,{className:`text-text-positive-muted text-xs`,children:g(n.size)})]}),r?_(t,{value:r}):null]})]}),_(`div`,{className:`flex items-center gap-2`,children:v(e,{type:`button`,variant:`ghost`,color:`danger`,size:`icon`,className:`size-7`,onClick:i,children:[_(d,{size:16,"aria-hidden":`true`}),_(`span`,{className:`sr-only`,children:`Remove file`})]})})]})}function w(e){return`preview`in e&&typeof e.preview==`string`}const T={"application/pdf":a,"text/csv":r,"application/msword":c,"application/vnd.openxmlformats-officedocument.wordprocessingml.document":c,"application/vnd.ms-excel":i,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":i,"application/zip":l,"application/x-zip-compressed":l};function E({file:e}){return e.type.startsWith(`image/`)?_(`img`,{src:e.preview,className:`aspect-square size-10 shrink-0 rounded border border-border object-cover object-top shadow-sm`}):e.type.startsWith(`audio/`)?_(n,{size:40,className:`size-10 shrink-0`,"aria-hidden":`true`}):e.type.startsWith(`video/`)?_(s,{size:40,className:`size-10 shrink-0`,"aria-hidden":`true`}):_(T[e.type]??o,{size:40,className:`size-10 shrink-0`,"aria-hidden":`true`})}export{S as FileUploader};
3
3
  //# sourceMappingURL=file-uploader.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-uploader.mjs","names":[],"sources":["../../packages/components/ui/file-uploader.tsx"],"sourcesContent":["'use client';\nimport { useCallback, useEffect } from 'react';\nimport Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone';\nimport { useControllableState } from '@customafk/react-toolkit/hooks/useControllableState';\nimport { cn, formatBytes } from '@customafk/react-toolkit/utils';\n\nimport { FileText, Image, Trash2, Upload } from 'lucide-react';\nimport { toast } from 'sonner';\n\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\n\n/**\n * Props for the FileUploader component.\n *\n * @example\n * ```tsx\n * import { FileUploader } from '@customafk/lunas-ui/ui/file-uploader';\n *\n * <FileUploader\n * accept={{ 'image/*': [] }}\n * maxSize={1024 * 1024 * 5}\n * maxFileCount={3}\n * multiple\n * onUpload={async (files) => { await uploadFiles(files); }}\n * />\n * ```\n */\ninterface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Value of the uploader.\n * @type File[]\n * @default undefined\n * @example value={files}\n */\n value?: File[];\n\n /**\n * Function to be called when the value changes.\n * @type (files: File[]) => void\n * @default undefined\n * @example onValueChange={(files) => setFiles(files)}\n */\n onValueChange?: (files: File[]) => void;\n\n /**\n * Function to be called when files are uploaded.\n * @type (files: File[]) => Promise<void>\n * @default undefined\n * @example onUpload={(files) => uploadFiles(files)}\n */\n onUpload?: (files: File[]) => Promise<void>;\n\n /**\n * Progress of the uploaded files.\n * @type Record<string, number> | undefined\n * @default undefined\n * @example progresses={{ \"file1.png\": 50 }}\n */\n progresses?: Record<string, number>;\n\n /**\n * Accepted file types for the uploader.\n * @type { [key: string]: string[]}\n * @default\n * ```ts\n * { \"image/*\": [] }\n * ```\n * @example accept={[\"image/png\", \"image/jpeg\"]}\n */\n accept?: DropzoneProps['accept'];\n\n /**\n * Maximum file size for the uploader.\n * @type number | undefined\n * @default 1024 * 1024 * 2 // 2MB\n * @example maxSize={1024 * 1024 * 2} // 2MB\n */\n maxSize?: DropzoneProps['maxSize'];\n\n /**\n * Maximum number of files for the uploader.\n * @type number | undefined\n * @default 1\n * @example maxFileCount={4}\n */\n maxFileCount?: DropzoneProps['maxFiles'];\n\n /**\n * Whether the uploader should accept multiple files.\n * @type boolean\n * @default false\n * @example multiple\n */\n multiple?: boolean;\n\n /**\n * Whether the uploader is disabled.\n * @type boolean\n * @default false\n * @example disabled\n */\n disabled?: boolean;\n\n /**\n * Whether to show the instructional drag-and-drop text inside the drop zone.\n * @default true\n */\n showText?: boolean;\n /** Size in pixels for the upload icon. */\n iconSize?: number;\n}\n\n/**\n * A drag-and-drop file upload zone that manages file selection, validation, preview generation, and optional async upload with progress tracking.\n */\nexport function FileUploader(props: FileUploaderProps) {\n const {\n value: valueProp,\n onValueChange,\n onUpload,\n progresses,\n accept = {\n 'image/*': [],\n },\n maxSize = 1024 * 1024 * 2,\n maxFileCount = 1,\n multiple = false,\n disabled = false,\n showText = true,\n iconSize,\n className,\n ...dropzoneProps\n } = props;\n\n const [files, setFiles] = useControllableState({\n prop: valueProp,\n onChange: onValueChange,\n });\n\n const onDrop = useCallback(\n (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {\n if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {\n toast.error('Cannot upload more than 1 file at a time');\n\n return;\n }\n\n if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {\n toast.error(`Cannot upload more than ${maxFileCount} files`);\n return;\n }\n\n const newFiles = acceptedFiles.map(file =>\n Object.assign(file, {\n preview: URL.createObjectURL(file),\n })\n );\n\n const updatedFiles = files ? [...files, ...newFiles] : newFiles;\n\n setFiles(updatedFiles);\n\n if (rejectedFiles.length > 0) {\n rejectedFiles.forEach(({ file }) => {\n toast.error(`File ${file.name} was rejected`);\n });\n }\n\n if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {\n const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;\n\n onUpload(updatedFiles).then(() => {\n setFiles([]);\n toast(`${target} uploaded`);\n });\n }\n },\n\n [files, maxFileCount, multiple, onUpload, setFiles]\n );\n\n function onRemove(index: number) {\n if (!files) return;\n\n if (isFileWithPreview(files[index])) {\n URL.revokeObjectURL(files[index].preview);\n }\n\n const newFiles = files.filter((_, i) => i !== index);\n setFiles(newFiles);\n onValueChange?.(newFiles);\n }\n\n // Revoke preview url when component unmounts\n useEffect(() => {\n return () => {\n if (!files) return;\n files.forEach(file => {\n if (isFileWithPreview(file)) {\n URL.revokeObjectURL(file.preview);\n }\n });\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;\n\n return (\n <div className=\"relative flex w-full flex-col gap-6\">\n <Dropzone onDrop={onDrop} accept={accept} maxSize={maxSize} maxFiles={maxFileCount} multiple={maxFileCount > 1 || multiple} disabled={isDisabled}>\n {({ getRootProps, getInputProps, isDragActive }) => (\n <div\n {...getRootProps()}\n className={cn(\n 'group',\n 'border-border-weak',\n 'hover:bg-border-weak/20',\n 'relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition',\n 'ring-offset-background',\n 'focus-visible:ring-primary-weak',\n 'focus-visible:ring-2',\n 'focus-visible:ring-offset-2',\n 'focus-visible:outline-hidden',\n isDragActive && 'border-border-primary-strong bg-border-weak/40',\n isDisabled && 'pointer-events-none opacity-60',\n className\n )}\n {...dropzoneProps}\n >\n <input {...getInputProps()} />\n {isDragActive ? (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload className=\"text-text-positive-muted size-7\" aria-hidden=\"true\" />\n </div>\n <p className=\"text-text-positive-muted font-medium\">Drop the files here</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload size={iconSize || 24} className=\"text-muted-foreground\" aria-hidden=\"true\" />\n </div>\n {showText && (\n <div className=\"flex flex-col gap-px\">\n <p className=\"text-text-positive-weak text-xs font-medium\">Drag {`'n'`} drop files here, or click to select files</p>\n <p className=\"text-text-positive-muted text-xs\">\n You can upload\n {maxFileCount > 1\n ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}\n files (up to ${formatBytes(maxSize)} each)`\n : ` a file with ${formatBytes(maxSize)}`}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </Dropzone>\n {files?.length ? (\n <div className=\"h-fit w-full overflow-y-auto\">\n <div className=\"flex items-center justify-between px-3 py-2.5\">\n <div className=\"text-text-positive-weak flex items-center gap-1\">\n <Image size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Ảnh đã tải lên ({files.length})</p>\n </div>\n <button\n className=\"hover:text-text-positive text-text-positive-muted flex cursor-pointer items-center gap-1 transition-colors duration-200\"\n onClick={() => {\n if (!files) return;\n files.forEach(file => {\n if (!isFileWithPreview(file)) return;\n URL.revokeObjectURL(file.preview);\n });\n setFiles([]);\n onValueChange?.([]);\n }}\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Xóa tất cả</p>\n </button>\n </div>\n <div className=\"flex flex-col gap-2.5\">\n {files?.map((file, index) => (\n <FileCard key={index} file={file} onRemove={() => onRemove(index)} progress={progresses?.[file.name]} />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface FileCardProps {\n file: File;\n onRemove: () => void;\n progress?: number;\n}\n\nfunction FileCard({ file, progress, onRemove }: FileCardProps) {\n return (\n <div className=\"border-border-weak hover:border-border relative flex items-center gap-4 rounded-md border py-2 pr-3 pl-2 shadow-xs transition-colors\">\n <div className=\"flex flex-1 gap-2.5\">\n {isFileWithPreview(file) ? <FilePreview file={file} /> : null}\n <div className=\"flex w-full flex-col gap-2\">\n <div className=\"flex flex-col gap-y-0.5\">\n <p className=\"text-text-positive line-clamp-1 text-sm\">{file.name}</p>\n <p className=\"text-text-positive-muted text-xs\">{formatBytes(file.size)}</p>\n </div>\n {progress ? <Progress value={progress} /> : null}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" color=\"secondary\" size=\"icon\" className=\"size-7\" onClick={onRemove}>\n <Trash2 size={16} className=\"text-text-positive-muted\" aria-hidden=\"true\" />\n <span className=\"sr-only\">Remove file</span>\n </Button>\n </div>\n </div>\n );\n}\n\nfunction isFileWithPreview(file: File): file is File & { preview: string } {\n return 'preview' in file && typeof file.preview === 'string';\n}\n\ninterface FilePreviewProps {\n file: File & { preview: string };\n}\n\nfunction FilePreview({ file }: FilePreviewProps) {\n if (file.type.startsWith('image/')) {\n return <img src={file.preview} className=\"border-border aspect-square size-10 shrink-0 rounded border object-cover object-top shadow-sm\" />;\n }\n\n return <FileText className=\"text-text-positive-muted size-10 border\" aria-hidden=\"true\" />;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-uploader.mjs","names":[],"sources":["../../packages/components/ui/file-uploader.tsx"],"sourcesContent":["'use client';\nimport { useCallback, useEffect } from 'react';\n\nimport { Image, Trash2, Upload } from 'lucide-react';\nimport { toast } from 'sonner';\n\nimport { useControllableState } from '@customafk/react-toolkit/hooks/useControllableState';\nimport { cn, formatBytes } from '@customafk/react-toolkit/utils';\n\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\nimport {\n AudioFileIcon,\n CsvFileIcon,\n ExcelFileIcon,\n type FileIconProps,\n PdfFileIcon,\n UnknownFileIcon,\n VideoFileIcon,\n WordFileIcon,\n ZipFileIcon,\n} from '@/components/features/file-icons';\n\nimport Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone';\n\n/**\n * Props for the FileUploader component.\n *\n * @example\n * ```tsx\n * import { FileUploader } from '@customafk/lunas-ui/ui/file-uploader';\n *\n * <FileUploader\n * accept={{ 'image/*': [] }}\n * maxSize={1024 * 1024 * 5}\n * maxFileCount={3}\n * multiple\n * onUpload={async (files) => { await uploadFiles(files); }}\n * />\n * ```\n */\ninterface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Value of the uploader.\n * @type File[]\n * @default undefined\n * @example value={files}\n */\n value?: File[];\n\n /**\n * Function to be called when the value changes.\n * @type (files: File[]) => void\n * @default undefined\n * @example onValueChange={(files) => setFiles(files)}\n */\n onValueChange?: (files: File[]) => void;\n\n /**\n * Function to be called when files are uploaded.\n * @type (files: File[]) => Promise<void>\n * @default undefined\n * @example onUpload={(files) => uploadFiles(files)}\n */\n onUpload?: (files: File[]) => Promise<void>;\n\n /**\n * Progress of the uploaded files.\n * @type Record<string, number> | undefined\n * @default undefined\n * @example progresses={{ \"file1.png\": 50 }}\n */\n progresses?: Record<string, number>;\n\n /**\n * Accepted file types for the uploader.\n * @type { [key: string]: string[]}\n * @default\n * ```ts\n * { \"image/*\": [] }\n * ```\n * @example accept={[\"image/png\", \"image/jpeg\"]}\n */\n accept?: DropzoneProps['accept'];\n\n /**\n * Maximum file size for the uploader.\n * @type number | undefined\n * @default 1024 * 1024 * 2 // 2MB\n * @example maxSize={1024 * 1024 * 2} // 2MB\n */\n maxSize?: DropzoneProps['maxSize'];\n\n /**\n * Maximum number of files for the uploader.\n * @type number | undefined\n * @default 1\n * @example maxFileCount={4}\n */\n maxFileCount?: DropzoneProps['maxFiles'];\n\n /**\n * Whether the uploader should accept multiple files.\n * @type boolean\n * @default false\n * @example multiple\n */\n multiple?: boolean;\n\n /**\n * Whether the uploader is disabled.\n * @type boolean\n * @default false\n * @example disabled\n */\n disabled?: boolean;\n\n /**\n * Whether to show the instructional drag-and-drop text inside the drop zone.\n * @default true\n */\n showText?: boolean;\n /** Size in pixels for the upload icon. */\n iconSize?: number;\n}\n\n/**\n * A drag-and-drop file upload zone that manages file selection, validation, preview generation, and optional async upload with progress tracking.\n */\nexport function FileUploader(props: FileUploaderProps) {\n const {\n value: valueProp,\n onValueChange,\n onUpload,\n progresses,\n accept = {\n 'image/*': [],\n },\n maxSize = 1024 * 1024 * 2,\n maxFileCount = 1,\n multiple = false,\n disabled = false,\n showText = true,\n iconSize,\n className,\n ...dropzoneProps\n } = props;\n\n const [files, setFiles] = useControllableState({\n prop: valueProp,\n onChange: onValueChange,\n });\n\n const onDrop = useCallback(\n (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {\n if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {\n toast.error('Cannot upload more than 1 file at a time');\n\n return;\n }\n\n if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {\n toast.error(`Cannot upload more than ${maxFileCount} files`);\n return;\n }\n\n const newFiles = acceptedFiles.map(file =>\n Object.assign(file, {\n preview: URL.createObjectURL(file),\n })\n );\n\n const updatedFiles = files ? [...files, ...newFiles] : newFiles;\n\n setFiles(updatedFiles);\n\n if (rejectedFiles.length > 0) {\n rejectedFiles.forEach(({ file }) => {\n toast.error(`File ${file.name} was rejected`);\n });\n }\n\n if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {\n const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;\n\n onUpload(updatedFiles).then(() => {\n setFiles([]);\n toast(`${target} uploaded`);\n });\n }\n },\n\n [files, maxFileCount, multiple, onUpload, setFiles]\n );\n\n function onRemove(index: number) {\n if (!files) return;\n\n if (isFileWithPreview(files[index])) {\n URL.revokeObjectURL(files[index].preview);\n }\n\n const newFiles = files.filter((_, i) => i !== index);\n setFiles(newFiles);\n onValueChange?.(newFiles);\n }\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to run this effect on unmount, so we don't need to include any dependencies.\n useEffect(() => {\n return () => {\n if (!files) return;\n files.forEach(file => {\n if (isFileWithPreview(file)) {\n URL.revokeObjectURL(file.preview);\n }\n });\n };\n }, []);\n\n const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;\n\n return (\n <div className=\"relative flex w-full flex-col gap-6\">\n <Dropzone onDrop={onDrop} accept={accept} maxSize={maxSize} maxFiles={maxFileCount} multiple={maxFileCount > 1 || multiple} disabled={isDisabled}>\n {({ getRootProps, getInputProps, isDragActive }) => (\n <div\n {...getRootProps()}\n className={cn(\n 'group',\n 'border-border-weak',\n 'hover:bg-border-weak/20',\n 'relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition',\n 'ring-offset-background',\n 'focus-visible:ring-primary-weak',\n 'focus-visible:ring-2',\n 'focus-visible:ring-offset-2',\n 'focus-visible:outline-hidden',\n isDragActive && 'border-border-primary-strong bg-border-weak/40',\n isDisabled && 'pointer-events-none opacity-60',\n className\n )}\n {...dropzoneProps}\n >\n <input {...getInputProps()} />\n {isDragActive ? (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload className=\"size-7 text-text-positive-muted\" aria-hidden=\"true\" />\n </div>\n <p className=\"font-medium text-text-positive-muted\">Drop the files here</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-4 sm:px-5\">\n <div className=\"rounded-full border border-dashed p-3\">\n <Upload size={iconSize || 24} className=\"text-muted-foreground\" aria-hidden=\"true\" />\n </div>\n {!!showText && (\n <div className=\"flex flex-col gap-px\">\n <p className=\"font-medium text-text-positive-weak text-xs\">Drag {`'n'`} drop files here, or click to select files</p>\n <p className=\"text-text-positive-muted text-xs\">\n You can upload\n {maxFileCount > 1\n ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}\n files (up to ${formatBytes(maxSize)} each)`\n : ` a file with ${formatBytes(maxSize)}`}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </Dropzone>\n {files?.length ? (\n <div className=\"h-fit w-full overflow-y-auto\">\n <div className=\"flex items-center justify-between px-3 py-2.5\">\n <div className=\"flex items-center gap-1 text-text-positive-weak\">\n <Image size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Ảnh đã tải lên ({files.length})</p>\n </div>\n <button\n className=\"flex cursor-pointer items-center gap-1 text-text-positive-muted transition-colors duration-200 hover:text-text-positive\"\n onClick={() => {\n if (!files) return;\n files.forEach(file => {\n if (!isFileWithPreview(file)) return;\n URL.revokeObjectURL(file.preview);\n });\n setFiles([]);\n onValueChange?.([]);\n }}\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n <p className=\"text-sm\">Xóa tất cả</p>\n </button>\n </div>\n <div className=\"flex flex-col gap-2.5\">\n {files?.map((file, index) => (\n <FileCard key={`${index.toString()}`} file={file} onRemove={() => onRemove(index)} progress={progresses?.[file.name]} />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface FileCardProps {\n file: File;\n onRemove: () => void;\n progress?: number;\n}\n\nfunction FileCard({ file, progress, onRemove }: FileCardProps) {\n return (\n <div className=\"relative flex items-center gap-4 rounded border border-border-weak py-2 pr-3 pl-2 shadow-xs transition-colors hover:border-border\">\n <div className=\"flex flex-1 gap-2.5\">\n {isFileWithPreview(file) ? <FilePreview file={file} /> : null}\n <div className=\"flex w-full flex-col gap-2\">\n <div className=\"flex flex-col gap-y-0.5\">\n <p className=\"line-clamp-1 text-sm text-text-positive\">{file.name}</p>\n <p className=\"text-text-positive-muted text-xs\">{formatBytes(file.size)}</p>\n </div>\n {progress ? <Progress value={progress} /> : null}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" color=\"danger\" size=\"icon\" className=\"size-7\" onClick={onRemove}>\n <Trash2 size={16} aria-hidden=\"true\" />\n <span className=\"sr-only\">Remove file</span>\n </Button>\n </div>\n </div>\n );\n}\n\nfunction isFileWithPreview(file: File): file is File & { preview: string } {\n return 'preview' in file && typeof file.preview === 'string';\n}\n\ninterface FilePreviewProps {\n file: File & { preview: string };\n}\n\nconst MIME_TYPE_ICON_MAP: Record<string, React.FC<FileIconProps>> = {\n 'application/pdf': PdfFileIcon,\n 'text/csv': CsvFileIcon,\n 'application/msword': WordFileIcon,\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': WordFileIcon,\n 'application/vnd.ms-excel': ExcelFileIcon,\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ExcelFileIcon,\n 'application/zip': ZipFileIcon,\n 'application/x-zip-compressed': ZipFileIcon,\n};\n\nfunction FilePreview({ file }: FilePreviewProps) {\n if (file.type.startsWith('image/')) {\n return <img src={file.preview} className=\"aspect-square size-10 shrink-0 rounded border border-border object-cover object-top shadow-sm\" />;\n }\n\n if (file.type.startsWith('audio/')) {\n return <AudioFileIcon size={40} className=\"size-10 shrink-0\" aria-hidden=\"true\" />;\n }\n\n if (file.type.startsWith('video/')) {\n return <VideoFileIcon size={40} className=\"size-10 shrink-0\" aria-hidden=\"true\" />;\n }\n\n const Icon = MIME_TYPE_ICON_MAP[file.type] ?? UnknownFileIcon;\n\n return <Icon size={40} className=\"size-10 shrink-0\" aria-hidden=\"true\" />;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as ItemFooter, c as ItemMedia, i as ItemDescription, l as ItemSeparator, n as ItemActions, o as ItemGroup, r as ItemContent, s as ItemHeader, t as Item, u as ItemTitle } from "../item-CtLZgUB6.cjs";
1
+ import { a as ItemFooter, c as ItemMedia, i as ItemDescription, l as ItemSeparator, n as ItemActions, o as ItemGroup, r as ItemContent, s as ItemHeader, t as Item, u as ItemTitle } from "../item-BxUBO7Hd.cjs";
2
2
  export { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle };
@@ -1,2 +1,2 @@
1
- import { a as ItemFooter, c as ItemMedia, i as ItemDescription, l as ItemSeparator, n as ItemActions, o as ItemGroup, r as ItemContent, s as ItemHeader, t as Item, u as ItemTitle } from "../item-Brbp9t6f.mjs";
1
+ import { a as ItemFooter, c as ItemMedia, i as ItemDescription, l as ItemSeparator, n as ItemActions, o as ItemGroup, r as ItemContent, s as ItemHeader, t as Item, u as ItemTitle } from "../item-ChW41yKF.mjs";
2
2
  export { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle };
@@ -1,2 +1,2 @@
1
- import { a as PaginationLink, i as PaginationItem, n as PaginationContent, o as PaginationNext, r as PaginationEllipsis, s as PaginationPrevious, t as Pagination } from "../pagination-DqJlXNY0.cjs";
1
+ import { a as PaginationLink, i as PaginationItem, n as PaginationContent, o as PaginationNext, r as PaginationEllipsis, s as PaginationPrevious, t as Pagination } from "../pagination-AxRzmAtZ.cjs";
2
2
  export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious };
@@ -1,2 +1,2 @@
1
- import { a as PaginationLink, i as PaginationItem, n as PaginationContent, o as PaginationNext, r as PaginationEllipsis, s as PaginationPrevious, t as Pagination } from "../pagination-BVA_yrBO.mjs";
1
+ import { a as PaginationLink, i as PaginationItem, n as PaginationContent, o as PaginationNext, r as PaginationEllipsis, s as PaginationPrevious, t as Pagination } from "../pagination-D12pR8_5.mjs";
2
2
  export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious };
@@ -1,2 +1,2 @@
1
- import { n as ResizablePanel, r as ResizablePanelGroup, t as ResizableHandle } from "../resizable-B61Fbl57.cjs";
1
+ import { n as ResizablePanel, r as ResizablePanelGroup, t as ResizableHandle } from "../resizable-D61lvcIL.cjs";
2
2
  export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
@@ -1,2 +1,2 @@
1
- import { n as ResizablePanel, r as ResizablePanelGroup, t as ResizableHandle } from "../resizable-B61Fbl57.mjs";
1
+ import { n as ResizablePanel, r as ResizablePanelGroup, t as ResizableHandle } from "../resizable-D61lvcIL.mjs";
2
2
  export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
2
2
  //#region packages/components/ui/status.variants.d.ts
3
3
  declare const statusVariants: (props?: ({
4
4
  variant?: "solid" | "soft" | "dot" | "bar" | null | undefined;
5
- color?: "none" | "neutral" | "slate" | "gray" | "zinc" | "stone" | "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink" | "rose" | null | undefined;
5
+ color?: "neutral" | "none" | "slate" | "gray" | "zinc" | "stone" | "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink" | "rose" | null | undefined;
6
6
  size?: "sm" | "md" | "lg" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
8
  type StatusVariants = VariantProps<typeof statusVariants>;
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
2
2
  //#region packages/components/ui/status.variants.d.ts
3
3
  declare const statusVariants: (props?: ({
4
4
  variant?: "solid" | "soft" | "dot" | "bar" | null | undefined;
5
- color?: "none" | "neutral" | "slate" | "gray" | "zinc" | "stone" | "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink" | "rose" | null | undefined;
5
+ color?: "neutral" | "none" | "slate" | "gray" | "zinc" | "stone" | "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink" | "rose" | null | undefined;
6
6
  size?: "sm" | "md" | "lg" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
8
  type StatusVariants = VariantProps<typeof statusVariants>;
@@ -1,2 +1,2 @@
1
- import { n as ToggleGroupItem, t as ToggleGroup } from "../toggle-group-DM5tS0Kt.cjs";
1
+ import { n as ToggleGroupItem, t as ToggleGroup } from "../toggle-group--bJylFDV.cjs";
2
2
  export { ToggleGroup, ToggleGroupItem };
@@ -1,2 +1,2 @@
1
- import { n as ToggleGroupItem, t as ToggleGroup } from "../toggle-group-_PBz44Im.mjs";
1
+ import { n as ToggleGroupItem, t as ToggleGroup } from "../toggle-group-l_Mi7NRg.mjs";
2
2
  export { ToggleGroup, ToggleGroupItem };
@@ -1,2 +1,2 @@
1
- import { n as toggleVariants, t as Toggle } from "../toggle-v1PgAvdG.cjs";
1
+ import { n as toggleVariants, t as Toggle } from "../toggle-CqTmiPDi.cjs";
2
2
  export { Toggle, toggleVariants };
@@ -1,2 +1,2 @@
1
- import { n as toggleVariants, t as Toggle } from "../toggle-v1PgAvdG.mjs";
1
+ import { n as toggleVariants, t as Toggle } from "../toggle-CqTmiPDi.mjs";
2
2
  export { Toggle, toggleVariants };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customafk/lunas-ui",
3
- "version": "0.2.79",
3
+ "version": "0.2.81",
4
4
  "description": "UI library for Lunas Enterprise",
5
5
  "type": "module",
6
6
  "author": {
@@ -112,6 +112,10 @@
112
112
  "require": "./dist/features/descriptions/index.cjs",
113
113
  "import": "./dist/features/descriptions/index.mjs"
114
114
  },
115
+ "./features/file-icons": {
116
+ "require": "./dist/features/file-icons/index.cjs",
117
+ "import": "./dist/features/file-icons/index.mjs"
118
+ },
115
119
  "./features/logo": {
116
120
  "require": "./dist/features/logo/index.cjs",
117
121
  "import": "./dist/features/logo/index.mjs"
@@ -1,2 +0,0 @@
1
- import{Skeleton as e}from"./ui/skeleton.mjs";import{Flex as t}from"./layouts/flex.mjs";import{t as n}from"./date-Nsz0Pewe.mjs";import{TooltipProvider as r}from"./ui/tooltip.mjs";import{Title as i}from"./typography/title.mjs";import{_ as a,a as o,d as s,f as c,i as l,m as u,n as d,o as f,p,r as m,s as h,t as g}from"./sidebar-core-Bz_LtXZy.mjs";import{LunasLogo as _}from"./features/logo/index.mjs";import{CalendarIcon as v,XIcon as y}from"lucide-react";import{Suspense as b,useCallback as x}from"react";import{cn as S}from"@customafk/react-toolkit/utils";import{jsx as C,jsxs as w}from"react/jsx-runtime";import{Dialog as T}from"radix-ui";function E(){return C(t,{justify:`center`,className:`inset-shadow-sm min-h-0 bg-muted-muted`,children:C(`div`,{className:`loader`})})}function D({isLoading:e,children:t}){return C(`main`,{"data-slot":`detail-dialog-main`,className:`@container col-start-2 row-start-2 grid min-h-0 min-w-0 grid-rows-1`,children:e?C(E,{}):C(b,{fallback:C(E,{}),children:C(`section`,{"data-slot":`detail-dialog-body`,className:`relative inset-shadow-sm grid min-h-0 snap-y grid-cols-1 overflow-y-auto bg-white`,children:t})})})}function O(){return w(`div`,{"data-slot":`detail-dialog-header-skeleton`,className:`grid justify-items-start gap-1.5`,children:[C(e,{className:`h-5 w-40`}),C(e,{className:`h-3 w-24`})]})}function k({title:e,createdAt:t,headerComponent:r}){return w(`header`,{"data-slot":`detail-dialog-header`,className:`col-start-2 row-start-1 grid grid-cols-[auto_1fr] items-start gap-x-2.5 border-border-weak border-b bg-card py-2.5 pr-12 pl-2`,children:[C(h,{}),C(`div`,{"data-slot":`detail-dialog-header-content`,className:`min-w-0 overflow-x-auto`,children:C(b,{fallback:C(O,{}),children:r??w(`div`,{className:`grid justify-items-start gap-0`,children:[C(i,{level:5,className:`line-clamp-1 truncate text-wrap`,children:e||`Detail Dialog`}),!!t&&w(`div`,{className:`relative grid grid-flow-col items-center gap-1 text-text-positive-weak`,children:[C(v,{size:12,className:`text-primary-strong`}),C(`p`,{className:`text-primary-strong text-xs`,children:`Ngày Tạo`}),C(n,{showHoliday:!0,date:t,format:`medium`})]})]})})})]})}function A({defaultOpen:e=!0,open:t,onOpenChange:n,className:i,style:o,children:l,...d}){let f=a({defaultOpen:e,open:t,onOpenChange:n});return C(u.Provider,{value:f,children:C(r,{delayDuration:0,children:C(`div`,{"data-slot":`detail-dialog-wrapper`,style:{"--sidebar-width":s,"--sidebar-width-icon":c,...o},className:S(`group/sidebar-wrapper size-full min-h-[85dvh]`,`grid grid-cols-[auto_1fr] grid-rows-[auto_1fr]`,i),...d,children:l})})})}function j({title:e,sidebarTitle:t,content:n,footer:r}){return C(`div`,{className:`col-start-1 row-span-2 row-start-1`,children:w(p,{children:[C(m,{children:C(l,{children:C(f,{children:w(o,{size:`lg`,tabIndex:-1,children:[C(_,{variant:`icon`,iconStyle:`solid`,size:`md`,className:`size-8 min-h-8 min-w-8`}),w(`div`,{className:`grid flex-1 text-left text-sm leading-tight`,children:[C(`span`,{className:`truncate font-medium`,children:t||`Detail Dialog`}),C(`span`,{className:`truncate text-xs`,children:`Lunas Enterprise`})]})]})})})}),C(g,{children:n}),C(d,{children:r})]})})}function M({open:e,onOpenChange:t,children:n}){let r=x(e=>{e.preventDefault(),e.stopPropagation()},[]);return C(T.Root,{"data-slot":`detail-dialog`,open:e,onOpenChange:t,children:C(T.Portal,{"data-slot":`detail-dialog-portal`,children:C(T.Overlay,{"data-slot":`detail-dialog-overlay`,className:`data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 flex overflow-auto bg-black/50 p-4 data-[state=closed]:animate-out data-[state=open]:animate-in supports-backdrop-filter:backdrop-blur-xs`,children:w(T.Content,{"data-slot":`dialog-content`,className:S(`relative z-50 m-auto grid`,`gap-4 overflow-hidden rounded-sm border-none bg-background p-0 shadow-dialog outline-none duration-200`,`size-full max-h-dvh min-h-160 min-w-264 max-w-svw`,`data-[state=closed]:animate-out data-[state=open]:animate-in`,`data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0`,`data-[state=closed]:zoom-out-80 data-[state=open]:zoom-in-80`),onInteractOutside:r,children:[n,C(T.Close,{"data-slot":`dialog-close`,tabIndex:-1,asChild:!0,className:`absolute top-3 right-3`,children:C(`button`,{className:`flex cursor-pointer items-center justify-center rounded-full p-2 text-text-positive-weak transition-colors hover:bg-muted-muted hover:text-text-positive active:bg-muted-weak active:text-text-positive-strong disabled:pointer-events-none disabled:opacity-60`,children:C(y,{size:24})})})]})})})})}const N=({open:e,isLoading:t,title:n,createdAt:r,onOpenChange:i,sidebar:a,headerComponent:o,children:s})=>C(M,{open:e,onOpenChange:i,children:w(A,{children:[C(j,{title:a?.title,content:a?.content,footer:a?.footer}),C(k,{title:n,createdAt:r,headerComponent:o}),C(D,{isLoading:t,children:s})]})});export{N as t};
2
- //# sourceMappingURL=detail-dialog-Ct0vAjFe.mjs.map