tempest-react-sdk 0.67.0 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +97 -93
  2. package/dist/auth/create-tempest-auth.cjs +1 -1
  3. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  4. package/dist/auth/create-tempest-auth.js +41 -40
  5. package/dist/auth/create-tempest-auth.js.map +1 -1
  6. package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
  7. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  8. package/dist/components/BottomSheet/BottomSheet.js +34 -30
  9. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  10. package/dist/components/Card/Card.module.cjs.map +1 -1
  11. package/dist/components/Card/Card.module.js.map +1 -1
  12. package/dist/components/Combobox/Combobox.cjs +1 -1
  13. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  14. package/dist/components/Combobox/Combobox.js +78 -77
  15. package/dist/components/Combobox/Combobox.js.map +1 -1
  16. package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
  17. package/dist/components/Combobox/Combobox.module.js.map +1 -1
  18. package/dist/components/Command/Command.cjs +1 -1
  19. package/dist/components/Command/Command.js +4 -4
  20. package/dist/components/DataTable/DataTable.cjs +1 -1
  21. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  22. package/dist/components/DataTable/DataTable.js +138 -127
  23. package/dist/components/DataTable/DataTable.js.map +1 -1
  24. package/dist/components/DataTable/use-dev-warnings.cjs +1 -1
  25. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
  26. package/dist/components/DataTable/use-dev-warnings.js +5 -3
  27. package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
  28. package/dist/components/Drawer/Drawer.cjs +1 -1
  29. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  30. package/dist/components/Drawer/Drawer.js +43 -40
  31. package/dist/components/Drawer/Drawer.js.map +1 -1
  32. package/dist/components/FilterPanel/FilterPanel.cjs +2 -0
  33. package/dist/components/FilterPanel/FilterPanel.cjs.map +1 -0
  34. package/dist/components/FilterPanel/FilterPanel.js +75 -0
  35. package/dist/components/FilterPanel/FilterPanel.js.map +1 -0
  36. package/dist/components/FilterPanel/FilterPanel.module.cjs +3 -0
  37. package/dist/components/FilterPanel/FilterPanel.module.cjs.map +1 -0
  38. package/dist/components/FilterPanel/FilterPanel.module.js +12 -0
  39. package/dist/components/FilterPanel/FilterPanel.module.js.map +1 -0
  40. package/dist/components/Lightbox/Lightbox.cjs +1 -1
  41. package/dist/components/Lightbox/Lightbox.js +5 -5
  42. package/dist/components/Modal/Modal.cjs +1 -1
  43. package/dist/components/Modal/Modal.cjs.map +1 -1
  44. package/dist/components/Modal/Modal.js +44 -41
  45. package/dist/components/Modal/Modal.js.map +1 -1
  46. package/dist/components/Pagination/Pagination.cjs +1 -1
  47. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  48. package/dist/components/Pagination/Pagination.js +31 -30
  49. package/dist/components/Pagination/Pagination.js.map +1 -1
  50. package/dist/components/Portal/tab-bridge.cjs +1 -1
  51. package/dist/components/Portal/tab-bridge.cjs.map +1 -1
  52. package/dist/components/Portal/tab-bridge.js +21 -23
  53. package/dist/components/Portal/tab-bridge.js.map +1 -1
  54. package/dist/components/Select/CaretIcon.cjs +2 -0
  55. package/dist/components/Select/CaretIcon.cjs.map +1 -0
  56. package/dist/components/Select/CaretIcon.js +21 -0
  57. package/dist/components/Select/CaretIcon.js.map +1 -0
  58. package/dist/components/Select/Select.cjs +1 -1
  59. package/dist/components/Select/Select.cjs.map +1 -1
  60. package/dist/components/Select/Select.js +33 -47
  61. package/dist/components/Select/Select.js.map +1 -1
  62. package/dist/components/Select/Select.module.cjs +1 -1
  63. package/dist/components/Select/Select.module.cjs.map +1 -1
  64. package/dist/components/Select/Select.module.js +4 -3
  65. package/dist/components/Select/Select.module.js.map +1 -1
  66. package/dist/components/Select/options.cjs +2 -0
  67. package/dist/components/Select/options.cjs.map +1 -0
  68. package/dist/components/Select/options.js +25 -0
  69. package/dist/components/Select/options.js.map +1 -0
  70. package/dist/components/Sidebar/Sidebar.cjs +1 -1
  71. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  72. package/dist/components/Sidebar/Sidebar.js +49 -43
  73. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  74. package/dist/components/Sidebar/active-nav-key.cjs +2 -0
  75. package/dist/components/Sidebar/active-nav-key.cjs.map +1 -0
  76. package/dist/components/Sidebar/active-nav-key.js +22 -0
  77. package/dist/components/Sidebar/active-nav-key.js.map +1 -0
  78. package/dist/hooks/tab-sequence.cjs +2 -0
  79. package/dist/hooks/tab-sequence.cjs.map +1 -0
  80. package/dist/hooks/tab-sequence.js +53 -0
  81. package/dist/hooks/tab-sequence.js.map +1 -0
  82. package/dist/hooks/use-deep-memo.cjs +1 -1
  83. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  84. package/dist/hooks/use-deep-memo.js +1 -1
  85. package/dist/hooks/use-deep-memo.js.map +1 -1
  86. package/dist/hooks/use-draft-filters.cjs +2 -0
  87. package/dist/hooks/use-draft-filters.cjs.map +1 -0
  88. package/dist/hooks/use-draft-filters.js +49 -0
  89. package/dist/hooks/use-draft-filters.js.map +1 -0
  90. package/dist/hooks/use-focus-trap.cjs +1 -1
  91. package/dist/hooks/use-focus-trap.cjs.map +1 -1
  92. package/dist/hooks/use-focus-trap.js +31 -34
  93. package/dist/hooks/use-focus-trap.js.map +1 -1
  94. package/dist/http/api-client.cjs +1 -1
  95. package/dist/http/api-client.cjs.map +1 -1
  96. package/dist/http/api-client.js +92 -80
  97. package/dist/http/api-client.js.map +1 -1
  98. package/dist/http/credential-scope.cjs +1 -1
  99. package/dist/http/credential-scope.cjs.map +1 -1
  100. package/dist/http/credential-scope.js +5 -3
  101. package/dist/http/credential-scope.js.map +1 -1
  102. package/dist/http/csrf.cjs +2 -0
  103. package/dist/http/csrf.cjs.map +1 -0
  104. package/dist/http/csrf.js +46 -0
  105. package/dist/http/csrf.js.map +1 -0
  106. package/dist/http/resumable-upload.cjs +1 -1
  107. package/dist/http/resumable-upload.cjs.map +1 -1
  108. package/dist/http/resumable-upload.js +131 -123
  109. package/dist/http/resumable-upload.js.map +1 -1
  110. package/dist/http/upload-with-progress.cjs +1 -1
  111. package/dist/http/upload-with-progress.cjs.map +1 -1
  112. package/dist/http/upload-with-progress.js +41 -33
  113. package/dist/http/upload-with-progress.js.map +1 -1
  114. package/dist/query/use-paginated-query.cjs +1 -1
  115. package/dist/query/use-paginated-query.cjs.map +1 -1
  116. package/dist/query/use-paginated-query.js +47 -30
  117. package/dist/query/use-paginated-query.js.map +1 -1
  118. package/dist/styles/FilterPanel.css +2 -0
  119. package/dist/styles/component/Card.css +1 -1
  120. package/dist/styles/component/Combobox.css +4 -3
  121. package/dist/styles/component/FilterPanel.css +5 -0
  122. package/dist/styles/component/Select.css +13 -6
  123. package/dist/styles/core.css +4 -4
  124. package/dist/styles/data.css +4 -0
  125. package/dist/styles/forms.css +17 -9
  126. package/dist/styles/identity.css +1 -1
  127. package/dist/styles/manifest.json +6 -0
  128. package/dist/styles/tokens.css +4 -4
  129. package/dist/styles.css +1 -1
  130. package/dist/sw/create-push-handler.cjs +1 -1
  131. package/dist/sw/create-push-handler.cjs.map +1 -1
  132. package/dist/sw/create-push-handler.js +58 -30
  133. package/dist/sw/create-push-handler.js.map +1 -1
  134. package/dist/sw.d.ts +53 -2
  135. package/dist/tempest-react-sdk.cjs +1 -1
  136. package/dist/tempest-react-sdk.d.ts +711 -24
  137. package/dist/tempest-react-sdk.js +347 -342
  138. package/dist/vite/tempest-csp.cjs +3 -0
  139. package/dist/vite/tempest-csp.cjs.map +1 -0
  140. package/dist/vite/tempest-csp.js +189 -0
  141. package/dist/vite/tempest-csp.js.map +1 -0
  142. package/dist/vite.cjs +1 -1
  143. package/dist/vite.d.ts +145 -0
  144. package/dist/vite.js +2 -1
  145. package/package.json +2 -2
  146. package/template/vite.config.ts +8 -1
  147. package/template-pwa/vite.config.ts +6 -0
@@ -1,9 +1,9 @@
1
1
  import { isDevBuild as e } from "../../utils/dev-mode.js";
2
2
  import { useEffect as t } from "react";
3
3
  //#region src/components/DataTable/use-dev-warnings.ts
4
- function n({ serverMode: n, controlledPage: r, onPageChange: i, sortIsManual: a, hasSortableColumn: o, onSortChange: s, searchable: c, onSearchChange: l }) {
4
+ function n({ serverMode: n, controlledPage: r, onPageChange: i, pageSizeControlled: a, onPageSizeChange: o, sortIsManual: s, hasSortableColumn: c, onSortChange: l, searchable: u, onSearchChange: d }) {
5
5
  t(() => {
6
- e() && (n && r === void 0 && console.warn("[tempest] <DataTable totalItems> is server mode, which needs a controlled `page`. Without it the pager moves the internal page while `data` keeps showing page 1."), r !== void 0 && !i && console.warn("[tempest] <DataTable page> is controlled but `onPageChange` is missing, so the pager cannot do anything."), n && c && !l && console.warn("[tempest] <DataTable totalItems searchable> delegates searching, so the box needs `onSearchChange`. Without it the user types and nothing filters and nothing is reported."), a && o && !s && console.warn("[tempest] <DataTable> is sorting manually but `onSortChange` is missing: clicking a sortable header changes the arrow and nothing else."));
6
+ e() && (n && r === void 0 && console.warn("[tempest] <DataTable totalItems> is server mode, which needs a controlled `page`. Without it the pager moves the internal page while `data` keeps showing page 1."), r !== void 0 && !i && console.warn("[tempest] <DataTable page> is controlled but `onPageChange` is missing, so the pager cannot do anything."), o && !a && console.warn("[tempest] <DataTable onPageSizeChange> needs a controlled `pageSize`. Without it the selector reports the new size and the table keeps counting pages by 10."), n && u && !d && console.warn("[tempest] <DataTable totalItems searchable> delegates searching, so the box needs `onSearchChange`. Without it the user types and nothing filters and nothing is reported."), s && c && !l && console.warn("[tempest] <DataTable> is sorting manually but `onSortChange` is missing: clicking a sortable header changes the arrow and nothing else."));
7
7
  }, [
8
8
  n,
9
9
  r,
@@ -12,7 +12,9 @@ function n({ serverMode: n, controlledPage: r, onPageChange: i, sortIsManual: a,
12
12
  o,
13
13
  s,
14
14
  c,
15
- l
15
+ l,
16
+ u,
17
+ d
16
18
  ]);
17
19
  }
18
20
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-dev-warnings.js","names":[],"sources":["../../../src/components/DataTable/use-dev-warnings.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { isDevBuild } from \"../../utils/dev-mode\";\n\n/** What {@link useDevWarnings} needs to judge the prop combination. */\nexport interface DataTableDevWarningsInput {\n /** Whether `totalItems` was passed. */\n serverMode: boolean;\n /** The controlled `page`, if any. */\n controlledPage: number | undefined;\n /** The page callback, if any. */\n onPageChange: ((page: number) => void) | undefined;\n /** Whether sorting is delegated. */\n sortIsManual: boolean;\n /** Whether any column renders a header the user can click to sort. */\n hasSortableColumn: boolean;\n /** The sort callback, if any. */\n onSortChange: ((sort: never) => void) | undefined;\n /** Whether a search box is rendered at all. */\n searchable: boolean;\n /** The search callback, if any. */\n onSearchChange: ((term: string) => void) | undefined;\n}\n\n/**\n * Warn, in development only, about prop combinations that render a table which\n * silently lies.\n *\n * Each of these produces a screen that looks like it works: the header sorts and\n * nothing moves, the pager renders and clicking it does nothing.\n *\n * Three of them **are** type errors now — `DataTablePagingProps`,\n * `DataTableSortProps` and `DataTableSearchProps` reject them at the call site —\n * so those warnings only reach a caller the types cannot: plain JavaScript, or\n * props arriving through an `any`-typed spread.\n *\n * Each one also has to be reachable. `totalItems` implies `manualSort`, so the\n * sort warning fired on every server-mode table — including one whose columns are\n * all plain, where no header is clickable and the sentence it prints (\"clicking a\n * sortable header changes the arrow and nothing else\") describes a state the user\n * cannot reach. A warning nobody can act on is not a warning: it teaches the\n * reader to skim past the two next to it, which *are* real, and the way out it\n * suggests is `onSortChange={() => {}}` — a callback that does nothing, that the\n * type-checker accepts, and that would hide the warning on the day a `sortable`\n * column does appear. So the sort warning asks for a sortable column first, the\n * way the search one already asks for `searchable`.\n *\n * The fourth is the one the types genuinely cannot afford. `totalItems` *implies*\n * `manualSearch`, so `searchable` with no `onSearchChange` is an inert search box\n * in server mode without anybody writing `manualSearch`. Expressing that means\n * the search axis has to read the paging axis, crossing two three-member unions\n * into nine — so it stays here, which is what \"runtime is the cheaper check\"\n * actually looks like when it is true.\n *\n * @param input - The resolved prop combination.\n */\nexport function useDevWarnings({\n serverMode,\n controlledPage,\n onPageChange,\n sortIsManual,\n hasSortableColumn,\n onSortChange,\n searchable,\n onSearchChange,\n}: DataTableDevWarningsInput): void {\n useEffect(() => {\n if (!isDevBuild()) return;\n\n if (serverMode && controlledPage === undefined) {\n console.warn(\n \"[tempest] <DataTable totalItems> is server mode, which needs a controlled `page`. \" +\n \"Without it the pager moves the internal page while `data` keeps showing page 1.\",\n );\n }\n if (controlledPage !== undefined && !onPageChange) {\n console.warn(\n \"[tempest] <DataTable page> is controlled but `onPageChange` is missing, so the pager cannot do anything.\",\n );\n }\n if (serverMode && searchable && !onSearchChange) {\n console.warn(\n \"[tempest] <DataTable totalItems searchable> delegates searching, so the box needs \" +\n \"`onSearchChange`. Without it the user types and nothing filters and nothing is reported.\",\n );\n }\n if (sortIsManual && hasSortableColumn && !onSortChange) {\n console.warn(\n \"[tempest] <DataTable> is sorting manually but `onSortChange` is missing: clicking a sortable header changes the arrow and nothing else.\",\n );\n }\n }, [\n serverMode,\n controlledPage,\n onPageChange,\n sortIsManual,\n hasSortableColumn,\n onSortChange,\n searchable,\n onSearchChange,\n ]);\n}\n"],"mappings":";;;AAwDA,SAAgB,EAAe,EAC3B,eACA,mBACA,iBACA,iBACA,sBACA,iBACA,eACA,qBACgC;CAChC,QAAgB;EACP,EAAW,MAEZ,KAAc,MAAmB,KAAA,KACjC,QAAQ,KACJ,mKAEJ,GAEA,MAAmB,KAAA,KAAa,CAAC,KACjC,QAAQ,KACJ,0GACJ,GAEA,KAAc,KAAc,CAAC,KAC7B,QAAQ,KACJ,4KAEJ,GAEA,KAAgB,KAAqB,CAAC,KACtC,QAAQ,KACJ,yIACJ;CAER,GAAG;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACJ,CAAC;AACL"}
1
+ {"version":3,"file":"use-dev-warnings.js","names":[],"sources":["../../../src/components/DataTable/use-dev-warnings.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { isDevBuild } from \"../../utils/dev-mode\";\n\n/** What {@link useDevWarnings} needs to judge the prop combination. */\nexport interface DataTableDevWarningsInput {\n /** Whether `totalItems` was passed. */\n serverMode: boolean;\n /** The controlled `page`, if any. */\n controlledPage: number | undefined;\n /** The page callback, if any. */\n onPageChange: ((page: number) => void) | undefined;\n /** Whether the caller passed `pageSize` at all. */\n pageSizeControlled: boolean;\n /** The size-selector callback, if any. */\n onPageSizeChange: ((size: number) => void) | undefined;\n /** Whether sorting is delegated. */\n sortIsManual: boolean;\n /** Whether any column renders a header the user can click to sort. */\n hasSortableColumn: boolean;\n /** The sort callback, if any. */\n onSortChange: ((sort: never) => void) | undefined;\n /** Whether a search box is rendered at all. */\n searchable: boolean;\n /** The search callback, if any. */\n onSearchChange: ((term: string) => void) | undefined;\n}\n\n/**\n * Warn, in development only, about prop combinations that render a table which\n * silently lies.\n *\n * Each of these produces a screen that looks like it works: the header sorts and\n * nothing moves, the pager renders and clicking it does nothing.\n *\n * Four of them **are** type errors now — `DataTablePagingProps`,\n * `DataTablePageSizeProps`, `DataTableSortProps` and `DataTableSearchProps` reject\n * them at the call site —\n * so those warnings only reach a caller the types cannot: plain JavaScript, or\n * props arriving through an `any`-typed spread.\n *\n * Each one also has to be reachable. `totalItems` implies `manualSort`, so the\n * sort warning fired on every server-mode table — including one whose columns are\n * all plain, where no header is clickable and the sentence it prints (\"clicking a\n * sortable header changes the arrow and nothing else\") describes a state the user\n * cannot reach. A warning nobody can act on is not a warning: it teaches the\n * reader to skim past the two next to it, which *are* real, and the way out it\n * suggests is `onSortChange={() => {}}` — a callback that does nothing, that the\n * type-checker accepts, and that would hide the warning on the day a `sortable`\n * column does appear. So the sort warning asks for a sortable column first, the\n * way the search one already asks for `searchable`.\n *\n * The server-mode search warning is the one the types genuinely cannot afford. `totalItems` *implies*\n * `manualSearch`, so `searchable` with no `onSearchChange` is an inert search box\n * in server mode without anybody writing `manualSearch`. Expressing that means\n * the search axis has to read the paging axis, crossing two three-member unions\n * into nine — so it stays here, which is what \"runtime is the cheaper check\"\n * actually looks like when it is true.\n *\n * @param input - The resolved prop combination.\n */\nexport function useDevWarnings({\n serverMode,\n controlledPage,\n onPageChange,\n pageSizeControlled,\n onPageSizeChange,\n sortIsManual,\n hasSortableColumn,\n onSortChange,\n searchable,\n onSearchChange,\n}: DataTableDevWarningsInput): void {\n useEffect(() => {\n if (!isDevBuild()) return;\n\n if (serverMode && controlledPage === undefined) {\n console.warn(\n \"[tempest] <DataTable totalItems> is server mode, which needs a controlled `page`. \" +\n \"Without it the pager moves the internal page while `data` keeps showing page 1.\",\n );\n }\n if (controlledPage !== undefined && !onPageChange) {\n console.warn(\n \"[tempest] <DataTable page> is controlled but `onPageChange` is missing, so the pager cannot do anything.\",\n );\n }\n if (onPageSizeChange && !pageSizeControlled) {\n console.warn(\n \"[tempest] <DataTable onPageSizeChange> needs a controlled `pageSize`. Without it the \" +\n \"selector reports the new size and the table keeps counting pages by 10.\",\n );\n }\n if (serverMode && searchable && !onSearchChange) {\n console.warn(\n \"[tempest] <DataTable totalItems searchable> delegates searching, so the box needs \" +\n \"`onSearchChange`. Without it the user types and nothing filters and nothing is reported.\",\n );\n }\n if (sortIsManual && hasSortableColumn && !onSortChange) {\n console.warn(\n \"[tempest] <DataTable> is sorting manually but `onSortChange` is missing: clicking a sortable header changes the arrow and nothing else.\",\n );\n }\n }, [\n serverMode,\n controlledPage,\n onPageChange,\n pageSizeControlled,\n onPageSizeChange,\n sortIsManual,\n hasSortableColumn,\n onSortChange,\n searchable,\n onSearchChange,\n ]);\n}\n"],"mappings":";;;AA6DA,SAAgB,EAAe,EAC3B,eACA,mBACA,iBACA,uBACA,qBACA,iBACA,sBACA,iBACA,eACA,qBACgC;CAChC,QAAgB;EACP,EAAW,MAEZ,KAAc,MAAmB,KAAA,KACjC,QAAQ,KACJ,mKAEJ,GAEA,MAAmB,KAAA,KAAa,CAAC,KACjC,QAAQ,KACJ,0GACJ,GAEA,KAAoB,CAAC,KACrB,QAAQ,KACJ,8JAEJ,GAEA,KAAc,KAAc,CAAC,KAC7B,QAAQ,KACJ,4KAEJ,GAEA,KAAgB,KAAqB,CAAC,KACtC,QAAQ,KACJ,yIACJ;CAER,GAAG;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACJ,CAAC;AACL"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-breakpoint.cjs"),n=require("../Portal/escape-layer.cjs"),r=require("../Portal/portal-host.cjs"),i=require("./Drawer.module.cjs");let a=require("react"),o=require("react/jsx-runtime"),s=require("react-dom");function c({open:c,onClose:u,placement:d=`right`,title:f,children:p,footer:m,closeOnBackdrop:h=!0,closeOnEsc:g=!0,hideCloseButton:_=!1,className:v,mobilePlacement:y,showHandle:b=!1}){let{isMobile:x}=t.useBreakpoint(),S=x&&y?y:d;n.useEscapeLayer(c,g?u:null),(0,a.useEffect)(()=>{if(!c)return;let e=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=e}},[c,g,u]);let C=r.usePortalHost();return!c||!C?null:(0,s.createPortal)((0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(`div`,{className:i.default.overlay,onClick:()=>{h&&u()}}),(0,o.jsxs)(`aside`,{role:`dialog`,"aria-modal":`true`,className:e.cn(i.default.panel,i.default[S],v),children:[b&&(S===`bottom`||S===`top`)&&(0,o.jsx)(`div`,{className:e.cn(i.default.handle,S===`top`&&i.default.handleTop),"aria-hidden":!0}),(f||!_)&&(0,o.jsxs)(`header`,{className:i.default.header,children:[(0,o.jsx)(`h3`,{className:i.default.title,children:f}),!_&&(0,o.jsx)(`button`,{type:`button`,"aria-label":`Fechar`,className:i.default.close,onClick:u,children:(0,o.jsx)(l,{})})]}),(0,o.jsx)(`div`,{className:i.default.body,children:p}),m&&(0,o.jsx)(`footer`,{className:i.default.footer,children:m})]})]}),C)}function l(){return(0,o.jsx)(`svg`,{width:`18`,height:`18`,viewBox:`0 0 24 24`,fill:`none`,children:(0,o.jsx)(`path`,{d:`M6 6l12 12M6 18L18 6`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`})})}exports.Drawer=c;
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-breakpoint.cjs"),n=require("../Portal/escape-layer.cjs"),r=require("../../hooks/use-focus-trap.cjs"),i=require("../Portal/portal-host.cjs"),a=require("./Drawer.module.cjs");let o=require("react"),s=require("react/jsx-runtime"),c=require("react-dom");function l({open:l,onClose:d,placement:f=`right`,title:p,children:m,footer:h,closeOnBackdrop:g=!0,closeOnEsc:_=!0,hideCloseButton:v=!1,className:y,mobilePlacement:b,showHandle:x=!1}){let{isMobile:S}=t.useBreakpoint(),C=S&&b?b:f,w=(0,o.useRef)(null);r.useFocusTrap(w,l),n.useEscapeLayer(l,_?d:null),(0,o.useEffect)(()=>{if(!l)return;let e=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=e}},[l,_,d]);let T=i.usePortalHost();return!l||!T?null:(0,c.createPortal)((0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(`div`,{className:a.default.overlay,onClick:()=>{g&&d()}}),(0,s.jsxs)(`aside`,{ref:w,role:`dialog`,"aria-modal":`true`,tabIndex:-1,className:e.cn(a.default.panel,a.default[C],y),children:[x&&(C===`bottom`||C===`top`)&&(0,s.jsx)(`div`,{className:e.cn(a.default.handle,C===`top`&&a.default.handleTop),"aria-hidden":!0}),(p||!v)&&(0,s.jsxs)(`header`,{className:a.default.header,children:[(0,s.jsx)(`h3`,{className:a.default.title,children:p}),!v&&(0,s.jsx)(`button`,{type:`button`,"aria-label":`Fechar`,className:a.default.close,onClick:d,children:(0,s.jsx)(u,{})})]}),(0,s.jsx)(`div`,{className:a.default.body,children:m}),h&&(0,s.jsx)(`footer`,{className:a.default.footer,children:h})]})]}),T)}function u(){return(0,s.jsx)(`svg`,{width:`18`,height:`18`,viewBox:`0 0 24 24`,fill:`none`,children:(0,s.jsx)(`path`,{d:`M6 6l12 12M6 18L18 6`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`})})}exports.Drawer=l;
2
2
  //# sourceMappingURL=Drawer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.cjs","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — placement plus mobilePlacement is\n * the whole point — a drawer that comes from the side on desktop and from the bottom\n * on a phone — and the rest is the dialog contract the SDK's Modal also has (open,\n * onClose, title, children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton)\n * plus showHandle for the sheet affordance.\n */\nimport { useEffect } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./Drawer.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type DrawerPlacement = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n placement?: DrawerPlacement;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n hideCloseButton?: boolean;\n className?: string;\n /**\n * Auto-switch to bottom-sheet placement on mobile viewports (< md).\n * Modern mobile apps default to bottom drawers; on desktop the original\n * `placement` is preserved.\n */\n mobilePlacement?: DrawerPlacement;\n /** Render a drag handle indicator at the leading edge (bottom-sheet style). */\n showHandle?: boolean;\n}\n\n/**\n * Sliding side panel. Same building blocks as {@link Modal} but anchored to\n * an edge. Locks body scroll while open.\n */\nexport function Drawer({\n open,\n onClose,\n placement = \"right\",\n title,\n children,\n footer,\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideCloseButton = false,\n className,\n mobilePlacement,\n showHandle = false,\n}: DrawerProps) {\n const { isMobile } = useBreakpoint();\n const effectivePlacement: DrawerPlacement =\n isMobile && mobilePlacement ? mobilePlacement : placement;\n\n useEscapeLayer(open, closeOnEsc ? onClose : null);\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [open, closeOnEsc, onClose]);\n\n const portalHost = usePortalHost();\n\n if (!open || !portalHost) return null;\n\n return createPortal(\n <>\n <div\n className={styles.overlay}\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n />\n <aside\n role=\"dialog\"\n aria-modal=\"true\"\n className={cn(styles.panel, styles[effectivePlacement], className)}\n >\n {showHandle &&\n (effectivePlacement === \"bottom\" || effectivePlacement === \"top\") && (\n <div\n className={cn(\n styles.handle,\n effectivePlacement === \"top\" && styles.handleTop,\n )}\n aria-hidden\n />\n )}\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n <h3 className={styles.title}>{title}</h3>\n {!hideCloseButton && (\n <button\n type=\"button\"\n aria-label=\"Fechar\"\n className={styles.close}\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </aside>\n </>,\n portalHost,\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"+QA2CA,SAAgB,EAAO,CACnB,OACA,UACA,YAAY,QACZ,QACA,WACA,SACA,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,YACA,kBACA,aAAa,IACD,CACZ,GAAM,CAAE,YAAa,EAAA,cAAc,EAC7B,EACF,GAAY,EAAkB,EAAkB,EAEpD,EAAA,eAAe,EAAM,EAAa,EAAU,IAAI,GAChD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAM,OACX,IAAM,EAAmB,SAAS,KAAK,MAAM,SAE7C,MADA,UAAS,KAAK,MAAM,SAAW,aAClB,CACT,SAAS,KAAK,MAAM,SAAW,CACnC,CACJ,EAAG,CAAC,EAAM,EAAY,CAAO,CAAC,EAE9B,IAAM,EAAa,EAAA,cAAc,EAIjC,MAFI,CAAC,GAAQ,CAAC,EAAmB,MAEjC,EAAO,EAAA,aAAA,EACH,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,QAClB,YAAe,CACP,GAAiB,EAAQ,CACjC,CACH,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,KAAK,SACL,aAAW,OACX,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,GAAqB,CAAS,EAHrE,SAAA,CAKK,IACI,IAAuB,UAAY,IAAuB,SACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,OACP,IAAuB,OAAS,EAAA,QAAO,SAC3C,EACA,cAAA,EACH,CAAA,GAEP,GAAS,CAAC,KACR,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAA1B,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAU,CAAA,EACvC,CAAC,IACE,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,aAAW,SACX,UAAW,EAAA,QAAO,MAClB,QAAS,EAET,UAAA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAER,KAEZ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAC3C,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,CAC1D,CACT,CAAA,CAAA,CAAA,CAAA,EACF,CACJ,CACJ,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}
1
+ {"version":3,"file":"Drawer.cjs","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — placement plus mobilePlacement is\n * the whole point — a drawer that comes from the side on desktop and from the bottom\n * on a phone — and the rest is the dialog contract the SDK's Modal also has (open,\n * onClose, title, children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton)\n * plus showHandle for the sheet affordance.\n */\nimport { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport styles from \"./Drawer.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type DrawerPlacement = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n placement?: DrawerPlacement;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n hideCloseButton?: boolean;\n className?: string;\n /**\n * Auto-switch to bottom-sheet placement on mobile viewports (< md).\n * Modern mobile apps default to bottom drawers; on desktop the original\n * `placement` is preserved.\n */\n mobilePlacement?: DrawerPlacement;\n /** Render a drag handle indicator at the leading edge (bottom-sheet style). */\n showHandle?: boolean;\n}\n\n/**\n * Sliding side panel. Same building blocks as {@link Modal} but anchored to\n * an edge. Locks body scroll while open, and traps keyboard focus in the panel\n * the same way `Modal` does: focus enters on open, `Tab` cycles inside (portalled\n * panels opened from it included) and returns to the opener on close.\n */\nexport function Drawer({\n open,\n onClose,\n placement = \"right\",\n title,\n children,\n footer,\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideCloseButton = false,\n className,\n mobilePlacement,\n showHandle = false,\n}: DrawerProps) {\n const { isMobile } = useBreakpoint();\n const effectivePlacement: DrawerPlacement =\n isMobile && mobilePlacement ? mobilePlacement : placement;\n\n const panelRef = useRef<HTMLElement>(null);\n useFocusTrap(panelRef, open);\n useEscapeLayer(open, closeOnEsc ? onClose : null);\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [open, closeOnEsc, onClose]);\n\n const portalHost = usePortalHost();\n\n if (!open || !portalHost) return null;\n\n return createPortal(\n <>\n <div\n className={styles.overlay}\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n />\n <aside\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n tabIndex={-1}\n className={cn(styles.panel, styles[effectivePlacement], className)}\n >\n {showHandle &&\n (effectivePlacement === \"bottom\" || effectivePlacement === \"top\") && (\n <div\n className={cn(\n styles.handle,\n effectivePlacement === \"top\" && styles.handleTop,\n )}\n aria-hidden\n />\n )}\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n <h3 className={styles.title}>{title}</h3>\n {!hideCloseButton && (\n <button\n type=\"button\"\n aria-label=\"Fechar\"\n className={styles.close}\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </aside>\n </>,\n portalHost,\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"2TA8CA,SAAgB,EAAO,CACnB,OACA,UACA,YAAY,QACZ,QACA,WACA,SACA,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,YACA,kBACA,aAAa,IACD,CACZ,GAAM,CAAE,YAAa,EAAA,cAAc,EAC7B,EACF,GAAY,EAAkB,EAAkB,EAE9C,GAAA,EAAW,EAAA,OAAA,CAAoB,IAAI,EACzC,EAAA,aAAa,EAAU,CAAI,EAC3B,EAAA,eAAe,EAAM,EAAa,EAAU,IAAI,GAChD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAM,OACX,IAAM,EAAmB,SAAS,KAAK,MAAM,SAE7C,MADA,UAAS,KAAK,MAAM,SAAW,aAClB,CACT,SAAS,KAAK,MAAM,SAAW,CACnC,CACJ,EAAG,CAAC,EAAM,EAAY,CAAO,CAAC,EAE9B,IAAM,EAAa,EAAA,cAAc,EAIjC,MAFI,CAAC,GAAQ,CAAC,EAAmB,MAEjC,EAAO,EAAA,aAAA,EACH,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,QAClB,YAAe,CACP,GAAiB,EAAQ,CACjC,CACH,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,IAAK,EACL,KAAK,SACL,aAAW,OACX,SAAU,GACV,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,GAAqB,CAAS,EALrE,SAAA,CAOK,IACI,IAAuB,UAAY,IAAuB,SACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,OACP,IAAuB,OAAS,EAAA,QAAO,SAC3C,EACA,cAAA,EACH,CAAA,GAEP,GAAS,CAAC,KACR,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAA1B,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAU,CAAA,EACvC,CAAC,IACE,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,aAAW,SACX,UAAW,EAAA,QAAO,MAClB,QAAS,EAET,UAAA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAER,KAEZ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAC3C,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,CAC1D,CACT,CAAA,CAAA,CAAA,CAAA,EACF,CACJ,CACJ,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}
@@ -1,71 +1,74 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
2
  import { useBreakpoint as t } from "../../hooks/use-breakpoint.js";
3
3
  import { useEscapeLayer as n } from "../Portal/escape-layer.js";
4
- import { usePortalHost as r } from "../Portal/portal-host.js";
5
- import i from "./Drawer.module.js";
6
- import { useEffect as a } from "react";
7
- import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
8
- import { createPortal as l } from "react-dom";
4
+ import { useFocusTrap as r } from "../../hooks/use-focus-trap.js";
5
+ import { usePortalHost as i } from "../Portal/portal-host.js";
6
+ import a from "./Drawer.module.js";
7
+ import { useEffect as o, useRef as s } from "react";
8
+ import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
9
+ import { createPortal as d } from "react-dom";
9
10
  //#region src/components/Drawer/Drawer.tsx
10
- function u({ open: u, onClose: f, placement: p = "right", title: m, children: h, footer: g, closeOnBackdrop: _ = !0, closeOnEsc: v = !0, hideCloseButton: y = !1, className: b, mobilePlacement: x, showHandle: S = !1 }) {
11
- let { isMobile: C } = t(), w = C && x ? x : p;
12
- n(u, v ? f : null), a(() => {
13
- if (!u) return;
11
+ function f({ open: f, onClose: m, placement: h = "right", title: g, children: _, footer: v, closeOnBackdrop: y = !0, closeOnEsc: b = !0, hideCloseButton: x = !1, className: S, mobilePlacement: C, showHandle: w = !1 }) {
12
+ let { isMobile: T } = t(), E = T && C ? C : h, D = s(null);
13
+ r(D, f), n(f, b ? m : null), o(() => {
14
+ if (!f) return;
14
15
  let e = document.body.style.overflow;
15
16
  return document.body.style.overflow = "hidden", () => {
16
17
  document.body.style.overflow = e;
17
18
  };
18
19
  }, [
19
- u,
20
- v,
21
- f
20
+ f,
21
+ b,
22
+ m
22
23
  ]);
23
- let T = r();
24
- return !u || !T ? null : l(/* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("div", {
25
- className: i.overlay,
24
+ let O = i();
25
+ return !f || !O ? null : d(/* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l("div", {
26
+ className: a.overlay,
26
27
  onClick: () => {
27
- _ && f();
28
+ y && m();
28
29
  }
29
- }), /* @__PURE__ */ c("aside", {
30
+ }), /* @__PURE__ */ u("aside", {
31
+ ref: D,
30
32
  role: "dialog",
31
33
  "aria-modal": "true",
32
- className: e(i.panel, i[w], b),
34
+ tabIndex: -1,
35
+ className: e(a.panel, a[E], S),
33
36
  children: [
34
- S && (w === "bottom" || w === "top") && /* @__PURE__ */ s("div", {
35
- className: e(i.handle, w === "top" && i.handleTop),
37
+ w && (E === "bottom" || E === "top") && /* @__PURE__ */ l("div", {
38
+ className: e(a.handle, E === "top" && a.handleTop),
36
39
  "aria-hidden": !0
37
40
  }),
38
- (m || !y) && /* @__PURE__ */ c("header", {
39
- className: i.header,
40
- children: [/* @__PURE__ */ s("h3", {
41
- className: i.title,
42
- children: m
43
- }), !y && /* @__PURE__ */ s("button", {
41
+ (g || !x) && /* @__PURE__ */ u("header", {
42
+ className: a.header,
43
+ children: [/* @__PURE__ */ l("h3", {
44
+ className: a.title,
45
+ children: g
46
+ }), !x && /* @__PURE__ */ l("button", {
44
47
  type: "button",
45
48
  "aria-label": "Fechar",
46
- className: i.close,
47
- onClick: f,
48
- children: /* @__PURE__ */ s(d, {})
49
+ className: a.close,
50
+ onClick: m,
51
+ children: /* @__PURE__ */ l(p, {})
49
52
  })]
50
53
  }),
51
- /* @__PURE__ */ s("div", {
52
- className: i.body,
53
- children: h
54
+ /* @__PURE__ */ l("div", {
55
+ className: a.body,
56
+ children: _
54
57
  }),
55
- g && /* @__PURE__ */ s("footer", {
56
- className: i.footer,
57
- children: g
58
+ v && /* @__PURE__ */ l("footer", {
59
+ className: a.footer,
60
+ children: v
58
61
  })
59
62
  ]
60
- })] }), T);
63
+ })] }), O);
61
64
  }
62
- function d() {
63
- return /* @__PURE__ */ s("svg", {
65
+ function p() {
66
+ return /* @__PURE__ */ l("svg", {
64
67
  width: "18",
65
68
  height: "18",
66
69
  viewBox: "0 0 24 24",
67
70
  fill: "none",
68
- children: /* @__PURE__ */ s("path", {
71
+ children: /* @__PURE__ */ l("path", {
69
72
  d: "M6 6l12 12M6 18L18 6",
70
73
  stroke: "currentColor",
71
74
  strokeWidth: "2",
@@ -74,6 +77,6 @@ function d() {
74
77
  });
75
78
  }
76
79
  //#endregion
77
- export { u as Drawer };
80
+ export { f as Drawer };
78
81
 
79
82
  //# sourceMappingURL=Drawer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.js","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — placement plus mobilePlacement is\n * the whole point — a drawer that comes from the side on desktop and from the bottom\n * on a phone — and the rest is the dialog contract the SDK's Modal also has (open,\n * onClose, title, children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton)\n * plus showHandle for the sheet affordance.\n */\nimport { useEffect } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport styles from \"./Drawer.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type DrawerPlacement = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n placement?: DrawerPlacement;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n hideCloseButton?: boolean;\n className?: string;\n /**\n * Auto-switch to bottom-sheet placement on mobile viewports (< md).\n * Modern mobile apps default to bottom drawers; on desktop the original\n * `placement` is preserved.\n */\n mobilePlacement?: DrawerPlacement;\n /** Render a drag handle indicator at the leading edge (bottom-sheet style). */\n showHandle?: boolean;\n}\n\n/**\n * Sliding side panel. Same building blocks as {@link Modal} but anchored to\n * an edge. Locks body scroll while open.\n */\nexport function Drawer({\n open,\n onClose,\n placement = \"right\",\n title,\n children,\n footer,\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideCloseButton = false,\n className,\n mobilePlacement,\n showHandle = false,\n}: DrawerProps) {\n const { isMobile } = useBreakpoint();\n const effectivePlacement: DrawerPlacement =\n isMobile && mobilePlacement ? mobilePlacement : placement;\n\n useEscapeLayer(open, closeOnEsc ? onClose : null);\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [open, closeOnEsc, onClose]);\n\n const portalHost = usePortalHost();\n\n if (!open || !portalHost) return null;\n\n return createPortal(\n <>\n <div\n className={styles.overlay}\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n />\n <aside\n role=\"dialog\"\n aria-modal=\"true\"\n className={cn(styles.panel, styles[effectivePlacement], className)}\n >\n {showHandle &&\n (effectivePlacement === \"bottom\" || effectivePlacement === \"top\") && (\n <div\n className={cn(\n styles.handle,\n effectivePlacement === \"top\" && styles.handleTop,\n )}\n aria-hidden\n />\n )}\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n <h3 className={styles.title}>{title}</h3>\n {!hideCloseButton && (\n <button\n type=\"button\"\n aria-label=\"Fechar\"\n className={styles.close}\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </aside>\n </>,\n portalHost,\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;;;;;;AA2CA,SAAgB,EAAO,EACnB,SACA,YACA,eAAY,SACZ,UACA,aACA,WACA,qBAAkB,IAClB,gBAAa,IACb,qBAAkB,IAClB,cACA,oBACA,gBAAa,MACD;CACZ,IAAM,EAAE,gBAAa,EAAc,GAC7B,IACF,KAAY,IAAkB,IAAkB;CAGpD,AADA,EAAe,GAAM,IAAa,IAAU,IAAI,GAChD,QAAgB;EACZ,IAAI,CAAC,GAAM;EACX,IAAM,IAAmB,SAAS,KAAK,MAAM;EAE7C,OADA,SAAS,KAAK,MAAM,WAAW,gBAClB;GACT,SAAS,KAAK,MAAM,WAAW;EACnC;CACJ,GAAG;EAAC;EAAM;EAAY;CAAO,CAAC;CAE9B,IAAM,IAAa,EAAc;CAIjC,OAFI,CAAC,KAAQ,CAAC,IAAmB,OAE1B,EACH,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,OAAD;EACI,WAAW,EAAO;EAClB,eAAe;GACX,AAAI,KAAiB,EAAQ;EACjC;CACH,CAAA,GACD,kBAAC,SAAD;EACI,MAAK;EACL,cAAW;EACX,WAAW,EAAG,EAAO,OAAO,EAAO,IAAqB,CAAS;EAHrE,UAAA;GAKK,MACI,MAAuB,YAAY,MAAuB,UACvD,kBAAC,OAAD;IACI,WAAW,EACP,EAAO,QACP,MAAuB,SAAS,EAAO,SAC3C;IACA,eAAA;GACH,CAAA;IAEP,KAAS,CAAC,MACR,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAA1B,UAAA,CACI,kBAAC,MAAD;KAAI,WAAW,EAAO;KAAQ,UAAA;IAAU,CAAA,GACvC,CAAC,KACE,kBAAC,UAAD;KACI,MAAK;KACL,cAAW;KACX,WAAW,EAAO;KAClB,SAAS;KAET,UAAA,kBAAC,GAAD,CAAY,CAAA;IACR,CAAA,CAER;;GAEZ,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO;GAAc,CAAA;GAC3C,KAAU,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAAS,UAAA;GAAe,CAAA;EAC1D;CACT,CAAA,CAAA,EAAA,CAAA,GACF,CACJ;AACJ;AAEA,SAAS,IAAY;CACjB,OACI,kBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;EACjD,UAAA,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACjB,CAAA;CACA,CAAA;AAEb"}
1
+ {"version":3,"file":"Drawer.js","names":[],"sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — placement plus mobilePlacement is\n * the whole point — a drawer that comes from the side on desktop and from the bottom\n * on a phone — and the rest is the dialog contract the SDK's Modal also has (open,\n * onClose, title, children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton)\n * plus showHandle for the sheet affordance.\n */\nimport { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport styles from \"./Drawer.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type DrawerPlacement = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n placement?: DrawerPlacement;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n hideCloseButton?: boolean;\n className?: string;\n /**\n * Auto-switch to bottom-sheet placement on mobile viewports (< md).\n * Modern mobile apps default to bottom drawers; on desktop the original\n * `placement` is preserved.\n */\n mobilePlacement?: DrawerPlacement;\n /** Render a drag handle indicator at the leading edge (bottom-sheet style). */\n showHandle?: boolean;\n}\n\n/**\n * Sliding side panel. Same building blocks as {@link Modal} but anchored to\n * an edge. Locks body scroll while open, and traps keyboard focus in the panel\n * the same way `Modal` does: focus enters on open, `Tab` cycles inside (portalled\n * panels opened from it included) and returns to the opener on close.\n */\nexport function Drawer({\n open,\n onClose,\n placement = \"right\",\n title,\n children,\n footer,\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideCloseButton = false,\n className,\n mobilePlacement,\n showHandle = false,\n}: DrawerProps) {\n const { isMobile } = useBreakpoint();\n const effectivePlacement: DrawerPlacement =\n isMobile && mobilePlacement ? mobilePlacement : placement;\n\n const panelRef = useRef<HTMLElement>(null);\n useFocusTrap(panelRef, open);\n useEscapeLayer(open, closeOnEsc ? onClose : null);\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [open, closeOnEsc, onClose]);\n\n const portalHost = usePortalHost();\n\n if (!open || !portalHost) return null;\n\n return createPortal(\n <>\n <div\n className={styles.overlay}\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n />\n <aside\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n tabIndex={-1}\n className={cn(styles.panel, styles[effectivePlacement], className)}\n >\n {showHandle &&\n (effectivePlacement === \"bottom\" || effectivePlacement === \"top\") && (\n <div\n className={cn(\n styles.handle,\n effectivePlacement === \"top\" && styles.handleTop,\n )}\n aria-hidden\n />\n )}\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n <h3 className={styles.title}>{title}</h3>\n {!hideCloseButton && (\n <button\n type=\"button\"\n aria-label=\"Fechar\"\n className={styles.close}\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </aside>\n </>,\n portalHost,\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":";;;;;;;;;;AA8CA,SAAgB,EAAO,EACnB,SACA,YACA,eAAY,SACZ,UACA,aACA,WACA,qBAAkB,IAClB,gBAAa,IACb,qBAAkB,IAClB,cACA,oBACA,gBAAa,MACD;CACZ,IAAM,EAAE,gBAAa,EAAc,GAC7B,IACF,KAAY,IAAkB,IAAkB,GAE9C,IAAW,EAAoB,IAAI;CAGzC,AAFA,EAAa,GAAU,CAAI,GAC3B,EAAe,GAAM,IAAa,IAAU,IAAI,GAChD,QAAgB;EACZ,IAAI,CAAC,GAAM;EACX,IAAM,IAAmB,SAAS,KAAK,MAAM;EAE7C,OADA,SAAS,KAAK,MAAM,WAAW,gBAClB;GACT,SAAS,KAAK,MAAM,WAAW;EACnC;CACJ,GAAG;EAAC;EAAM;EAAY;CAAO,CAAC;CAE9B,IAAM,IAAa,EAAc;CAIjC,OAFI,CAAC,KAAQ,CAAC,IAAmB,OAE1B,EACH,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,OAAD;EACI,WAAW,EAAO;EAClB,eAAe;GACX,AAAI,KAAiB,EAAQ;EACjC;CACH,CAAA,GACD,kBAAC,SAAD;EACI,KAAK;EACL,MAAK;EACL,cAAW;EACX,UAAU;EACV,WAAW,EAAG,EAAO,OAAO,EAAO,IAAqB,CAAS;EALrE,UAAA;GAOK,MACI,MAAuB,YAAY,MAAuB,UACvD,kBAAC,OAAD;IACI,WAAW,EACP,EAAO,QACP,MAAuB,SAAS,EAAO,SAC3C;IACA,eAAA;GACH,CAAA;IAEP,KAAS,CAAC,MACR,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAA1B,UAAA,CACI,kBAAC,MAAD;KAAI,WAAW,EAAO;KAAQ,UAAA;IAAU,CAAA,GACvC,CAAC,KACE,kBAAC,UAAD;KACI,MAAK;KACL,cAAW;KACX,WAAW,EAAO;KAClB,SAAS;KAET,UAAA,kBAAC,GAAD,CAAY,CAAA;IACR,CAAA,CAER;;GAEZ,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO;GAAc,CAAA;GAC3C,KAAU,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAAS,UAAA;GAAe,CAAA;EAC1D;CACT,CAAA,CAAA,EAAA,CAAA,GACF,CACJ;AACJ;AAEA,SAAS,IAAY;CACjB,OACI,kBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;EACjD,UAAA,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACjB,CAAA;CACA,CAAA;AAEb"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("../Button/Button.cjs"),n=require("../Card/Card.cjs"),r=require("../Layout/Layout.cjs"),i=require("./FilterPanel.module.cjs");let a=require("react"),o=require("react/jsx-runtime");var s={"pt-BR":{title:`Filtros`,clear:`Limpar filtros`,apply:`Filtrar`},en:{title:`Filters`,clear:`Clear filters`,apply:`Apply filters`}};function c({children:c,title:l,actions:u,onClear:d,onApply:f,applyDisabled:p=!1,locale:m=`pt-BR`,labels:h,minFieldWidth:g=`12rem`,columns:_,className:v,style:y,...b}){let x=(0,a.useId)(),S={...s[m],...h},C=l??S.title,w=e=>{e.preventDefault(),f?.()},T=u!==void 0||d!==void 0||f!==void 0?(0,o.jsxs)(`div`,{className:i.default.actions,children:[u,d&&(0,o.jsx)(t.Button,{type:`button`,variant:`ghost`,size:`sm`,onClick:d,children:S.clear}),f&&(0,o.jsx)(t.Button,{type:`submit`,size:`sm`,form:x,disabled:p,children:S.apply})]}):void 0,E=(0,o.jsx)(r.Grid,{className:i.default.fields,columns:_??`repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))`,children:c});return(0,o.jsx)(n.Card,{flush:!0,role:`search`,"aria-label":typeof C==`string`?C:void 0,title:C,actions:T,className:e.cn(i.default.panel,v),style:{"--tempest-filter-panel-min":g,...y},...b,children:f?(0,o.jsx)(`form`,{id:x,className:i.default.form,onSubmit:w,noValidate:!0,children:E}):E})}exports.FilterPanel=c;
2
+ //# sourceMappingURL=FilterPanel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.cjs","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.tsx"],"sourcesContent":["import {\n useId,\n type CSSProperties,\n type FormEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { Button } from \"../Button\";\nimport { Card } from \"../Card\";\nimport { Grid, type ResponsiveValue } from \"../Layout\";\nimport styles from \"./FilterPanel.module.css\";\n\n/** Labels the panel writes itself. */\nexport interface FilterPanelLabels {\n /** Default title. `\"Filtros\"` / `\"Filters\"`. */\n title: string;\n /** The clear button. `\"Limpar filtros\"` / `\"Clear filters\"`. */\n clear: string;\n /** The apply button. `\"Filtrar\"` / `\"Apply filters\"`. */\n apply: string;\n}\n\n/** Built-in labels per locale. */\nconst LABELS: Record<\"pt-BR\" | \"en\", FilterPanelLabels> = {\n \"pt-BR\": { title: \"Filtros\", clear: \"Limpar filtros\", apply: \"Filtrar\" },\n en: { title: \"Filters\", clear: \"Clear filters\", apply: \"Apply filters\" },\n};\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"title\" | \"children\" | \"onSubmit\";\n\nexport interface FilterPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The filter controls the screen declares — `Input`, `Select`, a date picker. One grid cell each. */\n children: ReactNode;\n /** Heading. Default from `locale`. A string renders as an `h3`; a node renders as given. */\n title?: ReactNode;\n /** Header actions that are not fields — \"Atualizar a cada 30s\", \"Exportar CSV\". */\n actions?: ReactNode;\n /** Return the screen to its initial filters. Renders the clear button only when set. */\n onClear?: () => void;\n /**\n * Publish the draft. When set, the fields sit in a `<form>`: the apply button\n * submits it and Enter in any text field applies.\n */\n onApply?: () => void;\n /**\n * Disable the apply button — pass `!filters.isDirty`. Enter follows the button:\n * it is the form's default button, so a disabled one blocks implicit submission\n * too, and nothing is re-applied when nothing changed.\n */\n applyDisabled?: boolean;\n /** Locale for the default title and button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Override any of the labels. */\n labels?: Partial<FilterPanelLabels>;\n /**\n * Narrowest a field may get before the grid drops a column. Default `\"12rem\"`.\n * The count follows the **panel's** width, so the same panel fits a page and a\n * sidebar.\n */\n minFieldWidth?: string;\n /**\n * Fixed columns per viewport, as in `Grid` — `{ mobile: 1, tablet: 2, desktop: 4 }`.\n * Replaces `minFieldWidth`, and follows the **viewport**, not the panel.\n */\n columns?: ResponsiveValue<number | string>;\n}\n\n/**\n * The frame around a fixed set of filters: a card with a title, a grid of the\n * fields the screen declares, and the actions that belong to no field — clear,\n * apply, export.\n *\n * It holds no state and has no opinion on the fields. `FilterBar` is for ad hoc\n * querying (the user picks field, operator and value); this is for the set a\n * screen already knows — município, UF, situação, a search box. Pair it with\n * `useDraftFilters`: the fields edit `draft`, `onApply` and `onClear` are the\n * hook's `apply` and `clear`.\n *\n * What it settles, each measured in Chromium against the `Card` + `Grid`\n * composition it replaces:\n *\n * - **The grid follows the panel, not the viewport.** `Grid` switches columns on\n * viewport breakpoints, so the same filters in a 360 px column of a 1280 px\n * screen got four 57.5 px fields. The default here is\n * `repeat(auto-fill, minmax(min(100%, minFieldWidth), 1fr))`: the column count\n * comes from the room the panel has, and `min(100%, …)` keeps a floor wider than\n * the phone from overflowing it.\n * - **Cells align to the top.** A field showing an error is taller; with the grid\n * default (`stretch`), its row-mates' boxes grew to match.\n * - **The header wraps.** Three actions next to the title measured 527 px in a\n * 308 px header at 390 px wide, and scrolled the whole page sideways. `Card`'s\n * header now wraps, and so do the actions inside it.\n * - **Clear and apply have a fixed place**, at the end of the header, apply last.\n *\n * @example\n * const filters = useDraftFilters({ search: \"\", uf: \"\" });\n * <FilterPanel\n * onApply={() => filters.apply()}\n * onClear={filters.clear}\n * applyDisabled={!filters.isDirty}\n * >\n * <Input label=\"Busca\" value={filters.draft.search}\n * onChange={(e) => filters.set({ search: e.target.value })} />\n * <Select label=\"UF\" options={UFS} value={filters.draft.uf}\n * onChange={(e) => filters.set({ uf: e.target.value })} />\n * </FilterPanel>\n */\nexport function FilterPanel({\n children,\n title,\n actions,\n onClear,\n onApply,\n applyDisabled = false,\n locale = \"pt-BR\",\n labels,\n minFieldWidth = \"12rem\",\n columns,\n className,\n style,\n ...props\n}: FilterPanelProps) {\n const formId = useId();\n const text: FilterPanelLabels = { ...LABELS[locale], ...labels };\n const heading = title ?? text.title;\n\n const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {\n event.preventDefault();\n onApply?.();\n };\n\n const hasActions = actions !== undefined || onClear !== undefined || onApply !== undefined;\n const headerActions = hasActions ? (\n <div className={styles.actions}>\n {actions}\n {onClear && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onClear}>\n {text.clear}\n </Button>\n )}\n {onApply && (\n <Button type=\"submit\" size=\"sm\" form={formId} disabled={applyDisabled}>\n {text.apply}\n </Button>\n )}\n </div>\n ) : undefined;\n\n const fields = (\n <Grid\n className={styles.fields}\n columns={\n columns ??\n \"repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))\"\n }\n >\n {children}\n </Grid>\n );\n\n return (\n <Card\n flush\n role=\"search\"\n aria-label={typeof heading === \"string\" ? heading : undefined}\n title={heading}\n actions={headerActions}\n className={cn(styles.panel, className)}\n style={{ \"--tempest-filter-panel-min\": minFieldWidth, ...style } as CSSProperties}\n {...props}\n >\n {onApply ? (\n <form id={formId} className={styles.form} onSubmit={handleSubmit} noValidate>\n {fields}\n </form>\n ) : (\n fields\n )}\n </Card>\n );\n}\n"],"mappings":"oOA0BA,IAAM,EAAoD,CACtD,QAAS,CAAE,MAAO,UAAW,MAAO,iBAAkB,MAAO,SAAU,EACvE,GAAI,CAAE,MAAO,UAAW,MAAO,gBAAiB,MAAO,eAAgB,CAC3E,EAkFA,SAAgB,EAAY,CACxB,WACA,QACA,UACA,UACA,UACA,gBAAgB,GAChB,SAAS,QACT,SACA,gBAAgB,QAChB,UACA,YACA,QACA,GAAG,GACc,CACjB,IAAM,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,EAA0B,CAAE,GAAG,EAAO,GAAS,GAAG,CAAO,EACzD,EAAU,GAAS,EAAK,MAExB,EAAgB,GAA4C,CAC9D,EAAM,eAAe,EACrB,IAAU,CACd,EAGM,EADa,IAAY,IAAA,IAAa,IAAY,IAAA,IAAa,IAAY,IAAA,IAE7E,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAvB,SAAA,CACK,EACA,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAQ,KAAK,SAAS,QAAQ,QAAQ,KAAK,KAAK,QAAS,EACpD,SAAA,EAAK,KACF,CAAA,EAEX,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAQ,KAAK,SAAS,KAAK,KAAK,KAAM,EAAQ,SAAU,EACnD,SAAA,EAAK,KACF,CAAA,CAEX,CACL,CAAA,EAAA,IAAA,GAEE,GACF,EAAA,EAAA,IAAA,CAAC,EAAA,KAAD,CACI,UAAW,EAAA,QAAO,OAClB,QACI,GACA,6EAGH,UACC,CAAA,EAGV,OACI,EAAA,EAAA,IAAA,CAAC,EAAA,KAAD,CACI,MAAA,GACA,KAAK,SACL,aAAY,OAAO,GAAY,SAAW,EAAU,IAAA,GACpD,MAAO,EACP,QAAS,EACT,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EACrC,MAAO,CAAE,6BAA8B,EAAe,GAAG,CAAM,EAC/D,GAAI,EAEH,SAAA,GACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,GAAI,EAAQ,UAAW,EAAA,QAAO,KAAM,SAAU,EAAc,WAAA,GAC7D,SAAA,CACC,CAAA,EAEN,CAEF,CAAA,CAEd"}
@@ -0,0 +1,75 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { Button as t } from "../Button/Button.js";
3
+ import { Card as n } from "../Card/Card.js";
4
+ import { Grid as r } from "../Layout/Layout.js";
5
+ import i from "./FilterPanel.module.js";
6
+ import { useId as a } from "react";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ //#region src/components/FilterPanel/FilterPanel.tsx
9
+ var c = {
10
+ "pt-BR": {
11
+ title: "Filtros",
12
+ clear: "Limpar filtros",
13
+ apply: "Filtrar"
14
+ },
15
+ en: {
16
+ title: "Filters",
17
+ clear: "Clear filters",
18
+ apply: "Apply filters"
19
+ }
20
+ };
21
+ function l({ children: l, title: u, actions: d, onClear: f, onApply: p, applyDisabled: m = !1, locale: h = "pt-BR", labels: g, minFieldWidth: _ = "12rem", columns: v, className: y, style: b, ...x }) {
22
+ let S = a(), C = {
23
+ ...c[h],
24
+ ...g
25
+ }, w = u ?? C.title, T = (e) => {
26
+ e.preventDefault(), p?.();
27
+ }, E = d !== void 0 || f !== void 0 || p !== void 0 ? /* @__PURE__ */ s("div", {
28
+ className: i.actions,
29
+ children: [
30
+ d,
31
+ f && /* @__PURE__ */ o(t, {
32
+ type: "button",
33
+ variant: "ghost",
34
+ size: "sm",
35
+ onClick: f,
36
+ children: C.clear
37
+ }),
38
+ p && /* @__PURE__ */ o(t, {
39
+ type: "submit",
40
+ size: "sm",
41
+ form: S,
42
+ disabled: m,
43
+ children: C.apply
44
+ })
45
+ ]
46
+ }) : void 0, D = /* @__PURE__ */ o(r, {
47
+ className: i.fields,
48
+ columns: v ?? "repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))",
49
+ children: l
50
+ });
51
+ return /* @__PURE__ */ o(n, {
52
+ flush: !0,
53
+ role: "search",
54
+ "aria-label": typeof w == "string" ? w : void 0,
55
+ title: w,
56
+ actions: E,
57
+ className: e(i.panel, y),
58
+ style: {
59
+ "--tempest-filter-panel-min": _,
60
+ ...b
61
+ },
62
+ ...x,
63
+ children: p ? /* @__PURE__ */ o("form", {
64
+ id: S,
65
+ className: i.form,
66
+ onSubmit: T,
67
+ noValidate: !0,
68
+ children: D
69
+ }) : D
70
+ });
71
+ }
72
+ //#endregion
73
+ export { l as FilterPanel };
74
+
75
+ //# sourceMappingURL=FilterPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.js","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.tsx"],"sourcesContent":["import {\n useId,\n type CSSProperties,\n type FormEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { Button } from \"../Button\";\nimport { Card } from \"../Card\";\nimport { Grid, type ResponsiveValue } from \"../Layout\";\nimport styles from \"./FilterPanel.module.css\";\n\n/** Labels the panel writes itself. */\nexport interface FilterPanelLabels {\n /** Default title. `\"Filtros\"` / `\"Filters\"`. */\n title: string;\n /** The clear button. `\"Limpar filtros\"` / `\"Clear filters\"`. */\n clear: string;\n /** The apply button. `\"Filtrar\"` / `\"Apply filters\"`. */\n apply: string;\n}\n\n/** Built-in labels per locale. */\nconst LABELS: Record<\"pt-BR\" | \"en\", FilterPanelLabels> = {\n \"pt-BR\": { title: \"Filtros\", clear: \"Limpar filtros\", apply: \"Filtrar\" },\n en: { title: \"Filters\", clear: \"Clear filters\", apply: \"Apply filters\" },\n};\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"title\" | \"children\" | \"onSubmit\";\n\nexport interface FilterPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The filter controls the screen declares — `Input`, `Select`, a date picker. One grid cell each. */\n children: ReactNode;\n /** Heading. Default from `locale`. A string renders as an `h3`; a node renders as given. */\n title?: ReactNode;\n /** Header actions that are not fields — \"Atualizar a cada 30s\", \"Exportar CSV\". */\n actions?: ReactNode;\n /** Return the screen to its initial filters. Renders the clear button only when set. */\n onClear?: () => void;\n /**\n * Publish the draft. When set, the fields sit in a `<form>`: the apply button\n * submits it and Enter in any text field applies.\n */\n onApply?: () => void;\n /**\n * Disable the apply button — pass `!filters.isDirty`. Enter follows the button:\n * it is the form's default button, so a disabled one blocks implicit submission\n * too, and nothing is re-applied when nothing changed.\n */\n applyDisabled?: boolean;\n /** Locale for the default title and button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Override any of the labels. */\n labels?: Partial<FilterPanelLabels>;\n /**\n * Narrowest a field may get before the grid drops a column. Default `\"12rem\"`.\n * The count follows the **panel's** width, so the same panel fits a page and a\n * sidebar.\n */\n minFieldWidth?: string;\n /**\n * Fixed columns per viewport, as in `Grid` — `{ mobile: 1, tablet: 2, desktop: 4 }`.\n * Replaces `minFieldWidth`, and follows the **viewport**, not the panel.\n */\n columns?: ResponsiveValue<number | string>;\n}\n\n/**\n * The frame around a fixed set of filters: a card with a title, a grid of the\n * fields the screen declares, and the actions that belong to no field — clear,\n * apply, export.\n *\n * It holds no state and has no opinion on the fields. `FilterBar` is for ad hoc\n * querying (the user picks field, operator and value); this is for the set a\n * screen already knows — município, UF, situação, a search box. Pair it with\n * `useDraftFilters`: the fields edit `draft`, `onApply` and `onClear` are the\n * hook's `apply` and `clear`.\n *\n * What it settles, each measured in Chromium against the `Card` + `Grid`\n * composition it replaces:\n *\n * - **The grid follows the panel, not the viewport.** `Grid` switches columns on\n * viewport breakpoints, so the same filters in a 360 px column of a 1280 px\n * screen got four 57.5 px fields. The default here is\n * `repeat(auto-fill, minmax(min(100%, minFieldWidth), 1fr))`: the column count\n * comes from the room the panel has, and `min(100%, …)` keeps a floor wider than\n * the phone from overflowing it.\n * - **Cells align to the top.** A field showing an error is taller; with the grid\n * default (`stretch`), its row-mates' boxes grew to match.\n * - **The header wraps.** Three actions next to the title measured 527 px in a\n * 308 px header at 390 px wide, and scrolled the whole page sideways. `Card`'s\n * header now wraps, and so do the actions inside it.\n * - **Clear and apply have a fixed place**, at the end of the header, apply last.\n *\n * @example\n * const filters = useDraftFilters({ search: \"\", uf: \"\" });\n * <FilterPanel\n * onApply={() => filters.apply()}\n * onClear={filters.clear}\n * applyDisabled={!filters.isDirty}\n * >\n * <Input label=\"Busca\" value={filters.draft.search}\n * onChange={(e) => filters.set({ search: e.target.value })} />\n * <Select label=\"UF\" options={UFS} value={filters.draft.uf}\n * onChange={(e) => filters.set({ uf: e.target.value })} />\n * </FilterPanel>\n */\nexport function FilterPanel({\n children,\n title,\n actions,\n onClear,\n onApply,\n applyDisabled = false,\n locale = \"pt-BR\",\n labels,\n minFieldWidth = \"12rem\",\n columns,\n className,\n style,\n ...props\n}: FilterPanelProps) {\n const formId = useId();\n const text: FilterPanelLabels = { ...LABELS[locale], ...labels };\n const heading = title ?? text.title;\n\n const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {\n event.preventDefault();\n onApply?.();\n };\n\n const hasActions = actions !== undefined || onClear !== undefined || onApply !== undefined;\n const headerActions = hasActions ? (\n <div className={styles.actions}>\n {actions}\n {onClear && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onClear}>\n {text.clear}\n </Button>\n )}\n {onApply && (\n <Button type=\"submit\" size=\"sm\" form={formId} disabled={applyDisabled}>\n {text.apply}\n </Button>\n )}\n </div>\n ) : undefined;\n\n const fields = (\n <Grid\n className={styles.fields}\n columns={\n columns ??\n \"repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))\"\n }\n >\n {children}\n </Grid>\n );\n\n return (\n <Card\n flush\n role=\"search\"\n aria-label={typeof heading === \"string\" ? heading : undefined}\n title={heading}\n actions={headerActions}\n className={cn(styles.panel, className)}\n style={{ \"--tempest-filter-panel-min\": minFieldWidth, ...style } as CSSProperties}\n {...props}\n >\n {onApply ? (\n <form id={formId} className={styles.form} onSubmit={handleSubmit} noValidate>\n {fields}\n </form>\n ) : (\n fields\n )}\n </Card>\n );\n}\n"],"mappings":";;;;;;;;AA0BA,IAAM,IAAoD;CACtD,SAAS;EAAE,OAAO;EAAW,OAAO;EAAkB,OAAO;CAAU;CACvE,IAAI;EAAE,OAAO;EAAW,OAAO;EAAiB,OAAO;CAAgB;AAC3E;AAkFA,SAAgB,EAAY,EACxB,aACA,UACA,YACA,YACA,YACA,mBAAgB,IAChB,YAAS,SACT,WACA,mBAAgB,SAChB,YACA,cACA,UACA,GAAG,KACc;CACjB,IAAM,IAAS,EAAM,GACf,IAA0B;EAAE,GAAG,EAAO;EAAS,GAAG;CAAO,GACzD,IAAU,KAAS,EAAK,OAExB,KAAgB,MAA4C;EAE9D,AADA,EAAM,eAAe,GACrB,IAAU;CACd,GAGM,IADa,MAAY,KAAA,KAAa,MAAY,KAAA,KAAa,MAAY,KAAA,IAE7E,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAvB,UAAA;GACK;GACA,KACG,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;IAAK,SAAS;IACpD,UAAA,EAAK;GACF,CAAA;GAEX,KACG,kBAAC,GAAD;IAAQ,MAAK;IAAS,MAAK;IAAK,MAAM;IAAQ,UAAU;IACnD,UAAA,EAAK;GACF,CAAA;EAEX;CACL,CAAA,IAAA,KAAA,GAEE,IACF,kBAAC,GAAD;EACI,WAAW,EAAO;EAClB,SACI,KACA;EAGH;CACC,CAAA;CAGV,OACI,kBAAC,GAAD;EACI,OAAA;EACA,MAAK;EACL,cAAY,OAAO,KAAY,WAAW,IAAU,KAAA;EACpD,OAAO;EACP,SAAS;EACT,WAAW,EAAG,EAAO,OAAO,CAAS;EACrC,OAAO;GAAE,8BAA8B;GAAe,GAAG;EAAM;EAC/D,GAAI;EAEH,UAAA,IACG,kBAAC,QAAD;GAAM,IAAI;GAAQ,WAAW,EAAO;GAAM,UAAU;GAAc,YAAA;GAC7D,UAAA;EACC,CAAA,IAEN;CAEF,CAAA;AAEd"}
@@ -0,0 +1,3 @@
1
+ require("../../styles/component/FilterPanel.css");
2
+ var e=`tempest_panel_ab2ba`,t=`tempest_actions_9a-Sm`,n=`tempest_form_bMDhN`,r=`tempest_fields_M30-g`,i={panel:e,actions:t,form:n,fields:r};exports.actions=t,exports.default=i,exports.fields=r,exports.form=n,exports.panel=e;
3
+ //# sourceMappingURL=FilterPanel.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.module.cjs","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.module.css"],"sourcesContent":[".panel {\n min-width: 0;\n}\n\n/*\n * Actions share the header row with the title on a wide screen and move under it,\n * wrapping among themselves, when they do not fit. Apply stays last.\n */\n.actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--tempest-space-2);\n margin-left: auto;\n}\n\n.form {\n margin: 0;\n}\n\n/*\n * Top alignment keeps a field's box its own height: the grid default stretches every\n * cell of a row to the tallest one, which is the field showing an error.\n */\n.fields {\n align-items: start;\n}\n"],"mappings":";"}
@@ -0,0 +1,12 @@
1
+ import "../../styles/component/FilterPanel.css";
2
+ //#region src/components/FilterPanel/FilterPanel.module.css
3
+ var e = "tempest_panel_ab2ba", t = "tempest_actions_9a-Sm", n = "tempest_form_bMDhN", r = "tempest_fields_M30-g", i = {
4
+ panel: e,
5
+ actions: t,
6
+ form: n,
7
+ fields: r
8
+ };
9
+ //#endregion
10
+ export { t as actions, i as default, r as fields, n as form, e as panel };
11
+
12
+ //# sourceMappingURL=FilterPanel.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.module.js","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.module.css"],"sourcesContent":[".panel {\n min-width: 0;\n}\n\n/*\n * Actions share the header row with the title on a wide screen and move under it,\n * wrapping among themselves, when they do not fit. Apply stays last.\n */\n.actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--tempest-space-2);\n margin-left: auto;\n}\n\n.form {\n margin: 0;\n}\n\n/*\n * Top alignment keeps a field's box its own height: the grid default stretches every\n * cell of a row to the tallest one, which is the field showing an error.\n */\n.fields {\n align-items: start;\n}\n"],"mappings":";"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../Portal/Portal.cjs"),r=require("../../hooks/use-scroll-lock.cjs"),i=require("../../hooks/use-focus-trap.cjs"),a=require("./Lightbox.module.cjs");let o=require("react"),s=require("react/jsx-runtime"),c=require("lucide-react");function l({items:l,open:u,index:d=0,onIndexChange:f,onClose:p,showThumbnails:m,showCounter:h=!0,loop:g=!0,closeLabel:_=`Close`,previousLabel:v=`Previous image`,nextLabel:y=`Next image`,className:b}){let x=(0,o.useRef)(null),[S,C]=(0,o.useState)(d),w=f!==void 0,T=w?d:S;i.useFocusTrap(x,u),r.useScrollLock(u);let E=(0,o.useCallback)(e=>{if(l.length===0)return;let t=g?(e+l.length)%l.length:Math.max(0,Math.min(e,l.length-1));w||C(t),f?.(t)},[w,l.length,g,f]);if((0,o.useEffect)(()=>{w||C(d)},[d,w]),t.useEscapeLayer(u,p),(0,o.useEffect)(()=>{if(!u)return;function e(e){switch(e.key){case`ArrowRight`:E(T+1);break;case`ArrowLeft`:E(T-1);break;case`Home`:E(0);break;case`End`:E(l.length-1);break;default:return}e.preventDefault()}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[T,l.length,E,u]),(0,o.useEffect)(()=>{if(!(!u||typeof Image>`u`))for(let e of[-1,1]){let t=l[(T+e+l.length)%l.length];if(!t)continue;let n=new Image;n.src=t.src}},[T,l,u]),!u||l.length===0)return null;let D=l[Math.max(0,Math.min(T,l.length-1))],O=m??l.length>1,k=l.length>1;return(0,s.jsx)(n.Portal,{children:(0,s.jsxs)(`div`,{ref:x,role:`dialog`,"aria-modal":`true`,"aria-label":D.alt,className:e.cn(a.default.overlay,b),children:[(0,s.jsxs)(`div`,{className:a.default.topBar,children:[h&&k?(0,s.jsxs)(`span`,{className:a.default.counter,children:[T+1,` / `,l.length]}):(0,s.jsx)(`span`,{}),(0,s.jsx)(`button`,{type:`button`,className:a.default.iconButton,"aria-label":_,onClick:p,children:(0,s.jsx)(c.X,{size:20})})]}),(0,s.jsxs)(`div`,{className:a.default.stage,children:[k?(0,s.jsx)(`button`,{type:`button`,className:e.cn(a.default.iconButton,a.default.nav),"aria-label":v,disabled:!g&&T===0,onClick:()=>E(T-1),children:(0,s.jsx)(c.ChevronLeft,{size:24})}):null,(0,s.jsxs)(`figure`,{className:a.default.figure,children:[(0,s.jsx)(`img`,{src:D.src,alt:D.alt,className:a.default.image}),D.caption?(0,s.jsx)(`figcaption`,{className:a.default.caption,children:D.caption}):null]}),k?(0,s.jsx)(`button`,{type:`button`,className:e.cn(a.default.iconButton,a.default.nav),"aria-label":y,disabled:!g&&T===l.length-1,onClick:()=>E(T+1),children:(0,s.jsx)(c.ChevronRight,{size:24})}):null]}),O&&k?(0,s.jsx)(`div`,{className:a.default.thumbnails,role:`tablist`,"aria-label":D.alt,children:l.map((t,n)=>(0,s.jsx)(`button`,{type:`button`,role:`tab`,"aria-selected":n===T,"aria-label":t.alt,className:e.cn(a.default.thumbnail,n===T&&a.default.thumbnailActive),onClick:()=>E(n),children:(0,s.jsx)(`img`,{src:t.thumbnail??t.src,alt:``,className:a.default.thumbnailImage})},`${t.src}-${n}`))}):null]})})}exports.Lightbox=l;
1
+ const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../../hooks/use-focus-trap.cjs"),r=require("../Portal/Portal.cjs"),i=require("../../hooks/use-scroll-lock.cjs"),a=require("./Lightbox.module.cjs");let o=require("react"),s=require("react/jsx-runtime"),c=require("lucide-react");function l({items:l,open:u,index:d=0,onIndexChange:f,onClose:p,showThumbnails:m,showCounter:h=!0,loop:g=!0,closeLabel:_=`Close`,previousLabel:v=`Previous image`,nextLabel:y=`Next image`,className:b}){let x=(0,o.useRef)(null),[S,C]=(0,o.useState)(d),w=f!==void 0,T=w?d:S;n.useFocusTrap(x,u),i.useScrollLock(u);let E=(0,o.useCallback)(e=>{if(l.length===0)return;let t=g?(e+l.length)%l.length:Math.max(0,Math.min(e,l.length-1));w||C(t),f?.(t)},[w,l.length,g,f]);if((0,o.useEffect)(()=>{w||C(d)},[d,w]),t.useEscapeLayer(u,p),(0,o.useEffect)(()=>{if(!u)return;function e(e){switch(e.key){case`ArrowRight`:E(T+1);break;case`ArrowLeft`:E(T-1);break;case`Home`:E(0);break;case`End`:E(l.length-1);break;default:return}e.preventDefault()}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[T,l.length,E,u]),(0,o.useEffect)(()=>{if(!(!u||typeof Image>`u`))for(let e of[-1,1]){let t=l[(T+e+l.length)%l.length];if(!t)continue;let n=new Image;n.src=t.src}},[T,l,u]),!u||l.length===0)return null;let D=l[Math.max(0,Math.min(T,l.length-1))],O=m??l.length>1,k=l.length>1;return(0,s.jsx)(r.Portal,{children:(0,s.jsxs)(`div`,{ref:x,role:`dialog`,"aria-modal":`true`,"aria-label":D.alt,className:e.cn(a.default.overlay,b),children:[(0,s.jsxs)(`div`,{className:a.default.topBar,children:[h&&k?(0,s.jsxs)(`span`,{className:a.default.counter,children:[T+1,` / `,l.length]}):(0,s.jsx)(`span`,{}),(0,s.jsx)(`button`,{type:`button`,className:a.default.iconButton,"aria-label":_,onClick:p,children:(0,s.jsx)(c.X,{size:20})})]}),(0,s.jsxs)(`div`,{className:a.default.stage,children:[k?(0,s.jsx)(`button`,{type:`button`,className:e.cn(a.default.iconButton,a.default.nav),"aria-label":v,disabled:!g&&T===0,onClick:()=>E(T-1),children:(0,s.jsx)(c.ChevronLeft,{size:24})}):null,(0,s.jsxs)(`figure`,{className:a.default.figure,children:[(0,s.jsx)(`img`,{src:D.src,alt:D.alt,className:a.default.image}),D.caption?(0,s.jsx)(`figcaption`,{className:a.default.caption,children:D.caption}):null]}),k?(0,s.jsx)(`button`,{type:`button`,className:e.cn(a.default.iconButton,a.default.nav),"aria-label":y,disabled:!g&&T===l.length-1,onClick:()=>E(T+1),children:(0,s.jsx)(c.ChevronRight,{size:24})}):null]}),O&&k?(0,s.jsx)(`div`,{className:a.default.thumbnails,role:`tablist`,"aria-label":D.alt,children:l.map((t,n)=>(0,s.jsx)(`button`,{type:`button`,role:`tab`,"aria-selected":n===T,"aria-label":t.alt,className:e.cn(a.default.thumbnail,n===T&&a.default.thumbnailActive),onClick:()=>E(n),children:(0,s.jsx)(`img`,{src:t.thumbnail??t.src,alt:``,className:a.default.thumbnailImage})},`${t.src}-${n}`))}):null]})})}exports.Lightbox=l;
2
2
  //# sourceMappingURL=Lightbox.cjs.map
@@ -1,8 +1,8 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
2
  import { useEscapeLayer as t } from "../Portal/escape-layer.js";
3
- import { Portal as n } from "../Portal/Portal.js";
4
- import { useScrollLock as r } from "../../hooks/use-scroll-lock.js";
5
- import { useFocusTrap as i } from "../../hooks/use-focus-trap.js";
3
+ import { useFocusTrap as n } from "../../hooks/use-focus-trap.js";
4
+ import { Portal as r } from "../Portal/Portal.js";
5
+ import { useScrollLock as i } from "../../hooks/use-scroll-lock.js";
6
6
  import a from "./Lightbox.module.js";
7
7
  import { useCallback as o, useEffect as s, useRef as c, useState as l } from "react";
8
8
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
@@ -10,7 +10,7 @@ import { ChevronLeft as f, ChevronRight as p, X as m } from "lucide-react";
10
10
  //#region src/components/Lightbox/Lightbox.tsx
11
11
  function h({ items: h, open: g, index: _ = 0, onIndexChange: v, onClose: y, showThumbnails: b, showCounter: x = !0, loop: S = !0, closeLabel: C = "Close", previousLabel: w = "Previous image", nextLabel: T = "Next image", className: E }) {
12
12
  let D = c(null), [O, k] = l(_), A = v !== void 0, j = A ? _ : O;
13
- i(D, g), r(g);
13
+ n(D, g), i(g);
14
14
  let M = o((e) => {
15
15
  if (h.length === 0) return;
16
16
  let t = S ? (e + h.length) % h.length : Math.max(0, Math.min(e, h.length - 1));
@@ -62,7 +62,7 @@ function h({ items: h, open: g, index: _ = 0, onIndexChange: v, onClose: y, show
62
62
  g
63
63
  ]), !g || h.length === 0) return null;
64
64
  let N = h[Math.max(0, Math.min(j, h.length - 1))], P = b ?? h.length > 1, F = h.length > 1;
65
- return /* @__PURE__ */ u(n, { children: /* @__PURE__ */ d("div", {
65
+ return /* @__PURE__ */ u(r, { children: /* @__PURE__ */ d("div", {
66
66
  ref: D,
67
67
  role: "dialog",
68
68
  "aria-modal": "true",
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../Portal/portal-host.cjs"),r=require("./Modal.module.cjs");let i=require("react"),a=require("react/jsx-runtime"),o=require("react-dom");function s({open:s,onClose:u,title:d,children:f,footer:p,size:m=`md`,closeOnBackdrop:h=!0,closeOnEsc:g=!0,className:_,hideCloseButton:v=!1,fullscreen:y=!1,fullscreenOnMobile:b=!1,"aria-label":x}){let S=(0,i.useId)();t.useEscapeLayer(s,g?u:null),(0,i.useEffect)(()=>{if(!s)return;let e=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=e}},[s]);let C=n.usePortalHost();return!s||!C?null:(0,o.createPortal)((0,a.jsx)(`div`,{className:r.default.overlay,role:`presentation`,onClick:()=>{h&&u()},children:(0,a.jsxs)(`div`,{role:`dialog`,"aria-modal":`true`,"aria-labelledby":d?S:void 0,"aria-label":d?void 0:x,className:e.cn(r.default.dialog,c(m),y&&r.default.fullscreen,b&&r.default.fullscreenOnMobile,_),onClick:e=>e.stopPropagation(),children:[(d||!v)&&(0,a.jsxs)(`header`,{className:r.default.header,children:[d&&(0,a.jsx)(`h3`,{id:S,className:r.default.title,children:d}),!v&&(0,a.jsx)(`button`,{type:`button`,className:r.default.close,"aria-label":`Fechar`,onClick:u,children:(0,a.jsx)(l,{})})]}),(0,a.jsx)(`div`,{className:r.default.body,children:f}),p&&(0,a.jsx)(`footer`,{className:r.default.footer,children:p})]})}),C)}function c(e){return e===`2xl`?r.default.size2xl:e===`3xl`?r.default.size3xl:r.default[e]}function l(){return(0,a.jsx)(`svg`,{width:`18`,height:`18`,viewBox:`0 0 24 24`,fill:`none`,children:(0,a.jsx)(`path`,{d:`M6 6l12 12M6 18L18 6`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`})})}exports.Modal=s;
1
+ const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../../hooks/use-focus-trap.cjs"),r=require("../Portal/portal-host.cjs"),i=require("./Modal.module.cjs");let a=require("react"),o=require("react/jsx-runtime"),s=require("react-dom");function c({open:c,onClose:d,title:f,children:p,footer:m,size:h=`md`,closeOnBackdrop:g=!0,closeOnEsc:_=!0,className:v,hideCloseButton:y=!1,fullscreen:b=!1,fullscreenOnMobile:x=!1,"aria-label":S}){let C=(0,a.useId)(),w=(0,a.useRef)(null);n.useFocusTrap(w,c),t.useEscapeLayer(c,_?d:null),(0,a.useEffect)(()=>{if(!c)return;let e=document.body.style.overflow;return document.body.style.overflow=`hidden`,()=>{document.body.style.overflow=e}},[c]);let T=r.usePortalHost();return!c||!T?null:(0,s.createPortal)((0,o.jsx)(`div`,{className:i.default.overlay,role:`presentation`,onClick:()=>{g&&d()},children:(0,o.jsxs)(`div`,{ref:w,role:`dialog`,"aria-modal":`true`,tabIndex:-1,"aria-labelledby":f?C:void 0,"aria-label":f?void 0:S,className:e.cn(i.default.dialog,l(h),b&&i.default.fullscreen,x&&i.default.fullscreenOnMobile,v),onClick:e=>e.stopPropagation(),children:[(f||!y)&&(0,o.jsxs)(`header`,{className:i.default.header,children:[f&&(0,o.jsx)(`h3`,{id:C,className:i.default.title,children:f}),!y&&(0,o.jsx)(`button`,{type:`button`,className:i.default.close,"aria-label":`Fechar`,onClick:d,children:(0,o.jsx)(u,{})})]}),(0,o.jsx)(`div`,{className:i.default.body,children:p}),m&&(0,o.jsx)(`footer`,{className:i.default.footer,children:m})]})}),T)}function l(e){return e===`2xl`?i.default.size2xl:e===`3xl`?i.default.size3xl:i.default[e]}function u(){return(0,o.jsx)(`svg`,{width:`18`,height:`18`,viewBox:`0 0 24 24`,fill:`none`,children:(0,o.jsx)(`path`,{d:`M6 6l12 12M6 18L18 6`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`})})}exports.Modal=c;
2
2
  //# sourceMappingURL=Modal.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.cjs","names":[],"sources":["../../../src/components/Modal/Modal.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — size, fullscreen and\n * fullscreenOnMobile are three distinct decisions (a fixed width, always full, full\n * only on a phone), and the rest is the dialog contract: open, onClose, title,\n * children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton. The body is the\n * focus trap and the scroll lock, which must be installed and torn down together.\n */\nimport { useEffect, useId } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Modal.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\";\n\nexport interface ModalProps {\n open: boolean;\n onClose: () => void;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n size?: ModalSize;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n className?: string;\n hideCloseButton?: boolean;\n /** Force fullscreen at all breakpoints. */\n fullscreen?: boolean;\n /** Auto-fullscreen on mobile viewports (< 640px). Default `false`. */\n fullscreenOnMobile?: boolean;\n /**\n * Accessible name for the dialog. Only needed when there is no `title` —\n * with a `title` the dialog is named by it through `aria-labelledby`.\n */\n \"aria-label\"?: string;\n}\n\n/**\n * Portal-rendered modal dialog with backdrop, Esc handler, and slots for\n * header/body/footer. Locks body scroll while open.\n *\n * The `dialog` role needs an accessible name: a `title` supplies it via\n * `aria-labelledby`, and a titleless dialog should pass `aria-label`. The\n * header heading is only rendered when there is a title, so a close-button-only\n * header does not leave an empty `h3` behind.\n */\nexport function Modal({\n open,\n onClose,\n title,\n children,\n footer,\n size = \"md\",\n closeOnBackdrop = true,\n closeOnEsc = true,\n className,\n hideCloseButton = false,\n fullscreen = false,\n fullscreenOnMobile = false,\n \"aria-label\": ariaLabel,\n}: ModalProps) {\n const titleId = useId();\n useEscapeLayer(open, closeOnEsc ? onClose : null);\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [open]);\n\n const portalHost = usePortalHost();\n\n if (!open || !portalHost) return null;\n\n return createPortal(\n <div\n className={styles.overlay}\n role=\"presentation\"\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-label={title ? undefined : ariaLabel}\n className={cn(\n styles.dialog,\n sizeClassName(size),\n fullscreen && styles.fullscreen,\n fullscreenOnMobile && styles.fullscreenOnMobile,\n className,\n )}\n onClick={(event) => event.stopPropagation()}\n >\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n {title && (\n <h3 id={titleId} className={styles.title}>\n {title}\n </h3>\n )}\n {!hideCloseButton && (\n <button\n type=\"button\"\n className={styles.close}\n aria-label=\"Fechar\"\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </div>\n </div>,\n portalHost,\n );\n}\n\nfunction sizeClassName(size: ModalSize): string | undefined {\n if (size === \"2xl\") return styles.size2xl;\n if (size === \"3xl\") return styles.size3xl;\n return styles[size];\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"kOAgDA,SAAgB,EAAM,CAClB,OACA,UACA,QACA,WACA,SACA,OAAO,KACP,kBAAkB,GAClB,aAAa,GACb,YACA,kBAAkB,GAClB,aAAa,GACb,qBAAqB,GACrB,aAAc,GACH,CACX,IAAM,GAAA,EAAU,EAAA,MAAA,CAAM,EACtB,EAAA,eAAe,EAAM,EAAa,EAAU,IAAI,GAChD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAM,OACX,IAAM,EAAmB,SAAS,KAAK,MAAM,SAE7C,MADA,UAAS,KAAK,MAAM,SAAW,aAClB,CACT,SAAS,KAAK,MAAM,SAAW,CACnC,CACJ,EAAG,CAAC,CAAI,CAAC,EAET,IAAM,EAAa,EAAA,cAAc,EAIjC,MAFI,CAAC,GAAQ,CAAC,EAAmB,MAEjC,EAAO,EAAA,aAAA,EACH,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,QAClB,KAAK,eACL,YAAe,CACP,GAAiB,EAAQ,CACjC,EAEA,UAAA,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,SACL,aAAW,OACX,kBAAiB,EAAQ,EAAU,IAAA,GACnC,aAAY,EAAQ,IAAA,GAAY,EAChC,UAAW,EAAA,GACP,EAAA,QAAO,OACP,EAAc,CAAI,EAClB,GAAc,EAAA,QAAO,WACrB,GAAsB,EAAA,QAAO,mBAC7B,CACJ,EACA,QAAU,GAAU,EAAM,gBAAgB,EAZ9C,SAAA,EAcM,GAAS,CAAC,KACR,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAA1B,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,GAAI,EAAS,UAAW,EAAA,QAAO,MAC9B,SAAA,CACD,CAAA,EAEP,CAAC,IACE,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,aAAW,SACX,QAAS,EAET,UAAA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAER,KAEZ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAC3C,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,CAC5D,GACJ,CAAA,EACL,CACJ,CACJ,CAEA,SAAS,EAAc,EAAqC,CAGxD,OAFI,IAAS,MAAc,EAAA,QAAO,QAC9B,IAAS,MAAc,EAAA,QAAO,QAC3B,EAAA,QAAO,EAClB,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}
1
+ {"version":3,"file":"Modal.cjs","names":[],"sources":["../../../src/components/Modal/Modal.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — size, fullscreen and\n * fullscreenOnMobile are three distinct decisions (a fixed width, always full, full\n * only on a phone), and the rest is the dialog contract: open, onClose, title,\n * children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton. The body is the\n * focus trap and the scroll lock, which must be installed and torn down together.\n */\nimport { useEffect, useId, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Modal.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\";\n\nexport interface ModalProps {\n open: boolean;\n onClose: () => void;\n title?: ReactNode;\n children?: ReactNode;\n footer?: ReactNode;\n size?: ModalSize;\n closeOnBackdrop?: boolean;\n closeOnEsc?: boolean;\n className?: string;\n hideCloseButton?: boolean;\n /** Force fullscreen at all breakpoints. */\n fullscreen?: boolean;\n /** Auto-fullscreen on mobile viewports (< 640px). Default `false`. */\n fullscreenOnMobile?: boolean;\n /**\n * Accessible name for the dialog. Only needed when there is no `title` —\n * with a `title` the dialog is named by it through `aria-labelledby`.\n */\n \"aria-label\"?: string;\n}\n\n/**\n * Portal-rendered modal dialog with backdrop, Esc handler, and slots for\n * header/body/footer. Locks body scroll while open.\n *\n * Keyboard focus is trapped in the dialog while it is open (`useFocusTrap`): it\n * enters on the first tab stop — or the dialog itself when there is none —,\n * `Tab`/`Shift+Tab` cycle inside it, including the panels of a `Popover` or\n * `HoverCard` opened from it, and closing returns focus to the element that\n * opened it. `aria-modal=\"true\"` tells a screen reader the page behind is inert;\n * the trap is what makes that true for the keyboard.\n *\n * The `dialog` role needs an accessible name: a `title` supplies it via\n * `aria-labelledby`, and a titleless dialog should pass `aria-label`. The\n * header heading is only rendered when there is a title, so a close-button-only\n * header does not leave an empty `h3` behind.\n */\nexport function Modal({\n open,\n onClose,\n title,\n children,\n footer,\n size = \"md\",\n closeOnBackdrop = true,\n closeOnEsc = true,\n className,\n hideCloseButton = false,\n fullscreen = false,\n fullscreenOnMobile = false,\n \"aria-label\": ariaLabel,\n}: ModalProps) {\n const titleId = useId();\n const dialogRef = useRef<HTMLDivElement>(null);\n useFocusTrap(dialogRef, open);\n useEscapeLayer(open, closeOnEsc ? onClose : null);\n useEffect(() => {\n if (!open) return;\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [open]);\n\n const portalHost = usePortalHost();\n\n if (!open || !portalHost) return null;\n\n return createPortal(\n <div\n className={styles.overlay}\n role=\"presentation\"\n onClick={() => {\n if (closeOnBackdrop) onClose();\n }}\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n tabIndex={-1}\n aria-labelledby={title ? titleId : undefined}\n aria-label={title ? undefined : ariaLabel}\n className={cn(\n styles.dialog,\n sizeClassName(size),\n fullscreen && styles.fullscreen,\n fullscreenOnMobile && styles.fullscreenOnMobile,\n className,\n )}\n onClick={(event) => event.stopPropagation()}\n >\n {(title || !hideCloseButton) && (\n <header className={styles.header}>\n {title && (\n <h3 id={titleId} className={styles.title}>\n {title}\n </h3>\n )}\n {!hideCloseButton && (\n <button\n type=\"button\"\n className={styles.close}\n aria-label=\"Fechar\"\n onClick={onClose}\n >\n <CloseIcon />\n </button>\n )}\n </header>\n )}\n <div className={styles.body}>{children}</div>\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </div>\n </div>,\n portalHost,\n );\n}\n\nfunction sizeClassName(size: ModalSize): string | undefined {\n if (size === \"2xl\") return styles.size2xl;\n if (size === \"3xl\") return styles.size3xl;\n return styles[size];\n}\n\nfunction CloseIcon() {\n return (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M6 6l12 12M6 18L18 6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"8QAwDA,SAAgB,EAAM,CAClB,OACA,UACA,QACA,WACA,SACA,OAAO,KACP,kBAAkB,GAClB,aAAa,GACb,YACA,kBAAkB,GAClB,aAAa,GACb,qBAAqB,GACrB,aAAc,GACH,CACX,IAAM,GAAA,EAAU,EAAA,MAAA,CAAM,EAChB,GAAA,EAAY,EAAA,OAAA,CAAuB,IAAI,EAC7C,EAAA,aAAa,EAAW,CAAI,EAC5B,EAAA,eAAe,EAAM,EAAa,EAAU,IAAI,GAChD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAM,OACX,IAAM,EAAmB,SAAS,KAAK,MAAM,SAE7C,MADA,UAAS,KAAK,MAAM,SAAW,aAClB,CACT,SAAS,KAAK,MAAM,SAAW,CACnC,CACJ,EAAG,CAAC,CAAI,CAAC,EAET,IAAM,EAAa,EAAA,cAAc,EAIjC,MAFI,CAAC,GAAQ,CAAC,EAAmB,MAEjC,EAAO,EAAA,aAAA,EACH,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,QAClB,KAAK,eACL,YAAe,CACP,GAAiB,EAAQ,CACjC,EAEA,UAAA,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,IAAK,EACL,KAAK,SACL,aAAW,OACX,SAAU,GACV,kBAAiB,EAAQ,EAAU,IAAA,GACnC,aAAY,EAAQ,IAAA,GAAY,EAChC,UAAW,EAAA,GACP,EAAA,QAAO,OACP,EAAc,CAAI,EAClB,GAAc,EAAA,QAAO,WACrB,GAAsB,EAAA,QAAO,mBAC7B,CACJ,EACA,QAAU,GAAU,EAAM,gBAAgB,EAd9C,SAAA,EAgBM,GAAS,CAAC,KACR,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAA1B,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,GAAI,EAAS,UAAW,EAAA,QAAO,MAC9B,SAAA,CACD,CAAA,EAEP,CAAC,IACE,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,aAAW,SACX,QAAS,EAET,UAAA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAER,KAEZ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAC3C,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,CAC5D,GACJ,CAAA,EACL,CACJ,CACJ,CAEA,SAAS,EAAc,EAAqC,CAGxD,OAFI,IAAS,MAAc,EAAA,QAAO,QAC9B,IAAS,MAAc,EAAA,QAAO,QAC3B,EAAA,QAAO,EAClB,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}