@apipass/design-system 1.20.1 → 1.21.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.
- package/dist/lib/embedded-styles.js +1 -1
- package/dist/lib/lib.index.cjs +1 -1
- package/dist/lib/lib.index.js +688 -687
- package/dist/lib/style.css +1 -1
- package/dist/lib/ui/components/DataTable/DataTable.cjs +1 -1
- package/dist/lib/ui/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/lib/ui/components/DataTable/DataTable.js +117 -171
- package/dist/lib/ui/components/DataTable/DataTable.js.map +1 -1
- package/dist/lib/ui/primitives/Pagination/Pagination.cjs +1 -1
- package/dist/lib/ui/primitives/Pagination/Pagination.cjs.map +1 -1
- package/dist/lib/ui/primitives/Pagination/Pagination.js +70 -55
- package/dist/lib/ui/primitives/Pagination/Pagination.js.map +1 -1
- package/dist/types/ui/primitives/Pagination/Pagination.d.ts +12 -2
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),h=require("react");;/* empty css */const Z=require("../../primitives/Search/Search.cjs"),t=require("../../primitives/Table/Table.cjs"),_=require("../../icons/IconChevronDown.cjs"),A=require("../../icons/IconChevronRight.cjs"),K=require("../../primitives/Pagination/Pagination.cjs"),ee=require("../../primitives/PermissionGate/PermissionGate.cjs"),ae=require("../../layout/Flex/Flex.cjs");function se({action:n,item:c}){const o=n.isAllowed!==!1,d=e.jsx("button",{className:"data-table-row-action-btn","aria-label":n.label,disabled:!o,onClick:o?()=>n.onPress(c):void 0,children:n.icon});return o?d:e.jsx(ee.PermissionGate,{isAllowed:!1,noAccessLabel:n.noAccessLabel??"",children:d})}function M({columns:n,data:c,actions:o,rowActions:d,rowActionsLabel:R="",searchPlaceholder:L="Search...",searchKeys:g=[],isLoading:k=!1,loadingMessage:N="Loading...",emptyMessage:D="No records found",previousLabel:I="Previous",nextLabel:$="Next",pageSize:b=10,nestedColumns:T,getNestedData:S,nestedLoadingMessage:B="Loading...",nestedEmptyMessage:E="No records found",nestedSearchKeys:F,nestedSearchPlaceholder:G,nestedActions:j,nestedPageSize:H}){const[p,O]=h.useState(""),[Q,v]=h.useState(1),[z,J]=h.useState(new Set),[P,w]=h.useState(new Map),q=!!S&&!!T,f=!!d;function U(a){const s=a.id;J(r=>{const i=new Set(r);if(i.has(s))i.delete(s);else if(i.add(s),!P.has(s)){const l=S(a);l instanceof Promise?(w(u=>new Map(u).set(s,{status:"loading",data:[]})),l.then(u=>{w(Y=>new Map(Y).set(s,{status:"loaded",data:u}))})):w(u=>new Map(u).set(s,{status:"loaded",data:l}))}return i})}const x=g.length>0,V=x||!!o,m=h.useMemo(()=>{if(!p.trim()||!x)return c;const a=p.toLowerCase();return c.filter(s=>g.some(r=>String(s[r]??"").toLowerCase().includes(a)))},[c,p,g,x]),y=Math.max(1,Math.ceil(m.length/b)),C=Math.min(Q,y),W=m.slice((C-1)*b,C*b);function X(a,s){return s.render?s.render(a):String(a[s.key]??"")}return e.jsxs(ae.Flex,{direction:"column",gap:"300",className:"data-table",children:[V&&e.jsxs("div",{className:"data-table-toolbar",children:[x&&e.jsx(Z.Search,{placeholder:L,onSearch:a=>{O(a),v(1)},className:"data-table-search"}),o&&e.jsx("div",{className:"data-table-actions",children:o})]}),k?e.jsx("div",{role:"status","aria-live":"polite","aria-label":N,className:"data-table-status",children:N}):m.length===0?e.jsx("div",{role:"status",className:"data-table-status data-table-empty",children:D}):e.jsxs(t.Table,{grid:!0,children:[e.jsxs(t.TableHead,{children:[q&&e.jsx(t.TableColumn,{className:"data-table-expand-col"}),n.map((a,s)=>e.jsx(t.TableColumn,{align:a.align,isRowHeader:s===0,children:a.label},String(a.key))),f&&e.jsx(t.TableColumn,{align:"right",className:"data-table-row-actions-col",children:R})]}),e.jsx(t.TableBody,{children:W.flatMap(a=>{const s=z.has(a.id),r=P.get(a.id),i=[e.jsxs(t.TableRow,{id:a.id,children:[q&&e.jsx(t.TableCell,{className:"data-table-expand-cell",children:e.jsx("button",{className:"data-table-expand-btn","aria-label":s?"Collapse row":"Expand row","aria-expanded":s,onClick:()=>U(a),children:s?e.jsx(_.IconChevronDown,{size:"16"}):e.jsx(A.IconChevronRight,{size:"16"})})}),n.map(l=>e.jsx(t.TableCell,{align:l.align,children:X(a,l)},String(l.key))),f&&e.jsx(t.TableCell,{align:"right",className:"data-table-row-actions-cell",children:e.jsx("div",{className:"data-table-row-actions",children:(typeof d=="function"?d(a):d).map(l=>e.jsx(se,{action:l,item:a},l.key))})})]},a.id)];return s&&i.push(e.jsx(t.TableRow,{id:`${a.id}-nested`,className:"data-table-nested-row",children:e.jsx(t.TableCell,{colSpan:n.length+1+(f?1:0),className:"data-table-nested-cell",children:e.jsx(M,{columns:T,data:(r==null?void 0:r.data)??[],isLoading:!r||r.status==="loading",loadingMessage:B,emptyMessage:E,searchKeys:F,searchPlaceholder:G,actions:j==null?void 0:j(a),pageSize:H??b})})},`${a.id}-nested`)),i})})]}),e.jsx(K.Pagination,{page:C,totalPages:y,onPageChange:v,previousLabel:I,nextLabel:$})]})}exports.DataTable=M;
|
|
2
2
|
//# sourceMappingURL=DataTable.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.cjs","sources":["../../../../../src/ui/components/DataTable/DataTable.tsx"],"sourcesContent":["import {\n PermissionGate,\n Search,\n Table,\n TableBody,\n TableCell,\n TableColumn,\n TableHead,\n TableRow,\n} from \"primitives\";\nimport { Flex } from \"layout\";\nimport { IconArrowLeft, IconArrowRight, IconChevronDown, IconChevronRight } from \"icons\";\nimport type { ReactNode } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport \"./datatable.css\";\n\nfunction buildPages(current: number, total: number): (number | \"...\")[] {\n if (total <= 6) return Array.from({ length: total }, (_, i) => i + 1);\n const head = [1, 2, 3];\n const tail = [total - 1, total];\n if (current <= 4) return [...head, \"...\", ...tail];\n if (current >= total - 3) return [1, 2, \"...\", total - 2, total - 1, total];\n return [1, \"...\", current - 1, current, current + 1, \"...\", total];\n}\n\ninterface PaginationProps {\n currentPage: number;\n totalPages: number;\n onPageChange: (page: number) => void;\n previousLabel: string;\n nextLabel: string;\n}\n\nfunction Pagination({\n currentPage,\n totalPages,\n onPageChange,\n previousLabel,\n nextLabel,\n}: PaginationProps) {\n const pages = buildPages(currentPage, totalPages);\n return (\n <div className=\"data-table-pagination\">\n <button\n onClick={() => onPageChange(Math.max(1, currentPage - 1))}\n disabled={currentPage === 1}\n className=\"data-table-page-btn data-table-page-nav\"\n aria-label={previousLabel}\n >\n <IconArrowLeft size=\"16\" />\n {previousLabel}\n </button>\n <div className=\"data-table-page-numbers\">\n {pages.map((p, i) =>\n p === \"...\" ? (\n <span key={`ellipsis-${i}`} className=\"data-table-page-ellipsis\">\n ...\n </span>\n ) : (\n <button\n key={p}\n onClick={() => onPageChange(p as number)}\n className={`data-table-page-btn${p === currentPage ? \" data-table-page-btn-active\" : \"\"}`}\n >\n {p}\n </button>\n ),\n )}\n </div>\n <button\n onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}\n disabled={currentPage === totalPages}\n className=\"data-table-page-btn data-table-page-nav\"\n aria-label={nextLabel}\n >\n {nextLabel}\n <IconArrowRight size=\"16\" />\n </button>\n </div>\n );\n}\n\nexport interface Column<T> {\n key: keyof T | string;\n label: string;\n align?: \"start\" | \"center\" | \"right\";\n render?: (item: T) => ReactNode;\n}\n\nexport interface RowActionDef<T> {\n /** Unique key for this action. */\n key: string;\n /** aria-label for the button. */\n label: string;\n /** Icon element rendered inside the button. */\n icon: ReactNode;\n /** When false, button is disabled and a tooltip with `noAccessLabel` is shown. Default: true. */\n isAllowed?: boolean;\n /** Tooltip text shown when `isAllowed` is false. */\n noAccessLabel?: string;\n /** Called with the row item when the button is pressed. */\n onPress: (item: T) => void;\n}\n\nfunction RowActionButton<T>({\n action,\n item,\n}: {\n action: RowActionDef<T>;\n item: T;\n}) {\n const allowed = action.isAllowed !== false;\n const btn = (\n <button\n className=\"data-table-row-action-btn\"\n aria-label={action.label}\n disabled={!allowed}\n onClick={allowed ? () => action.onPress(item) : undefined}\n >\n {action.icon}\n </button>\n );\n if (allowed) return btn;\n return (\n <PermissionGate isAllowed={false} noAccessLabel={action.noAccessLabel ?? \"\"}>\n {btn}\n </PermissionGate>\n );\n}\n\ntype NestedStatus = { status: \"loading\"; data: never[] } | { status: \"loaded\"; data: any[] };\n\nexport interface DataTableProps<T extends { id: string }> {\n columns: Column<T>[];\n data: T[];\n actions?: ReactNode;\n /** Per-row action buttons rendered in a dedicated last column. */\n rowActions?: RowActionDef<T>[] | ((item: T) => RowActionDef<T>[]);\n /** Label for the row actions column header. Defaults to empty string. */\n rowActionsLabel?: string;\n searchPlaceholder?: string;\n searchKeys?: (keyof T)[];\n isLoading?: boolean;\n loadingMessage?: string;\n emptyMessage?: string;\n previousLabel?: string;\n nextLabel?: string;\n pageSize?: number;\n nestedColumns?: Column<any>[];\n getNestedData?: (item: T) => any[] | Promise<any[]>;\n nestedLoadingMessage?: string;\n nestedEmptyMessage?: string;\n nestedSearchKeys?: (keyof any)[];\n nestedSearchPlaceholder?: string;\n nestedActions?: (item: T) => ReactNode;\n nestedPageSize?: number;\n}\n\nexport function DataTable<T extends { id: string }>({\n columns,\n data,\n actions,\n rowActions,\n rowActionsLabel = \"\",\n searchPlaceholder = \"Search...\",\n searchKeys = [],\n isLoading = false,\n loadingMessage = \"Loading...\",\n emptyMessage = \"No records found\",\n previousLabel = \"Previous\",\n nextLabel = \"Next\",\n pageSize = 10,\n nestedColumns,\n getNestedData,\n nestedLoadingMessage = \"Loading...\",\n nestedEmptyMessage = \"No records found\",\n nestedSearchKeys,\n nestedSearchPlaceholder,\n nestedActions,\n nestedPageSize,\n}: DataTableProps<T>) {\n const [query, setQuery] = useState(\"\");\n const [page, setPage] = useState(1);\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n const [nestedCache, setNestedCache] = useState<Map<string, NestedStatus>>(new Map());\n\n const hasNested = !!getNestedData && !!nestedColumns;\n const hasRowActions = !!rowActions;\n\n function toggleRow(item: T) {\n const id = item.id;\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n if (!nestedCache.has(id)) {\n const result = getNestedData!(item);\n if (result instanceof Promise) {\n setNestedCache((c) => new Map(c).set(id, { status: \"loading\", data: [] }));\n result.then((rows) => {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: rows }));\n });\n } else {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: result }));\n }\n }\n }\n return next;\n });\n }\n\n const hasSearch = searchKeys.length > 0;\n const hasToolbar = hasSearch || !!actions;\n\n const filtered = useMemo(() => {\n if (!query.trim() || !hasSearch) return data;\n const lower = query.toLowerCase();\n return data.filter((item) =>\n searchKeys.some((k) =>\n String(item[k] ?? \"\")\n .toLowerCase()\n .includes(lower),\n ),\n );\n }, [data, query, searchKeys, hasSearch]);\n\n const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));\n const currentPage = Math.min(page, totalPages);\n const pageData = filtered.slice(\n (currentPage - 1) * pageSize,\n currentPage * pageSize,\n );\n\n function getCell(item: T, col: Column<T>): ReactNode {\n if (col.render) return col.render(item);\n return String((item as Record<string, unknown>)[col.key as string] ?? \"\");\n }\n\n return (\n <Flex direction=\"column\" gap=\"300\" className=\"data-table\">\n {hasToolbar && (\n <div className=\"data-table-toolbar\">\n {hasSearch && (\n <Search\n placeholder={searchPlaceholder}\n onSearch={(v) => {\n setQuery(v);\n setPage(1);\n }}\n className=\"data-table-search\"\n />\n )}\n {actions && <div className=\"data-table-actions\">{actions}</div>}\n </div>\n )}\n\n {isLoading ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-label={loadingMessage}\n className=\"data-table-status\"\n >\n {loadingMessage}\n </div>\n ) : filtered.length === 0 ? (\n <div role=\"status\" className=\"data-table-status data-table-empty\">\n {emptyMessage}\n </div>\n ) : (\n <Table grid>\n <TableHead>\n {hasNested && <TableColumn className=\"data-table-expand-col\" />}\n {columns.map((col, idx) => (\n <TableColumn\n key={String(col.key)}\n align={col.align}\n isRowHeader={idx === 0}\n >\n {col.label}\n </TableColumn>\n ))}\n {hasRowActions && (\n <TableColumn align=\"right\" className=\"data-table-row-actions-col\">\n {rowActionsLabel}\n </TableColumn>\n )}\n </TableHead>\n <TableBody>\n {pageData.flatMap((item) => {\n const isExpanded = expanded.has(item.id);\n const cached = nestedCache.get(item.id);\n const rows: ReactNode[] = [\n <TableRow key={item.id} id={item.id}>\n {hasNested && (\n <TableCell className=\"data-table-expand-cell\">\n <button\n className=\"data-table-expand-btn\"\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n aria-expanded={isExpanded}\n onClick={() => toggleRow(item)}\n >\n {isExpanded ? (\n <IconChevronDown size=\"16\" />\n ) : (\n <IconChevronRight size=\"16\" />\n )}\n </button>\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={String(col.key)} align={col.align}>\n {getCell(item, col)}\n </TableCell>\n ))}\n {hasRowActions && (\n <TableCell align=\"right\" className=\"data-table-row-actions-cell\">\n <div className=\"data-table-row-actions\">\n {(typeof rowActions === \"function\"\n ? rowActions(item)\n : rowActions!\n ).map((action) => (\n <RowActionButton key={action.key} action={action} item={item} />\n ))}\n </div>\n </TableCell>\n )}\n </TableRow>,\n ];\n if (isExpanded) {\n rows.push(\n <TableRow key={`${item.id}-nested`} id={`${item.id}-nested`} className=\"data-table-nested-row\">\n <TableCell\n colSpan={columns.length + 1 + (hasRowActions ? 1 : 0)}\n className=\"data-table-nested-cell\"\n >\n <DataTable\n columns={nestedColumns!}\n data={cached?.data ?? []}\n isLoading={!cached || cached.status === \"loading\"}\n loadingMessage={nestedLoadingMessage}\n emptyMessage={nestedEmptyMessage}\n searchKeys={nestedSearchKeys}\n searchPlaceholder={nestedSearchPlaceholder}\n actions={nestedActions?.(item)}\n pageSize={nestedPageSize ?? pageSize}\n />\n </TableCell>\n </TableRow>,\n );\n }\n return rows;\n })}\n </TableBody>\n </Table>\n )}\n\n {totalPages > 1 && (\n <Pagination\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={setPage}\n previousLabel={previousLabel}\n nextLabel={nextLabel}\n />\n )}\n </Flex>\n );\n}\n"],"names":["buildPages","current","total","_","i","head","tail","Pagination","currentPage","totalPages","onPageChange","previousLabel","nextLabel","pages","jsxs","jsx","IconArrowLeft","p","IconArrowRight","RowActionButton","action","item","allowed","btn","PermissionGate","DataTable","columns","data","actions","rowActions","rowActionsLabel","searchPlaceholder","searchKeys","isLoading","loadingMessage","emptyMessage","pageSize","nestedColumns","getNestedData","nestedLoadingMessage","nestedEmptyMessage","nestedSearchKeys","nestedSearchPlaceholder","nestedActions","nestedPageSize","query","setQuery","useState","page","setPage","expanded","setExpanded","nestedCache","setNestedCache","hasNested","hasRowActions","toggleRow","id","prev","next","result","c","rows","hasSearch","hasToolbar","filtered","useMemo","lower","k","pageData","getCell","col","Flex","Search","v","Table","TableHead","TableColumn","idx","TableBody","isExpanded","cached","TableRow","TableCell","IconChevronDown","IconChevronRight"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataTable.cjs","sources":["../../../../../src/ui/components/DataTable/DataTable.tsx"],"sourcesContent":["import {\n Pagination,\n PermissionGate,\n Search,\n Table,\n TableBody,\n TableCell,\n TableColumn,\n TableHead,\n TableRow,\n} from \"primitives\";\nimport { Flex } from \"layout\";\nimport { IconChevronDown, IconChevronRight } from \"icons\";\nimport type { ReactNode } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport \"./datatable.css\";\n\nexport interface Column<T> {\n key: keyof T | string;\n label: string;\n align?: \"start\" | \"center\" | \"right\";\n render?: (item: T) => ReactNode;\n}\n\nexport interface RowActionDef<T> {\n /** Unique key for this action. */\n key: string;\n /** aria-label for the button. */\n label: string;\n /** Icon element rendered inside the button. */\n icon: ReactNode;\n /** When false, button is disabled and a tooltip with `noAccessLabel` is shown. Default: true. */\n isAllowed?: boolean;\n /** Tooltip text shown when `isAllowed` is false. */\n noAccessLabel?: string;\n /** Called with the row item when the button is pressed. */\n onPress: (item: T) => void;\n}\n\nfunction RowActionButton<T>({\n action,\n item,\n}: {\n action: RowActionDef<T>;\n item: T;\n}) {\n const allowed = action.isAllowed !== false;\n const btn = (\n <button\n className=\"data-table-row-action-btn\"\n aria-label={action.label}\n disabled={!allowed}\n onClick={allowed ? () => action.onPress(item) : undefined}\n >\n {action.icon}\n </button>\n );\n if (allowed) return btn;\n return (\n <PermissionGate isAllowed={false} noAccessLabel={action.noAccessLabel ?? \"\"}>\n {btn}\n </PermissionGate>\n );\n}\n\ntype NestedStatus = { status: \"loading\"; data: never[] } | { status: \"loaded\"; data: any[] };\n\nexport interface DataTableProps<T extends { id: string }> {\n columns: Column<T>[];\n data: T[];\n actions?: ReactNode;\n /** Per-row action buttons rendered in a dedicated last column. */\n rowActions?: RowActionDef<T>[] | ((item: T) => RowActionDef<T>[]);\n /** Label for the row actions column header. Defaults to empty string. */\n rowActionsLabel?: string;\n searchPlaceholder?: string;\n searchKeys?: (keyof T)[];\n isLoading?: boolean;\n loadingMessage?: string;\n emptyMessage?: string;\n previousLabel?: string;\n nextLabel?: string;\n pageSize?: number;\n nestedColumns?: Column<any>[];\n getNestedData?: (item: T) => any[] | Promise<any[]>;\n nestedLoadingMessage?: string;\n nestedEmptyMessage?: string;\n nestedSearchKeys?: (keyof any)[];\n nestedSearchPlaceholder?: string;\n nestedActions?: (item: T) => ReactNode;\n nestedPageSize?: number;\n}\n\nexport function DataTable<T extends { id: string }>({\n columns,\n data,\n actions,\n rowActions,\n rowActionsLabel = \"\",\n searchPlaceholder = \"Search...\",\n searchKeys = [],\n isLoading = false,\n loadingMessage = \"Loading...\",\n emptyMessage = \"No records found\",\n previousLabel = \"Previous\",\n nextLabel = \"Next\",\n pageSize = 10,\n nestedColumns,\n getNestedData,\n nestedLoadingMessage = \"Loading...\",\n nestedEmptyMessage = \"No records found\",\n nestedSearchKeys,\n nestedSearchPlaceholder,\n nestedActions,\n nestedPageSize,\n}: DataTableProps<T>) {\n const [query, setQuery] = useState(\"\");\n const [page, setPage] = useState(1);\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n const [nestedCache, setNestedCache] = useState<Map<string, NestedStatus>>(new Map());\n\n const hasNested = !!getNestedData && !!nestedColumns;\n const hasRowActions = !!rowActions;\n\n function toggleRow(item: T) {\n const id = item.id;\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n if (!nestedCache.has(id)) {\n const result = getNestedData!(item);\n if (result instanceof Promise) {\n setNestedCache((c) => new Map(c).set(id, { status: \"loading\", data: [] }));\n result.then((rows) => {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: rows }));\n });\n } else {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: result }));\n }\n }\n }\n return next;\n });\n }\n\n const hasSearch = searchKeys.length > 0;\n const hasToolbar = hasSearch || !!actions;\n\n const filtered = useMemo(() => {\n if (!query.trim() || !hasSearch) return data;\n const lower = query.toLowerCase();\n return data.filter((item) =>\n searchKeys.some((k) =>\n String(item[k] ?? \"\")\n .toLowerCase()\n .includes(lower),\n ),\n );\n }, [data, query, searchKeys, hasSearch]);\n\n const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));\n const currentPage = Math.min(page, totalPages);\n const pageData = filtered.slice(\n (currentPage - 1) * pageSize,\n currentPage * pageSize,\n );\n\n function getCell(item: T, col: Column<T>): ReactNode {\n if (col.render) return col.render(item);\n return String((item as Record<string, unknown>)[col.key as string] ?? \"\");\n }\n\n return (\n <Flex direction=\"column\" gap=\"300\" className=\"data-table\">\n {hasToolbar && (\n <div className=\"data-table-toolbar\">\n {hasSearch && (\n <Search\n placeholder={searchPlaceholder}\n onSearch={(v) => {\n setQuery(v);\n setPage(1);\n }}\n className=\"data-table-search\"\n />\n )}\n {actions && <div className=\"data-table-actions\">{actions}</div>}\n </div>\n )}\n\n {isLoading ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-label={loadingMessage}\n className=\"data-table-status\"\n >\n {loadingMessage}\n </div>\n ) : filtered.length === 0 ? (\n <div role=\"status\" className=\"data-table-status data-table-empty\">\n {emptyMessage}\n </div>\n ) : (\n <Table grid>\n <TableHead>\n {hasNested && <TableColumn className=\"data-table-expand-col\" />}\n {columns.map((col, idx) => (\n <TableColumn\n key={String(col.key)}\n align={col.align}\n isRowHeader={idx === 0}\n >\n {col.label}\n </TableColumn>\n ))}\n {hasRowActions && (\n <TableColumn align=\"right\" className=\"data-table-row-actions-col\">\n {rowActionsLabel}\n </TableColumn>\n )}\n </TableHead>\n <TableBody>\n {pageData.flatMap((item) => {\n const isExpanded = expanded.has(item.id);\n const cached = nestedCache.get(item.id);\n const rows: ReactNode[] = [\n <TableRow key={item.id} id={item.id}>\n {hasNested && (\n <TableCell className=\"data-table-expand-cell\">\n <button\n className=\"data-table-expand-btn\"\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n aria-expanded={isExpanded}\n onClick={() => toggleRow(item)}\n >\n {isExpanded ? (\n <IconChevronDown size=\"16\" />\n ) : (\n <IconChevronRight size=\"16\" />\n )}\n </button>\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={String(col.key)} align={col.align}>\n {getCell(item, col)}\n </TableCell>\n ))}\n {hasRowActions && (\n <TableCell align=\"right\" className=\"data-table-row-actions-cell\">\n <div className=\"data-table-row-actions\">\n {(typeof rowActions === \"function\"\n ? rowActions(item)\n : rowActions!\n ).map((action) => (\n <RowActionButton key={action.key} action={action} item={item} />\n ))}\n </div>\n </TableCell>\n )}\n </TableRow>,\n ];\n if (isExpanded) {\n rows.push(\n <TableRow key={`${item.id}-nested`} id={`${item.id}-nested`} className=\"data-table-nested-row\">\n <TableCell\n colSpan={columns.length + 1 + (hasRowActions ? 1 : 0)}\n className=\"data-table-nested-cell\"\n >\n <DataTable\n columns={nestedColumns!}\n data={cached?.data ?? []}\n isLoading={!cached || cached.status === \"loading\"}\n loadingMessage={nestedLoadingMessage}\n emptyMessage={nestedEmptyMessage}\n searchKeys={nestedSearchKeys}\n searchPlaceholder={nestedSearchPlaceholder}\n actions={nestedActions?.(item)}\n pageSize={nestedPageSize ?? pageSize}\n />\n </TableCell>\n </TableRow>,\n );\n }\n return rows;\n })}\n </TableBody>\n </Table>\n )}\n\n <Pagination\n page={currentPage}\n totalPages={totalPages}\n onPageChange={setPage}\n previousLabel={previousLabel}\n nextLabel={nextLabel}\n />\n </Flex>\n );\n}\n"],"names":["RowActionButton","action","item","allowed","btn","jsx","PermissionGate","DataTable","columns","data","actions","rowActions","rowActionsLabel","searchPlaceholder","searchKeys","isLoading","loadingMessage","emptyMessage","previousLabel","nextLabel","pageSize","nestedColumns","getNestedData","nestedLoadingMessage","nestedEmptyMessage","nestedSearchKeys","nestedSearchPlaceholder","nestedActions","nestedPageSize","query","setQuery","useState","page","setPage","expanded","setExpanded","nestedCache","setNestedCache","hasNested","hasRowActions","toggleRow","id","prev","next","result","c","rows","hasSearch","hasToolbar","filtered","useMemo","lower","k","totalPages","currentPage","pageData","getCell","col","Flex","jsxs","Search","v","Table","TableHead","TableColumn","idx","TableBody","isExpanded","cached","TableRow","TableCell","IconChevronDown","IconChevronRight","Pagination"],"mappings":"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"}
|
|
@@ -1,229 +1,175 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useState as
|
|
1
|
+
import { jsxs as h, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { useState as u, useMemo as A } from "react";
|
|
3
3
|
/* empty css */
|
|
4
4
|
import { Search as K } from "../../primitives/Search/Search.js";
|
|
5
|
-
import { Table as aa, TableHead as ea, TableColumn as
|
|
5
|
+
import { Table as aa, TableHead as ea, TableColumn as M, TableBody as ta, TableRow as E, TableCell as g } from "../../primitives/Table/Table.js";
|
|
6
6
|
import { IconChevronDown as la } from "../../icons/IconChevronDown.js";
|
|
7
7
|
import { IconChevronRight as na } from "../../icons/IconChevronRight.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const s = [1, 2, 3], i = [n - 1, n];
|
|
15
|
-
return t <= 4 ? [...s, "...", ...i] : t >= n - 3 ? [1, 2, "...", n - 2, n - 1, n] : [1, "...", t - 1, t, t + 1, "...", n];
|
|
16
|
-
}
|
|
17
|
-
function ca({
|
|
18
|
-
currentPage: t,
|
|
19
|
-
totalPages: n,
|
|
20
|
-
onPageChange: s,
|
|
21
|
-
previousLabel: i,
|
|
22
|
-
nextLabel: b
|
|
23
|
-
}) {
|
|
24
|
-
const m = oa(t, n);
|
|
25
|
-
return /* @__PURE__ */ c("div", { className: "data-table-pagination", children: [
|
|
26
|
-
/* @__PURE__ */ c(
|
|
27
|
-
"button",
|
|
28
|
-
{
|
|
29
|
-
onClick: () => s(Math.max(1, t - 1)),
|
|
30
|
-
disabled: t === 1,
|
|
31
|
-
className: "data-table-page-btn data-table-page-nav",
|
|
32
|
-
"aria-label": i,
|
|
33
|
-
children: [
|
|
34
|
-
/* @__PURE__ */ a(ia, { size: "16" }),
|
|
35
|
-
i
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
),
|
|
39
|
-
/* @__PURE__ */ a("div", { className: "data-table-page-numbers", children: m.map(
|
|
40
|
-
(d, N) => d === "..." ? /* @__PURE__ */ a("span", { className: "data-table-page-ellipsis", children: "..." }, `ellipsis-${N}`) : /* @__PURE__ */ a(
|
|
41
|
-
"button",
|
|
42
|
-
{
|
|
43
|
-
onClick: () => s(d),
|
|
44
|
-
className: `data-table-page-btn${d === t ? " data-table-page-btn-active" : ""}`,
|
|
45
|
-
children: d
|
|
46
|
-
},
|
|
47
|
-
d
|
|
48
|
-
)
|
|
49
|
-
) }),
|
|
50
|
-
/* @__PURE__ */ c(
|
|
51
|
-
"button",
|
|
52
|
-
{
|
|
53
|
-
onClick: () => s(Math.min(n, t + 1)),
|
|
54
|
-
disabled: t === n,
|
|
55
|
-
className: "data-table-page-btn data-table-page-nav",
|
|
56
|
-
"aria-label": b,
|
|
57
|
-
children: [
|
|
58
|
-
b,
|
|
59
|
-
/* @__PURE__ */ a(ra, { size: "16" })
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
)
|
|
63
|
-
] });
|
|
64
|
-
}
|
|
65
|
-
function ha({
|
|
66
|
-
action: t,
|
|
67
|
-
item: n
|
|
8
|
+
import { Pagination as sa } from "../../primitives/Pagination/Pagination.js";
|
|
9
|
+
import { PermissionGate as ra } from "../../primitives/PermissionGate/PermissionGate.js";
|
|
10
|
+
import { Flex as oa } from "../../layout/Flex/Flex.js";
|
|
11
|
+
function da({
|
|
12
|
+
action: n,
|
|
13
|
+
item: i
|
|
68
14
|
}) {
|
|
69
|
-
const
|
|
15
|
+
const r = n.isAllowed !== !1, o = /* @__PURE__ */ e(
|
|
70
16
|
"button",
|
|
71
17
|
{
|
|
72
18
|
className: "data-table-row-action-btn",
|
|
73
|
-
"aria-label":
|
|
74
|
-
disabled: !
|
|
75
|
-
onClick:
|
|
76
|
-
children:
|
|
19
|
+
"aria-label": n.label,
|
|
20
|
+
disabled: !r,
|
|
21
|
+
onClick: r ? () => n.onPress(i) : void 0,
|
|
22
|
+
children: n.icon
|
|
77
23
|
}
|
|
78
24
|
);
|
|
79
|
-
return
|
|
25
|
+
return r ? o : /* @__PURE__ */ e(ra, { isAllowed: !1, noAccessLabel: n.noAccessLabel ?? "", children: o });
|
|
80
26
|
}
|
|
81
|
-
function
|
|
82
|
-
columns:
|
|
83
|
-
data:
|
|
84
|
-
actions:
|
|
85
|
-
rowActions:
|
|
86
|
-
rowActionsLabel:
|
|
87
|
-
searchPlaceholder:
|
|
88
|
-
searchKeys:
|
|
89
|
-
isLoading:
|
|
90
|
-
loadingMessage:
|
|
91
|
-
emptyMessage:
|
|
92
|
-
previousLabel:
|
|
93
|
-
nextLabel:
|
|
94
|
-
pageSize:
|
|
95
|
-
nestedColumns:
|
|
96
|
-
getNestedData:
|
|
97
|
-
nestedLoadingMessage:
|
|
98
|
-
nestedEmptyMessage:
|
|
99
|
-
nestedSearchKeys:
|
|
100
|
-
nestedSearchPlaceholder:
|
|
101
|
-
nestedActions:
|
|
102
|
-
nestedPageSize:
|
|
27
|
+
function ia({
|
|
28
|
+
columns: n,
|
|
29
|
+
data: i,
|
|
30
|
+
actions: r,
|
|
31
|
+
rowActions: o,
|
|
32
|
+
rowActionsLabel: j = "",
|
|
33
|
+
searchPlaceholder: B = "Search...",
|
|
34
|
+
searchKeys: m = [],
|
|
35
|
+
isLoading: D = !1,
|
|
36
|
+
loadingMessage: P = "Loading...",
|
|
37
|
+
emptyMessage: H = "No records found",
|
|
38
|
+
previousLabel: I = "Previous",
|
|
39
|
+
nextLabel: $ = "Next",
|
|
40
|
+
pageSize: b = 10,
|
|
41
|
+
nestedColumns: S,
|
|
42
|
+
getNestedData: y,
|
|
43
|
+
nestedLoadingMessage: q = "Loading...",
|
|
44
|
+
nestedEmptyMessage: F = "No records found",
|
|
45
|
+
nestedSearchKeys: G,
|
|
46
|
+
nestedSearchPlaceholder: Q,
|
|
47
|
+
nestedActions: f,
|
|
48
|
+
nestedPageSize: z
|
|
103
49
|
}) {
|
|
104
|
-
const [
|
|
105
|
-
function
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
else if (
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
})) :
|
|
50
|
+
const [w, J] = u(""), [O, T] = u(1), [U, V] = u(/* @__PURE__ */ new Set()), [L, N] = u(/* @__PURE__ */ new Map()), k = !!y && !!S, x = !!o;
|
|
51
|
+
function W(a) {
|
|
52
|
+
const t = a.id;
|
|
53
|
+
V((s) => {
|
|
54
|
+
const d = new Set(s);
|
|
55
|
+
if (d.has(t))
|
|
56
|
+
d.delete(t);
|
|
57
|
+
else if (d.add(t), !L.has(t)) {
|
|
58
|
+
const l = y(a);
|
|
59
|
+
l instanceof Promise ? (N((c) => new Map(c).set(t, { status: "loading", data: [] })), l.then((c) => {
|
|
60
|
+
N((_) => new Map(_).set(t, { status: "loaded", data: c }));
|
|
61
|
+
})) : N((c) => new Map(c).set(t, { status: "loaded", data: l }));
|
|
116
62
|
}
|
|
117
|
-
return
|
|
63
|
+
return d;
|
|
118
64
|
});
|
|
119
65
|
}
|
|
120
|
-
const
|
|
121
|
-
if (!
|
|
122
|
-
const
|
|
123
|
-
return
|
|
124
|
-
(
|
|
125
|
-
(
|
|
66
|
+
const p = m.length > 0, X = p || !!r, C = A(() => {
|
|
67
|
+
if (!w.trim() || !p) return i;
|
|
68
|
+
const a = w.toLowerCase();
|
|
69
|
+
return i.filter(
|
|
70
|
+
(t) => m.some(
|
|
71
|
+
(s) => String(t[s] ?? "").toLowerCase().includes(a)
|
|
126
72
|
)
|
|
127
73
|
);
|
|
128
|
-
}, [
|
|
129
|
-
(
|
|
130
|
-
|
|
74
|
+
}, [i, w, m, p]), R = Math.max(1, Math.ceil(C.length / b)), v = Math.min(O, R), Y = C.slice(
|
|
75
|
+
(v - 1) * b,
|
|
76
|
+
v * b
|
|
131
77
|
);
|
|
132
|
-
function
|
|
133
|
-
return
|
|
78
|
+
function Z(a, t) {
|
|
79
|
+
return t.render ? t.render(a) : String(a[t.key] ?? "");
|
|
134
80
|
}
|
|
135
|
-
return /* @__PURE__ */
|
|
136
|
-
|
|
137
|
-
|
|
81
|
+
return /* @__PURE__ */ h(oa, { direction: "column", gap: "300", className: "data-table", children: [
|
|
82
|
+
X && /* @__PURE__ */ h("div", { className: "data-table-toolbar", children: [
|
|
83
|
+
p && /* @__PURE__ */ e(
|
|
138
84
|
K,
|
|
139
85
|
{
|
|
140
|
-
placeholder:
|
|
141
|
-
onSearch: (
|
|
142
|
-
|
|
86
|
+
placeholder: B,
|
|
87
|
+
onSearch: (a) => {
|
|
88
|
+
J(a), T(1);
|
|
143
89
|
},
|
|
144
90
|
className: "data-table-search"
|
|
145
91
|
}
|
|
146
92
|
),
|
|
147
|
-
|
|
93
|
+
r && /* @__PURE__ */ e("div", { className: "data-table-actions", children: r })
|
|
148
94
|
] }),
|
|
149
|
-
|
|
95
|
+
D ? /* @__PURE__ */ e(
|
|
150
96
|
"div",
|
|
151
97
|
{
|
|
152
98
|
role: "status",
|
|
153
99
|
"aria-live": "polite",
|
|
154
|
-
"aria-label":
|
|
100
|
+
"aria-label": P,
|
|
155
101
|
className: "data-table-status",
|
|
156
|
-
children:
|
|
102
|
+
children: P
|
|
157
103
|
}
|
|
158
|
-
) :
|
|
159
|
-
/* @__PURE__ */
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
104
|
+
) : C.length === 0 ? /* @__PURE__ */ e("div", { role: "status", className: "data-table-status data-table-empty", children: H }) : /* @__PURE__ */ h(aa, { grid: !0, children: [
|
|
105
|
+
/* @__PURE__ */ h(ea, { children: [
|
|
106
|
+
k && /* @__PURE__ */ e(M, { className: "data-table-expand-col" }),
|
|
107
|
+
n.map((a, t) => /* @__PURE__ */ e(
|
|
108
|
+
M,
|
|
163
109
|
{
|
|
164
|
-
align:
|
|
165
|
-
isRowHeader:
|
|
166
|
-
children:
|
|
110
|
+
align: a.align,
|
|
111
|
+
isRowHeader: t === 0,
|
|
112
|
+
children: a.label
|
|
167
113
|
},
|
|
168
|
-
String(
|
|
114
|
+
String(a.key)
|
|
169
115
|
)),
|
|
170
|
-
|
|
116
|
+
x && /* @__PURE__ */ e(M, { align: "right", className: "data-table-row-actions-col", children: j })
|
|
171
117
|
] }),
|
|
172
|
-
/* @__PURE__ */
|
|
173
|
-
const
|
|
174
|
-
/* @__PURE__ */
|
|
175
|
-
|
|
118
|
+
/* @__PURE__ */ e(ta, { children: Y.flatMap((a) => {
|
|
119
|
+
const t = U.has(a.id), s = L.get(a.id), d = [
|
|
120
|
+
/* @__PURE__ */ h(E, { id: a.id, children: [
|
|
121
|
+
k && /* @__PURE__ */ e(g, { className: "data-table-expand-cell", children: /* @__PURE__ */ e(
|
|
176
122
|
"button",
|
|
177
123
|
{
|
|
178
124
|
className: "data-table-expand-btn",
|
|
179
|
-
"aria-label":
|
|
180
|
-
"aria-expanded":
|
|
181
|
-
onClick: () =>
|
|
182
|
-
children:
|
|
125
|
+
"aria-label": t ? "Collapse row" : "Expand row",
|
|
126
|
+
"aria-expanded": t,
|
|
127
|
+
onClick: () => W(a),
|
|
128
|
+
children: t ? /* @__PURE__ */ e(la, { size: "16" }) : /* @__PURE__ */ e(na, { size: "16" })
|
|
183
129
|
}
|
|
184
130
|
) }),
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
] },
|
|
131
|
+
n.map((l) => /* @__PURE__ */ e(g, { align: l.align, children: Z(a, l) }, String(l.key))),
|
|
132
|
+
x && /* @__PURE__ */ e(g, { align: "right", className: "data-table-row-actions-cell", children: /* @__PURE__ */ e("div", { className: "data-table-row-actions", children: (typeof o == "function" ? o(a) : o).map((l) => /* @__PURE__ */ e(da, { action: l, item: a }, l.key)) }) })
|
|
133
|
+
] }, a.id)
|
|
188
134
|
];
|
|
189
|
-
return
|
|
190
|
-
/* @__PURE__ */
|
|
191
|
-
|
|
135
|
+
return t && d.push(
|
|
136
|
+
/* @__PURE__ */ e(E, { id: `${a.id}-nested`, className: "data-table-nested-row", children: /* @__PURE__ */ e(
|
|
137
|
+
g,
|
|
192
138
|
{
|
|
193
|
-
colSpan:
|
|
139
|
+
colSpan: n.length + 1 + (x ? 1 : 0),
|
|
194
140
|
className: "data-table-nested-cell",
|
|
195
|
-
children: /* @__PURE__ */
|
|
196
|
-
|
|
141
|
+
children: /* @__PURE__ */ e(
|
|
142
|
+
ia,
|
|
197
143
|
{
|
|
198
|
-
columns:
|
|
199
|
-
data: (
|
|
200
|
-
isLoading: !
|
|
201
|
-
loadingMessage:
|
|
202
|
-
emptyMessage:
|
|
203
|
-
searchKeys:
|
|
204
|
-
searchPlaceholder:
|
|
205
|
-
actions:
|
|
206
|
-
pageSize:
|
|
144
|
+
columns: S,
|
|
145
|
+
data: (s == null ? void 0 : s.data) ?? [],
|
|
146
|
+
isLoading: !s || s.status === "loading",
|
|
147
|
+
loadingMessage: q,
|
|
148
|
+
emptyMessage: F,
|
|
149
|
+
searchKeys: G,
|
|
150
|
+
searchPlaceholder: Q,
|
|
151
|
+
actions: f == null ? void 0 : f(a),
|
|
152
|
+
pageSize: z ?? b
|
|
207
153
|
}
|
|
208
154
|
)
|
|
209
155
|
}
|
|
210
|
-
) }, `${
|
|
211
|
-
),
|
|
156
|
+
) }, `${a.id}-nested`)
|
|
157
|
+
), d;
|
|
212
158
|
}) })
|
|
213
159
|
] }),
|
|
214
|
-
|
|
215
|
-
|
|
160
|
+
/* @__PURE__ */ e(
|
|
161
|
+
sa,
|
|
216
162
|
{
|
|
217
|
-
|
|
218
|
-
totalPages:
|
|
219
|
-
onPageChange:
|
|
220
|
-
previousLabel:
|
|
221
|
-
nextLabel:
|
|
163
|
+
page: v,
|
|
164
|
+
totalPages: R,
|
|
165
|
+
onPageChange: T,
|
|
166
|
+
previousLabel: I,
|
|
167
|
+
nextLabel: $
|
|
222
168
|
}
|
|
223
169
|
)
|
|
224
170
|
] });
|
|
225
171
|
}
|
|
226
172
|
export {
|
|
227
|
-
|
|
173
|
+
ia as DataTable
|
|
228
174
|
};
|
|
229
175
|
//# sourceMappingURL=DataTable.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.js","sources":["../../../../../src/ui/components/DataTable/DataTable.tsx"],"sourcesContent":["import {\n PermissionGate,\n Search,\n Table,\n TableBody,\n TableCell,\n TableColumn,\n TableHead,\n TableRow,\n} from \"primitives\";\nimport { Flex } from \"layout\";\nimport { IconArrowLeft, IconArrowRight, IconChevronDown, IconChevronRight } from \"icons\";\nimport type { ReactNode } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport \"./datatable.css\";\n\nfunction buildPages(current: number, total: number): (number | \"...\")[] {\n if (total <= 6) return Array.from({ length: total }, (_, i) => i + 1);\n const head = [1, 2, 3];\n const tail = [total - 1, total];\n if (current <= 4) return [...head, \"...\", ...tail];\n if (current >= total - 3) return [1, 2, \"...\", total - 2, total - 1, total];\n return [1, \"...\", current - 1, current, current + 1, \"...\", total];\n}\n\ninterface PaginationProps {\n currentPage: number;\n totalPages: number;\n onPageChange: (page: number) => void;\n previousLabel: string;\n nextLabel: string;\n}\n\nfunction Pagination({\n currentPage,\n totalPages,\n onPageChange,\n previousLabel,\n nextLabel,\n}: PaginationProps) {\n const pages = buildPages(currentPage, totalPages);\n return (\n <div className=\"data-table-pagination\">\n <button\n onClick={() => onPageChange(Math.max(1, currentPage - 1))}\n disabled={currentPage === 1}\n className=\"data-table-page-btn data-table-page-nav\"\n aria-label={previousLabel}\n >\n <IconArrowLeft size=\"16\" />\n {previousLabel}\n </button>\n <div className=\"data-table-page-numbers\">\n {pages.map((p, i) =>\n p === \"...\" ? (\n <span key={`ellipsis-${i}`} className=\"data-table-page-ellipsis\">\n ...\n </span>\n ) : (\n <button\n key={p}\n onClick={() => onPageChange(p as number)}\n className={`data-table-page-btn${p === currentPage ? \" data-table-page-btn-active\" : \"\"}`}\n >\n {p}\n </button>\n ),\n )}\n </div>\n <button\n onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}\n disabled={currentPage === totalPages}\n className=\"data-table-page-btn data-table-page-nav\"\n aria-label={nextLabel}\n >\n {nextLabel}\n <IconArrowRight size=\"16\" />\n </button>\n </div>\n );\n}\n\nexport interface Column<T> {\n key: keyof T | string;\n label: string;\n align?: \"start\" | \"center\" | \"right\";\n render?: (item: T) => ReactNode;\n}\n\nexport interface RowActionDef<T> {\n /** Unique key for this action. */\n key: string;\n /** aria-label for the button. */\n label: string;\n /** Icon element rendered inside the button. */\n icon: ReactNode;\n /** When false, button is disabled and a tooltip with `noAccessLabel` is shown. Default: true. */\n isAllowed?: boolean;\n /** Tooltip text shown when `isAllowed` is false. */\n noAccessLabel?: string;\n /** Called with the row item when the button is pressed. */\n onPress: (item: T) => void;\n}\n\nfunction RowActionButton<T>({\n action,\n item,\n}: {\n action: RowActionDef<T>;\n item: T;\n}) {\n const allowed = action.isAllowed !== false;\n const btn = (\n <button\n className=\"data-table-row-action-btn\"\n aria-label={action.label}\n disabled={!allowed}\n onClick={allowed ? () => action.onPress(item) : undefined}\n >\n {action.icon}\n </button>\n );\n if (allowed) return btn;\n return (\n <PermissionGate isAllowed={false} noAccessLabel={action.noAccessLabel ?? \"\"}>\n {btn}\n </PermissionGate>\n );\n}\n\ntype NestedStatus = { status: \"loading\"; data: never[] } | { status: \"loaded\"; data: any[] };\n\nexport interface DataTableProps<T extends { id: string }> {\n columns: Column<T>[];\n data: T[];\n actions?: ReactNode;\n /** Per-row action buttons rendered in a dedicated last column. */\n rowActions?: RowActionDef<T>[] | ((item: T) => RowActionDef<T>[]);\n /** Label for the row actions column header. Defaults to empty string. */\n rowActionsLabel?: string;\n searchPlaceholder?: string;\n searchKeys?: (keyof T)[];\n isLoading?: boolean;\n loadingMessage?: string;\n emptyMessage?: string;\n previousLabel?: string;\n nextLabel?: string;\n pageSize?: number;\n nestedColumns?: Column<any>[];\n getNestedData?: (item: T) => any[] | Promise<any[]>;\n nestedLoadingMessage?: string;\n nestedEmptyMessage?: string;\n nestedSearchKeys?: (keyof any)[];\n nestedSearchPlaceholder?: string;\n nestedActions?: (item: T) => ReactNode;\n nestedPageSize?: number;\n}\n\nexport function DataTable<T extends { id: string }>({\n columns,\n data,\n actions,\n rowActions,\n rowActionsLabel = \"\",\n searchPlaceholder = \"Search...\",\n searchKeys = [],\n isLoading = false,\n loadingMessage = \"Loading...\",\n emptyMessage = \"No records found\",\n previousLabel = \"Previous\",\n nextLabel = \"Next\",\n pageSize = 10,\n nestedColumns,\n getNestedData,\n nestedLoadingMessage = \"Loading...\",\n nestedEmptyMessage = \"No records found\",\n nestedSearchKeys,\n nestedSearchPlaceholder,\n nestedActions,\n nestedPageSize,\n}: DataTableProps<T>) {\n const [query, setQuery] = useState(\"\");\n const [page, setPage] = useState(1);\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n const [nestedCache, setNestedCache] = useState<Map<string, NestedStatus>>(new Map());\n\n const hasNested = !!getNestedData && !!nestedColumns;\n const hasRowActions = !!rowActions;\n\n function toggleRow(item: T) {\n const id = item.id;\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n if (!nestedCache.has(id)) {\n const result = getNestedData!(item);\n if (result instanceof Promise) {\n setNestedCache((c) => new Map(c).set(id, { status: \"loading\", data: [] }));\n result.then((rows) => {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: rows }));\n });\n } else {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: result }));\n }\n }\n }\n return next;\n });\n }\n\n const hasSearch = searchKeys.length > 0;\n const hasToolbar = hasSearch || !!actions;\n\n const filtered = useMemo(() => {\n if (!query.trim() || !hasSearch) return data;\n const lower = query.toLowerCase();\n return data.filter((item) =>\n searchKeys.some((k) =>\n String(item[k] ?? \"\")\n .toLowerCase()\n .includes(lower),\n ),\n );\n }, [data, query, searchKeys, hasSearch]);\n\n const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));\n const currentPage = Math.min(page, totalPages);\n const pageData = filtered.slice(\n (currentPage - 1) * pageSize,\n currentPage * pageSize,\n );\n\n function getCell(item: T, col: Column<T>): ReactNode {\n if (col.render) return col.render(item);\n return String((item as Record<string, unknown>)[col.key as string] ?? \"\");\n }\n\n return (\n <Flex direction=\"column\" gap=\"300\" className=\"data-table\">\n {hasToolbar && (\n <div className=\"data-table-toolbar\">\n {hasSearch && (\n <Search\n placeholder={searchPlaceholder}\n onSearch={(v) => {\n setQuery(v);\n setPage(1);\n }}\n className=\"data-table-search\"\n />\n )}\n {actions && <div className=\"data-table-actions\">{actions}</div>}\n </div>\n )}\n\n {isLoading ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-label={loadingMessage}\n className=\"data-table-status\"\n >\n {loadingMessage}\n </div>\n ) : filtered.length === 0 ? (\n <div role=\"status\" className=\"data-table-status data-table-empty\">\n {emptyMessage}\n </div>\n ) : (\n <Table grid>\n <TableHead>\n {hasNested && <TableColumn className=\"data-table-expand-col\" />}\n {columns.map((col, idx) => (\n <TableColumn\n key={String(col.key)}\n align={col.align}\n isRowHeader={idx === 0}\n >\n {col.label}\n </TableColumn>\n ))}\n {hasRowActions && (\n <TableColumn align=\"right\" className=\"data-table-row-actions-col\">\n {rowActionsLabel}\n </TableColumn>\n )}\n </TableHead>\n <TableBody>\n {pageData.flatMap((item) => {\n const isExpanded = expanded.has(item.id);\n const cached = nestedCache.get(item.id);\n const rows: ReactNode[] = [\n <TableRow key={item.id} id={item.id}>\n {hasNested && (\n <TableCell className=\"data-table-expand-cell\">\n <button\n className=\"data-table-expand-btn\"\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n aria-expanded={isExpanded}\n onClick={() => toggleRow(item)}\n >\n {isExpanded ? (\n <IconChevronDown size=\"16\" />\n ) : (\n <IconChevronRight size=\"16\" />\n )}\n </button>\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={String(col.key)} align={col.align}>\n {getCell(item, col)}\n </TableCell>\n ))}\n {hasRowActions && (\n <TableCell align=\"right\" className=\"data-table-row-actions-cell\">\n <div className=\"data-table-row-actions\">\n {(typeof rowActions === \"function\"\n ? rowActions(item)\n : rowActions!\n ).map((action) => (\n <RowActionButton key={action.key} action={action} item={item} />\n ))}\n </div>\n </TableCell>\n )}\n </TableRow>,\n ];\n if (isExpanded) {\n rows.push(\n <TableRow key={`${item.id}-nested`} id={`${item.id}-nested`} className=\"data-table-nested-row\">\n <TableCell\n colSpan={columns.length + 1 + (hasRowActions ? 1 : 0)}\n className=\"data-table-nested-cell\"\n >\n <DataTable\n columns={nestedColumns!}\n data={cached?.data ?? []}\n isLoading={!cached || cached.status === \"loading\"}\n loadingMessage={nestedLoadingMessage}\n emptyMessage={nestedEmptyMessage}\n searchKeys={nestedSearchKeys}\n searchPlaceholder={nestedSearchPlaceholder}\n actions={nestedActions?.(item)}\n pageSize={nestedPageSize ?? pageSize}\n />\n </TableCell>\n </TableRow>,\n );\n }\n return rows;\n })}\n </TableBody>\n </Table>\n )}\n\n {totalPages > 1 && (\n <Pagination\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={setPage}\n previousLabel={previousLabel}\n nextLabel={nextLabel}\n />\n )}\n </Flex>\n );\n}\n"],"names":["buildPages","current","total","_","i","head","tail","Pagination","currentPage","totalPages","onPageChange","previousLabel","nextLabel","pages","jsxs","jsx","IconArrowLeft","p","IconArrowRight","RowActionButton","action","item","allowed","btn","PermissionGate","DataTable","columns","data","actions","rowActions","rowActionsLabel","searchPlaceholder","searchKeys","isLoading","loadingMessage","emptyMessage","pageSize","nestedColumns","getNestedData","nestedLoadingMessage","nestedEmptyMessage","nestedSearchKeys","nestedSearchPlaceholder","nestedActions","nestedPageSize","query","setQuery","useState","page","setPage","expanded","setExpanded","nestedCache","setNestedCache","hasNested","hasRowActions","toggleRow","id","prev","next","result","c","rows","hasSearch","hasToolbar","filtered","useMemo","lower","k","pageData","getCell","col","Flex","Search","v","Table","TableHead","TableColumn","idx","TableBody","isExpanded","cached","TableRow","TableCell","IconChevronDown","IconChevronRight"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataTable.js","sources":["../../../../../src/ui/components/DataTable/DataTable.tsx"],"sourcesContent":["import {\n Pagination,\n PermissionGate,\n Search,\n Table,\n TableBody,\n TableCell,\n TableColumn,\n TableHead,\n TableRow,\n} from \"primitives\";\nimport { Flex } from \"layout\";\nimport { IconChevronDown, IconChevronRight } from \"icons\";\nimport type { ReactNode } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport \"./datatable.css\";\n\nexport interface Column<T> {\n key: keyof T | string;\n label: string;\n align?: \"start\" | \"center\" | \"right\";\n render?: (item: T) => ReactNode;\n}\n\nexport interface RowActionDef<T> {\n /** Unique key for this action. */\n key: string;\n /** aria-label for the button. */\n label: string;\n /** Icon element rendered inside the button. */\n icon: ReactNode;\n /** When false, button is disabled and a tooltip with `noAccessLabel` is shown. Default: true. */\n isAllowed?: boolean;\n /** Tooltip text shown when `isAllowed` is false. */\n noAccessLabel?: string;\n /** Called with the row item when the button is pressed. */\n onPress: (item: T) => void;\n}\n\nfunction RowActionButton<T>({\n action,\n item,\n}: {\n action: RowActionDef<T>;\n item: T;\n}) {\n const allowed = action.isAllowed !== false;\n const btn = (\n <button\n className=\"data-table-row-action-btn\"\n aria-label={action.label}\n disabled={!allowed}\n onClick={allowed ? () => action.onPress(item) : undefined}\n >\n {action.icon}\n </button>\n );\n if (allowed) return btn;\n return (\n <PermissionGate isAllowed={false} noAccessLabel={action.noAccessLabel ?? \"\"}>\n {btn}\n </PermissionGate>\n );\n}\n\ntype NestedStatus = { status: \"loading\"; data: never[] } | { status: \"loaded\"; data: any[] };\n\nexport interface DataTableProps<T extends { id: string }> {\n columns: Column<T>[];\n data: T[];\n actions?: ReactNode;\n /** Per-row action buttons rendered in a dedicated last column. */\n rowActions?: RowActionDef<T>[] | ((item: T) => RowActionDef<T>[]);\n /** Label for the row actions column header. Defaults to empty string. */\n rowActionsLabel?: string;\n searchPlaceholder?: string;\n searchKeys?: (keyof T)[];\n isLoading?: boolean;\n loadingMessage?: string;\n emptyMessage?: string;\n previousLabel?: string;\n nextLabel?: string;\n pageSize?: number;\n nestedColumns?: Column<any>[];\n getNestedData?: (item: T) => any[] | Promise<any[]>;\n nestedLoadingMessage?: string;\n nestedEmptyMessage?: string;\n nestedSearchKeys?: (keyof any)[];\n nestedSearchPlaceholder?: string;\n nestedActions?: (item: T) => ReactNode;\n nestedPageSize?: number;\n}\n\nexport function DataTable<T extends { id: string }>({\n columns,\n data,\n actions,\n rowActions,\n rowActionsLabel = \"\",\n searchPlaceholder = \"Search...\",\n searchKeys = [],\n isLoading = false,\n loadingMessage = \"Loading...\",\n emptyMessage = \"No records found\",\n previousLabel = \"Previous\",\n nextLabel = \"Next\",\n pageSize = 10,\n nestedColumns,\n getNestedData,\n nestedLoadingMessage = \"Loading...\",\n nestedEmptyMessage = \"No records found\",\n nestedSearchKeys,\n nestedSearchPlaceholder,\n nestedActions,\n nestedPageSize,\n}: DataTableProps<T>) {\n const [query, setQuery] = useState(\"\");\n const [page, setPage] = useState(1);\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n const [nestedCache, setNestedCache] = useState<Map<string, NestedStatus>>(new Map());\n\n const hasNested = !!getNestedData && !!nestedColumns;\n const hasRowActions = !!rowActions;\n\n function toggleRow(item: T) {\n const id = item.id;\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n if (!nestedCache.has(id)) {\n const result = getNestedData!(item);\n if (result instanceof Promise) {\n setNestedCache((c) => new Map(c).set(id, { status: \"loading\", data: [] }));\n result.then((rows) => {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: rows }));\n });\n } else {\n setNestedCache((c) => new Map(c).set(id, { status: \"loaded\", data: result }));\n }\n }\n }\n return next;\n });\n }\n\n const hasSearch = searchKeys.length > 0;\n const hasToolbar = hasSearch || !!actions;\n\n const filtered = useMemo(() => {\n if (!query.trim() || !hasSearch) return data;\n const lower = query.toLowerCase();\n return data.filter((item) =>\n searchKeys.some((k) =>\n String(item[k] ?? \"\")\n .toLowerCase()\n .includes(lower),\n ),\n );\n }, [data, query, searchKeys, hasSearch]);\n\n const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));\n const currentPage = Math.min(page, totalPages);\n const pageData = filtered.slice(\n (currentPage - 1) * pageSize,\n currentPage * pageSize,\n );\n\n function getCell(item: T, col: Column<T>): ReactNode {\n if (col.render) return col.render(item);\n return String((item as Record<string, unknown>)[col.key as string] ?? \"\");\n }\n\n return (\n <Flex direction=\"column\" gap=\"300\" className=\"data-table\">\n {hasToolbar && (\n <div className=\"data-table-toolbar\">\n {hasSearch && (\n <Search\n placeholder={searchPlaceholder}\n onSearch={(v) => {\n setQuery(v);\n setPage(1);\n }}\n className=\"data-table-search\"\n />\n )}\n {actions && <div className=\"data-table-actions\">{actions}</div>}\n </div>\n )}\n\n {isLoading ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-label={loadingMessage}\n className=\"data-table-status\"\n >\n {loadingMessage}\n </div>\n ) : filtered.length === 0 ? (\n <div role=\"status\" className=\"data-table-status data-table-empty\">\n {emptyMessage}\n </div>\n ) : (\n <Table grid>\n <TableHead>\n {hasNested && <TableColumn className=\"data-table-expand-col\" />}\n {columns.map((col, idx) => (\n <TableColumn\n key={String(col.key)}\n align={col.align}\n isRowHeader={idx === 0}\n >\n {col.label}\n </TableColumn>\n ))}\n {hasRowActions && (\n <TableColumn align=\"right\" className=\"data-table-row-actions-col\">\n {rowActionsLabel}\n </TableColumn>\n )}\n </TableHead>\n <TableBody>\n {pageData.flatMap((item) => {\n const isExpanded = expanded.has(item.id);\n const cached = nestedCache.get(item.id);\n const rows: ReactNode[] = [\n <TableRow key={item.id} id={item.id}>\n {hasNested && (\n <TableCell className=\"data-table-expand-cell\">\n <button\n className=\"data-table-expand-btn\"\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n aria-expanded={isExpanded}\n onClick={() => toggleRow(item)}\n >\n {isExpanded ? (\n <IconChevronDown size=\"16\" />\n ) : (\n <IconChevronRight size=\"16\" />\n )}\n </button>\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={String(col.key)} align={col.align}>\n {getCell(item, col)}\n </TableCell>\n ))}\n {hasRowActions && (\n <TableCell align=\"right\" className=\"data-table-row-actions-cell\">\n <div className=\"data-table-row-actions\">\n {(typeof rowActions === \"function\"\n ? rowActions(item)\n : rowActions!\n ).map((action) => (\n <RowActionButton key={action.key} action={action} item={item} />\n ))}\n </div>\n </TableCell>\n )}\n </TableRow>,\n ];\n if (isExpanded) {\n rows.push(\n <TableRow key={`${item.id}-nested`} id={`${item.id}-nested`} className=\"data-table-nested-row\">\n <TableCell\n colSpan={columns.length + 1 + (hasRowActions ? 1 : 0)}\n className=\"data-table-nested-cell\"\n >\n <DataTable\n columns={nestedColumns!}\n data={cached?.data ?? []}\n isLoading={!cached || cached.status === \"loading\"}\n loadingMessage={nestedLoadingMessage}\n emptyMessage={nestedEmptyMessage}\n searchKeys={nestedSearchKeys}\n searchPlaceholder={nestedSearchPlaceholder}\n actions={nestedActions?.(item)}\n pageSize={nestedPageSize ?? pageSize}\n />\n </TableCell>\n </TableRow>,\n );\n }\n return rows;\n })}\n </TableBody>\n </Table>\n )}\n\n <Pagination\n page={currentPage}\n totalPages={totalPages}\n onPageChange={setPage}\n previousLabel={previousLabel}\n nextLabel={nextLabel}\n />\n </Flex>\n );\n}\n"],"names":["RowActionButton","action","item","allowed","btn","jsx","PermissionGate","DataTable","columns","data","actions","rowActions","rowActionsLabel","searchPlaceholder","searchKeys","isLoading","loadingMessage","emptyMessage","previousLabel","nextLabel","pageSize","nestedColumns","getNestedData","nestedLoadingMessage","nestedEmptyMessage","nestedSearchKeys","nestedSearchPlaceholder","nestedActions","nestedPageSize","query","setQuery","useState","page","setPage","expanded","setExpanded","nestedCache","setNestedCache","hasNested","hasRowActions","toggleRow","id","prev","next","result","rows","c","hasSearch","hasToolbar","filtered","useMemo","lower","k","totalPages","currentPage","pageData","getCell","col","Flex","jsxs","Search","v","Table","TableHead","TableColumn","idx","TableBody","isExpanded","cached","TableRow","TableCell","IconChevronDown","IconChevronRight","Pagination"],"mappings":";;;;;;;;;;AAuCA,SAASA,GAAmB;AAAA,EAC1B,QAAAC;AAAA,EACA,MAAAC;AACF,GAGG;AACD,QAAMC,IAAUF,EAAO,cAAc,IAC/BG,IACJ,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACV,cAAYJ,EAAO;AAAA,MACnB,UAAU,CAACE;AAAA,MACX,SAASA,IAAU,MAAMF,EAAO,QAAQC,CAAI,IAAI;AAAA,MAE/C,UAAAD,EAAO;AAAA,IAAA;AAAA,EAAA;AAGZ,SAAIE,IAAgBC,IAElB,gBAAAC,EAACC,MAAe,WAAW,IAAO,eAAeL,EAAO,iBAAiB,IACtE,UAAAG,EAAA,CACH;AAEJ;AA8BO,SAASG,GAAoC;AAAA,EAClD,SAAAC;AAAA,EACA,MAAAC;AAAA,EACA,SAAAC;AAAA,EACA,YAAAC;AAAA,EACA,iBAAAC,IAAkB;AAAA,EAClB,mBAAAC,IAAoB;AAAA,EACpB,YAAAC,IAAa,CAAA;AAAA,EACb,WAAAC,IAAY;AAAA,EACZ,gBAAAC,IAAiB;AAAA,EACjB,cAAAC,IAAe;AAAA,EACf,eAAAC,IAAgB;AAAA,EAChB,WAAAC,IAAY;AAAA,EACZ,UAAAC,IAAW;AAAA,EACX,eAAAC;AAAA,EACA,eAAAC;AAAA,EACA,sBAAAC,IAAuB;AAAA,EACvB,oBAAAC,IAAqB;AAAA,EACrB,kBAAAC;AAAA,EACA,yBAAAC;AAAA,EACA,eAAAC;AAAA,EACA,gBAAAC;AACF,GAAsB;AACpB,QAAM,CAACC,GAAOC,CAAQ,IAAIC,EAAS,EAAE,GAC/B,CAACC,GAAMC,CAAO,IAAIF,EAAS,CAAC,GAC5B,CAACG,GAAUC,CAAW,IAAIJ,EAAsB,oBAAI,KAAK,GACzD,CAACK,GAAaC,CAAc,IAAIN,EAAoC,oBAAI,KAAK,GAE7EO,IAAY,CAAC,CAAChB,KAAiB,CAAC,CAACD,GACjCkB,IAAgB,CAAC,CAAC5B;AAExB,WAAS6B,EAAUtC,GAAS;AAC1B,UAAMuC,IAAKvC,EAAK;AAChB,IAAAiC,EAAY,CAACO,MAAS;AACpB,YAAMC,IAAO,IAAI,IAAID,CAAI;AACzB,UAAIC,EAAK,IAAIF,CAAE;AACb,QAAAE,EAAK,OAAOF,CAAE;AAAA,eAEdE,EAAK,IAAIF,CAAE,GACP,CAACL,EAAY,IAAIK,CAAE,GAAG;AACxB,cAAMG,IAAStB,EAAepB,CAAI;AAClC,QAAI0C,aAAkB,WACpBP,EAAe,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,IAAII,GAAI,EAAE,QAAQ,WAAW,MAAM,CAAA,EAAC,CAAG,CAAC,GACzEG,EAAO,KAAK,CAACC,MAAS;AACpB,UAAAR,EAAe,CAACS,MAAM,IAAI,IAAIA,CAAC,EAAE,IAAIL,GAAI,EAAE,QAAQ,UAAU,MAAMI,EAAA,CAAM,CAAC;AAAA,QAC5E,CAAC,KAEDR,EAAe,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,IAAII,GAAI,EAAE,QAAQ,UAAU,MAAMG,EAAA,CAAQ,CAAC;AAAA,MAEhF;AAEF,aAAOD;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAMI,IAAYjC,EAAW,SAAS,GAChCkC,IAAaD,KAAa,CAAC,CAACrC,GAE5BuC,IAAWC,EAAQ,MAAM;AAC7B,QAAI,CAACrB,EAAM,KAAA,KAAU,CAACkB,EAAW,QAAOtC;AACxC,UAAM0C,IAAQtB,EAAM,YAAA;AACpB,WAAOpB,EAAK;AAAA,MAAO,CAACP,MAClBY,EAAW;AAAA,QAAK,CAACsC,MACf,OAAOlD,EAAKkD,CAAC,KAAK,EAAE,EACjB,cACA,SAASD,CAAK;AAAA,MAAA;AAAA,IACnB;AAAA,EAEJ,GAAG,CAAC1C,GAAMoB,GAAOf,GAAYiC,CAAS,CAAC,GAEjCM,IAAa,KAAK,IAAI,GAAG,KAAK,KAAKJ,EAAS,SAAS7B,CAAQ,CAAC,GAC9DkC,IAAc,KAAK,IAAItB,GAAMqB,CAAU,GACvCE,IAAWN,EAAS;AAAA,KACvBK,IAAc,KAAKlC;AAAA,IACpBkC,IAAclC;AAAA,EAAA;AAGhB,WAASoC,EAAQtD,GAASuD,GAA2B;AACnD,WAAIA,EAAI,SAAeA,EAAI,OAAOvD,CAAI,IAC/B,OAAQA,EAAiCuD,EAAI,GAAa,KAAK,EAAE;AAAA,EAC1E;AAEA,2BACGC,IAAA,EAAK,WAAU,UAAS,KAAI,OAAM,WAAU,cAC1C,UAAA;AAAA,IAAAV,KACC,gBAAAW,EAAC,OAAA,EAAI,WAAU,sBACZ,UAAA;AAAA,MAAAZ,KACC,gBAAA1C;AAAA,QAACuD;AAAA,QAAA;AAAA,UACC,aAAa/C;AAAA,UACb,UAAU,CAACgD,MAAM;AACf,YAAA/B,EAAS+B,CAAC,GACV5B,EAAQ,CAAC;AAAA,UACX;AAAA,UACA,WAAU;AAAA,QAAA;AAAA,MAAA;AAAA,MAGbvB,KAAW,gBAAAL,EAAC,OAAA,EAAI,WAAU,sBAAsB,UAAAK,EAAA,CAAQ;AAAA,IAAA,GAC3D;AAAA,IAGDK,IACC,gBAAAV;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAU;AAAA,QACV,cAAYW;AAAA,QACZ,WAAU;AAAA,QAET,UAAAA;AAAA,MAAA;AAAA,IAAA,IAEDiC,EAAS,WAAW,sBACrB,OAAA,EAAI,MAAK,UAAS,WAAU,sCAC1B,UAAAhC,EAAA,CACH,IAEA,gBAAA0C,EAACG,IAAA,EAAM,MAAI,IACT,UAAA;AAAA,MAAA,gBAAAH,EAACI,IAAA,EACE,UAAA;AAAA,QAAAzB,KAAa,gBAAAjC,EAAC2D,GAAA,EAAY,WAAU,wBAAA,CAAwB;AAAA,QAC5DxD,EAAQ,IAAI,CAACiD,GAAKQ,MACjB,gBAAA5D;AAAA,UAAC2D;AAAA,UAAA;AAAA,YAEC,OAAOP,EAAI;AAAA,YACX,aAAaQ,MAAQ;AAAA,YAEpB,UAAAR,EAAI;AAAA,UAAA;AAAA,UAJA,OAAOA,EAAI,GAAG;AAAA,QAAA,CAMtB;AAAA,QACAlB,KACC,gBAAAlC,EAAC2D,GAAA,EAAY,OAAM,SAAQ,WAAU,8BAClC,UAAApD,EAAA,CACH;AAAA,MAAA,GAEJ;AAAA,MACA,gBAAAP,EAAC6D,IAAA,EACE,UAAAX,EAAS,QAAQ,CAACrD,MAAS;AAC1B,cAAMiE,IAAajC,EAAS,IAAIhC,EAAK,EAAE,GACjCkE,IAAShC,EAAY,IAAIlC,EAAK,EAAE,GAChC2C,IAAoB;AAAA,UACxB,gBAAAc,EAACU,GAAA,EAAuB,IAAInE,EAAK,IAC9B,UAAA;AAAA,YAAAoC,KACC,gBAAAjC,EAACiE,GAAA,EAAU,WAAU,0BACnB,UAAA,gBAAAjE;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,cAAY8D,IAAa,iBAAiB;AAAA,gBAC1C,iBAAeA;AAAA,gBACf,SAAS,MAAM3B,EAAUtC,CAAI;AAAA,gBAE5B,UAAAiE,sBACEI,IAAA,EAAgB,MAAK,MAAK,IAE3B,gBAAAlE,EAACmE,IAAA,EAAiB,MAAK,KAAA,CAAK;AAAA,cAAA;AAAA,YAAA,GAGlC;AAAA,YAEDhE,EAAQ,IAAI,CAACiD,MACZ,gBAAApD,EAACiE,KAAgC,OAAOb,EAAI,OACzC,UAAAD,EAAQtD,GAAMuD,CAAG,EAAA,GADJ,OAAOA,EAAI,GAAG,CAE9B,CACD;AAAA,YACAlB,KACC,gBAAAlC,EAACiE,GAAA,EAAU,OAAM,SAAQ,WAAU,+BACjC,UAAA,gBAAAjE,EAAC,OAAA,EAAI,WAAU,0BACX,WAAA,OAAOM,KAAe,aACpBA,EAAWT,CAAI,IACfS,GACF,IAAI,CAACV,MACL,gBAAAI,EAACL,IAAA,EAAiC,QAAAC,GAAgB,MAAAC,EAAA,GAA5BD,EAAO,GAAiC,CAC/D,GACH,EAAA,CACF;AAAA,UAAA,EAAA,GAhCWC,EAAK,EAkCpB;AAAA,QAAA;AAEF,eAAIiE,KACFtB,EAAK;AAAA,UACH,gBAAAxC,EAACgE,KAAmC,IAAI,GAAGnE,EAAK,EAAE,WAAW,WAAU,yBACrE,UAAA,gBAAAG;AAAA,YAACiE;AAAA,YAAA;AAAA,cACC,SAAS9D,EAAQ,SAAS,KAAK+B,IAAgB,IAAI;AAAA,cACnD,WAAU;AAAA,cAEV,UAAA,gBAAAlC;AAAA,gBAACE;AAAA,gBAAA;AAAA,kBACC,SAASc;AAAA,kBACT,OAAM+C,KAAA,gBAAAA,EAAQ,SAAQ,CAAA;AAAA,kBACtB,WAAW,CAACA,KAAUA,EAAO,WAAW;AAAA,kBACxC,gBAAgB7C;AAAA,kBAChB,cAAcC;AAAA,kBACd,YAAYC;AAAA,kBACZ,mBAAmBC;AAAA,kBACnB,SAASC,KAAA,gBAAAA,EAAgBzB;AAAA,kBACzB,UAAU0B,KAAkBR;AAAA,gBAAA;AAAA,cAAA;AAAA,YAC9B;AAAA,UAAA,EACF,GAhBa,GAAGlB,EAAK,EAAE,SAiBzB;AAAA,QAAA,GAGG2C;AAAA,MACT,CAAC,EAAA,CACH;AAAA,IAAA,GACF;AAAA,IAGF,gBAAAxC;AAAA,MAACoE;AAAA,MAAA;AAAA,QACC,MAAMnB;AAAA,QACN,YAAAD;AAAA,QACA,cAAcpB;AAAA,QACd,eAAAf;AAAA,QACA,WAAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react/jsx-runtime"),l=require("../../../vendor/clsx/dist/clsx.cjs");;/* empty css */const h=require("../../icons/IconArrowLeft.cjs"),N=require("../../icons/IconArrowRight.cjs"),A=require("../../hooks/useMediaQuery.cjs"),c=require("../../utils/AnchorOrButton.cjs");function q(a){return"totalPages"in a}function L(a){if(q(a)){const{page:t,totalPages:r,onPageChange:u,previousLabel:m,nextLabel:p,className:f}=a;return r<=1?null:i.jsxs("nav",{"aria-label":"Page navigation",className:l.clsx(f,"pagination"),children:[i.jsx(g,{isDisabled:t<=1,onPress:()=>u(Math.max(1,t-1)),children:m}),i.jsx(x,{children:j(t,r).map((o,v)=>o==="..."?i.jsx(b,{},`gap-${v}`):i.jsx(d,{current:o===t,onPress:()=>u(o),children:String(o)},o))}),i.jsx(P,{isDisabled:t>=r,onPress:()=>u(Math.min(r,t+1)),children:p})]})}const{"aria-label":n="Page navigation",className:e,...s}=a;return i.jsx("nav",{"aria-label":n,...s,className:l.clsx(e,"pagination")})}function g({href:a=null,onPress:n,isDisabled:e=!1,children:s="Previous"}){return i.jsxs(c.AnchorOrButton,{...a!==null?{href:a}:{},onPress:n,isDisabled:e||a===null&&!n,className:l.clsx("pagination-btn","pagination-nav"),"aria-label":"Previous page",children:[i.jsx(h.IconArrowLeft,{size:"16"}),s]})}function P({href:a=null,onPress:n,isDisabled:e=!1,children:s="Next"}){return i.jsxs(c.AnchorOrButton,{...a!==null?{href:a}:{},onPress:n,isDisabled:e||a===null&&!n,className:l.clsx("pagination-btn","pagination-nav"),"aria-label":"Next page",children:[s,i.jsx(N.IconArrowRight,{size:"16"})]})}function x({children:a}){const{isTabletUp:n}=A.useMediaQuery();return n&&i.jsx("span",{className:"pagination-list",children:a})}function d({href:a,onPress:n,children:e,current:s=!1}){return i.jsx(c.AnchorOrButton,{...a?{href:a}:{},onPress:n,"aria-label":`Page ${e}`,"aria-current":s?"page":void 0,className:l.clsx("pagination-btn",s&&"pagination-btn-active"),children:e})}function b(){return i.jsx("div",{"aria-hidden":"true",className:"pagination-gap",children:"…"})}function j(a,n){if(n<=6)return Array.from({length:n},(t,r)=>r+1);const e=[1,2,3],s=[n-1,n];return a<=4?[...e,"...",...s]:a>=n-3?[1,2,"...",n-2,n-1,n]:[1,"...",a-1,a,a+1,"...",n]}exports.Pagination=L;exports.PaginationGap=b;exports.PaginationList=x;exports.PaginationNext=P;exports.PaginationPage=d;exports.PaginationPrevious=g;exports.buildPaginationPages=j;
|
|
2
2
|
//# sourceMappingURL=Pagination.cjs.map
|