@placeholderco/placeholder-ui 1.0.12 → 2.0.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 (276) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +134 -130
  3. package/dist/app.css +178 -0
  4. package/dist/bootstrap.min.css +3600 -0
  5. package/dist/cards/GitCard.svelte +115 -0
  6. package/dist/cards/GitCard.svelte.d.ts +22 -0
  7. package/dist/display/Avatar.svelte +29 -17
  8. package/dist/display/Avatar.svelte.d.ts +12 -2
  9. package/dist/display/LinkCollection.svelte +40 -18
  10. package/dist/display/LinkCollection.svelte.d.ts +9 -2
  11. package/dist/display/Paper.svelte +31 -19
  12. package/dist/display/Paper.svelte.d.ts +12 -3
  13. package/dist/display/alert/Alert.svelte +129 -0
  14. package/dist/display/alert/Alert.svelte.d.ts +15 -0
  15. package/dist/display/alert/AlertVariant.d.ts +1 -0
  16. package/dist/display/alert/AlertVariant.js +1 -0
  17. package/dist/display/filetree/FileTree.svelte +259 -0
  18. package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
  19. package/dist/display/filetree/FileTreeItem.svelte +396 -0
  20. package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
  21. package/dist/display/filetree/FileTreeNode.d.ts +46 -0
  22. package/dist/display/filetree/FileTreeNode.js +115 -0
  23. package/dist/display/filetree/index.d.ts +5 -0
  24. package/dist/display/filetree/index.js +3 -0
  25. package/dist/editors/CodeMirror.svelte +306 -0
  26. package/dist/editors/CodeMirror.svelte.d.ts +31 -0
  27. package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
  28. package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
  29. package/dist/editors/tiptap/TipTap.svelte +856 -0
  30. package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
  31. package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
  32. package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
  33. package/dist/editors/tiptap/collaboration.d.ts +55 -0
  34. package/dist/editors/tiptap/collaboration.js +1 -0
  35. package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
  36. package/dist/editors/tiptap/extensions/Alert.js +72 -0
  37. package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
  38. package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
  39. package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
  40. package/dist/editors/tiptap/extensions/Div.js +89 -0
  41. package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
  42. package/dist/editors/tiptap/extensions/Span.js +56 -0
  43. package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
  44. package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
  45. package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
  46. package/dist/editors/tiptap/extensions/index.d.ts +7 -0
  47. package/dist/editors/tiptap/extensions/index.js +5 -0
  48. package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
  49. package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
  50. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
  51. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
  52. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
  53. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
  54. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
  55. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
  56. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
  57. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
  58. package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
  59. package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
  60. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
  61. package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
  62. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
  63. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
  64. package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
  65. package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
  66. package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
  67. package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
  68. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
  69. package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
  70. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
  71. package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
  72. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
  73. package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
  74. package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
  75. package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
  76. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
  77. package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
  78. package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
  79. package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
  80. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
  81. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
  82. package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
  83. package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
  84. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
  85. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
  86. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
  87. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
  88. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
  89. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
  90. package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
  91. package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
  92. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
  93. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
  94. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
  95. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
  96. package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
  97. package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
  98. package/dist/form/Autocomplete.svelte +86 -135
  99. package/dist/form/Autocomplete.svelte.d.ts +6 -8
  100. package/dist/form/AutocompleteMulti.svelte +385 -227
  101. package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
  102. package/dist/form/Checkbox.svelte +59 -54
  103. package/dist/form/Checkbox.svelte.d.ts +14 -5
  104. package/dist/form/Chips.svelte +29 -13
  105. package/dist/form/Chips.svelte.d.ts +16 -2
  106. package/dist/form/ComboBox.svelte +30 -20
  107. package/dist/form/ComboBox.svelte.d.ts +1 -1
  108. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  109. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  110. package/dist/form/ComboBoxMulti.svelte +38 -24
  111. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  112. package/dist/form/CronBuilder.svelte +84 -67
  113. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  114. package/dist/form/DatePicker.svelte +148 -195
  115. package/dist/form/DateRangePicker.svelte +101 -98
  116. package/dist/form/DateTimePicker.svelte +167 -208
  117. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  118. package/dist/form/FormGroup.svelte +33 -10
  119. package/dist/form/FormGroup.svelte.d.ts +12 -2
  120. package/dist/form/Number.svelte +70 -47
  121. package/dist/form/Number.svelte.d.ts +36 -6
  122. package/dist/form/Radio.svelte +18 -18
  123. package/dist/form/RadioGroup.svelte +23 -12
  124. package/dist/form/RadioGroup.svelte.d.ts +13 -2
  125. package/dist/form/SegmentedControl.svelte +76 -56
  126. package/dist/form/SegmentedControl.svelte.d.ts +9 -4
  127. package/dist/form/Select.svelte +150 -233
  128. package/dist/form/Select.svelte.d.ts +29 -5
  129. package/dist/form/SelectMulti.svelte +163 -277
  130. package/dist/form/SelectMulti.svelte.d.ts +36 -7
  131. package/dist/form/Slider.svelte +183 -269
  132. package/dist/form/Slider.svelte.d.ts +32 -17
  133. package/dist/form/StringArrayBuilder.svelte +249 -0
  134. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  135. package/dist/form/Switch.svelte +164 -106
  136. package/dist/form/Switch.svelte.d.ts +22 -3
  137. package/dist/form/TextArea.svelte +49 -29
  138. package/dist/form/TextArea.svelte.d.ts +23 -3
  139. package/dist/form/Textbox.svelte +69 -68
  140. package/dist/form/Textbox.svelte.d.ts +32 -4
  141. package/dist/form/TimePicker.svelte +42 -47
  142. package/dist/form/TimePicker.svelte.d.ts +12 -2
  143. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  144. package/dist/form/comboBoxCore.svelte.js +243 -0
  145. package/dist/icon/Icon.svelte +8 -3
  146. package/dist/icon/Icon.svelte.d.ts +8 -3
  147. package/dist/icon/file-type-bmp.svg +1 -0
  148. package/dist/icon/file-type-csv.svg +1 -0
  149. package/dist/icon/file-type-doc.svg +1 -0
  150. package/dist/icon/file-type-docx.svg +1 -0
  151. package/dist/icon/file-type-jpg.svg +1 -0
  152. package/dist/icon/file-type-pdf.svg +1 -0
  153. package/dist/icon/file-type-png.svg +1 -0
  154. package/dist/icon/file-type-ppt.svg +1 -0
  155. package/dist/icon/file-type-svg.svg +1 -0
  156. package/dist/icon/file-type-txt.svg +1 -0
  157. package/dist/icon/file-type-xls.svg +1 -0
  158. package/dist/icon/file-type-zip.svg +1 -0
  159. package/dist/icon/index.d.ts +166 -151
  160. package/dist/icon/index.js +166 -152
  161. package/dist/icon/logo-placeholder.svg +6 -0
  162. package/dist/icon/source-code.svg +1 -0
  163. package/dist/icon/sun-moon.svg +1 -0
  164. package/dist/icon/table.svg +6 -0
  165. package/dist/index.d.ts +87 -26
  166. package/dist/index.js +25 -28
  167. package/dist/layout/AppShell.svelte +12 -162
  168. package/dist/layout/AppShell.svelte.d.ts +6 -27
  169. package/dist/layout/CustomNavbar.svelte +9 -7
  170. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  171. package/dist/layout/Navbar.svelte +161 -129
  172. package/dist/layout/Navbar.svelte.d.ts +29 -15
  173. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  174. package/dist/layout/NavbarItemView.svelte +182 -0
  175. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  176. package/dist/layout/Sidenav.svelte +280 -341
  177. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  178. package/dist/models/ComboBoxItem.d.ts +2 -0
  179. package/dist/models/Employee.d.ts +53 -0
  180. package/dist/models/Employee.js +23 -0
  181. package/dist/models/Hyperlink.d.ts +3 -1
  182. package/dist/models/LinkCollectionItem.d.ts +3 -1
  183. package/dist/models/NavbarItem.d.ts +6 -2
  184. package/dist/theme.svelte.d.ts +0 -1
  185. package/dist/theme.svelte.js +46 -58
  186. package/dist/ui/Accordion.svelte +81 -242
  187. package/dist/ui/Accordion.svelte.d.ts +37 -14
  188. package/dist/ui/AccordionItem.svelte +303 -0
  189. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  190. package/dist/ui/ActionIcon.svelte +25 -21
  191. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  192. package/dist/ui/Badge.svelte +163 -171
  193. package/dist/ui/Badge.svelte.d.ts +13 -5
  194. package/dist/ui/Button.svelte +178 -205
  195. package/dist/ui/Button.svelte.d.ts +19 -2
  196. package/dist/ui/ButtonVariant.d.ts +15 -1
  197. package/dist/ui/ContextMenu.svelte +146 -0
  198. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  199. package/dist/ui/ContextMenuList.svelte +188 -0
  200. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  201. package/dist/ui/Dialog.svelte +183 -111
  202. package/dist/ui/Dialog.svelte.d.ts +21 -4
  203. package/dist/ui/Drawer.svelte +237 -184
  204. package/dist/ui/Drawer.svelte.d.ts +20 -2
  205. package/dist/ui/Dropdown.svelte +24 -14
  206. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  207. package/dist/ui/Dropzone.svelte +159 -57
  208. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  209. package/dist/ui/Link.svelte +12 -4
  210. package/dist/ui/Link.svelte.d.ts +8 -2
  211. package/dist/ui/Loader.svelte +23 -18
  212. package/dist/ui/Loader.svelte.d.ts +5 -2
  213. package/dist/ui/Logo.svelte +37 -0
  214. package/dist/ui/Logo.svelte.d.ts +16 -0
  215. package/dist/ui/MultiSortable.svelte +287 -0
  216. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  217. package/dist/ui/Pagination.svelte +15 -9
  218. package/dist/ui/Pagination.svelte.d.ts +6 -2
  219. package/dist/ui/Popover.svelte +43 -65
  220. package/dist/ui/Popover.svelte.d.ts +14 -5
  221. package/dist/ui/ProgressBar.svelte +28 -30
  222. package/dist/ui/ProgressBarVariant.d.ts +7 -1
  223. package/dist/ui/SegmentedControl.svelte +0 -0
  224. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  225. package/dist/ui/Sortable.svelte +163 -0
  226. package/dist/ui/Sortable.svelte.d.ts +47 -0
  227. package/dist/ui/Table.svelte +383 -80
  228. package/dist/ui/Table.svelte.d.ts +43 -2
  229. package/dist/ui/Tabs.svelte +22 -18
  230. package/dist/ui/Tabs.svelte.d.ts +6 -2
  231. package/dist/ui/ThemeSwitcher.svelte +26 -7
  232. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  233. package/dist/ui/Tooltip.svelte +312 -180
  234. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  235. package/dist/uiTheme.svelte.d.ts +86 -0
  236. package/dist/uiTheme.svelte.js +214 -0
  237. package/dist/util/DateFunctions.js +1 -1
  238. package/dist/util/DragDrop.d.ts +50 -0
  239. package/dist/util/DragDrop.js +154 -0
  240. package/dist/util/NavigateTo.js +2 -2
  241. package/dist/util/dayjs.d.ts +4 -0
  242. package/dist/util/dayjs.js +15 -0
  243. package/package.json +66 -26
  244. package/dist/display/Alert.svelte +0 -179
  245. package/dist/display/Alert.svelte.d.ts +0 -13
  246. package/dist/form/FileInput.svelte +0 -235
  247. package/dist/form/FileInput.svelte.d.ts +0 -23
  248. package/dist/form/PasswordInput.svelte +0 -253
  249. package/dist/form/PasswordInput.svelte.d.ts +0 -34
  250. package/dist/form/Rating.svelte +0 -235
  251. package/dist/form/Rating.svelte.d.ts +0 -21
  252. package/dist/styles/components.css +0 -199
  253. package/dist/styles/dark.css +0 -146
  254. package/dist/styles/index.css +0 -116
  255. package/dist/styles/reset.css +0 -110
  256. package/dist/styles/semantic.css +0 -86
  257. package/dist/styles/tokens.css +0 -203
  258. package/dist/styles/utilities.css +0 -523
  259. package/dist/ui/Breadcrumbs.svelte +0 -131
  260. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  261. package/dist/ui/LoadingOverlay.svelte +0 -53
  262. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  263. package/dist/ui/Progress.svelte +0 -191
  264. package/dist/ui/Progress.svelte.d.ts +0 -15
  265. package/dist/ui/RingProgress.svelte +0 -141
  266. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  267. package/dist/ui/Skeleton.svelte +0 -85
  268. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  269. package/dist/ui/Stepper.svelte +0 -355
  270. package/dist/ui/Stepper.svelte.d.ts +0 -24
  271. package/dist/ui/Timeline.svelte +0 -225
  272. package/dist/ui/Timeline.svelte.d.ts +0 -23
  273. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  274. /package/dist/{actions → util}/ClickOutside.js +0 -0
  275. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  276. /package/dist/{transitions → util}/Transitions.js +0 -0
@@ -3,7 +3,15 @@
3
3
  import Textbox from '../form/Textbox.svelte';
4
4
  import Checkbox from '../form/Checkbox.svelte';
5
5
  import Icon from '../icon/Icon.svelte';
6
- import { iconArrowsSort, iconChevronDown, iconChevronUp, iconSearch } from '../icon/index.js';
6
+ import Pagination from './Pagination.svelte';
7
+ import {
8
+ iconArrowsSort,
9
+ iconChevronDown,
10
+ iconChevronRight,
11
+ iconChevronUp,
12
+ iconSearch
13
+ } from '../icon/index.js';
14
+ import { navigateTo } from '../util/NavigateTo.js';
7
15
 
8
16
  export interface Column<T = any> {
9
17
  key: string;
@@ -11,10 +19,14 @@
11
19
  sortable?: boolean;
12
20
  width?: string;
13
21
  align?: 'left' | 'center' | 'right';
22
+ /** Snippet rendered as the cell content. Takes precedence over `render`. */
23
+ cell?: Snippet<[{ value: any; row: T; index: number }]>;
24
+ /** @deprecated Returns raw HTML injected via {@html} — unsafe for untrusted
25
+ * data and cannot render components. Use `cell` instead. */
14
26
  render?: (value: any, row: T) => any;
15
27
  }
16
28
 
17
- interface Props<T = any> {
29
+ export interface TableProps<T = any> {
18
30
  /** Column definitions for the table */
19
31
  columns: Column<T>[];
20
32
  /** Data rows to display */
@@ -41,6 +53,10 @@
41
53
  buttons?: Snippet<[T, number]>;
42
54
  /** Callback when a row is clicked */
43
55
  onrowclick?: (row: T, index: number) => void;
56
+ /** Returns a URL for a row. When provided, rows act as links: a plain click
57
+ * navigates client-side, while middle-click or ctrl/cmd/shift-click opens the
58
+ * URL in a new tab. Return a falsy value to make a row non-navigable. */
59
+ rowHref?: (row: T, index: number) => string | undefined | null;
44
60
  /** Enable checkbox selection */
45
61
  selectable?: boolean;
46
62
  /** Field name used to uniquely identify rows (required when selectable is true) */
@@ -49,6 +65,35 @@
49
65
  selectedRows?: T[];
50
66
  /** Callback when selection changes */
51
67
  onselect?: (selectedRows: T[]) => void;
68
+ /** Detail content rendered in a full-width row beneath an expanded row.
69
+ * Providing this enables row expansion: expandable rows toggle on row click
70
+ * or via their chevron button. Requires rowKey. */
71
+ expandedContent?: Snippet<[T, number]>;
72
+ /** Limits which rows can expand (e.g. only group rows). Default: all rows. */
73
+ canExpand?: (row: T, index: number) => boolean;
74
+ /** Keys (via rowKey) of currently expanded rows (bindable) */
75
+ expandedKeys?: any[];
76
+ /** Callback when a row is expanded or collapsed — trigger on-demand fetches here */
77
+ onexpand?: (row: T, index: number, expanded: boolean) => void;
78
+ /** Rows per page. Setting this enables built-in client-side pagination,
79
+ * applied after search filtering and sorting. */
80
+ pageSize?: number;
81
+ /** Current page number, 1-based (bindable) */
82
+ page?: number;
83
+ /** Callback when the current page changes */
84
+ onpagechange?: (page: number) => void;
85
+ /** Key of the column currently sorted by, or null for unsorted (bindable).
86
+ * Set it to render the sort indicator for a server-supplied sort order. */
87
+ sortKey?: string | null;
88
+ /** Current sort direction (bindable) */
89
+ sortDirection?: 'asc' | 'desc';
90
+ /** Callback when the user clicks a sortable header, after `sortKey` and
91
+ * `sortDirection` have been updated. Use it to request sorted data from a
92
+ * server. Paginated tables also reset to page 1 before this fires. */
93
+ onsort?: (key: string, direction: 'asc' | 'desc') => void;
94
+ /** Skip the built-in client-side sort and render `rows` in the order given.
95
+ * Use with `onsort` when the server returns pre-sorted rows. */
96
+ manualSort?: boolean;
52
97
  }
53
98
 
54
99
  let {
@@ -65,24 +110,85 @@
65
110
  children,
66
111
  buttons,
67
112
  onrowclick,
113
+ rowHref = undefined,
68
114
  selectable = false,
69
115
  rowKey = undefined,
70
116
  selectedRows = $bindable([]),
71
- onselect = undefined
72
- }: Props = $props();
117
+ onselect = undefined,
118
+ expandedContent = undefined,
119
+ canExpand = undefined,
120
+ expandedKeys = $bindable([]),
121
+ onexpand = undefined,
122
+ pageSize = undefined,
123
+ page = $bindable(1),
124
+ onpagechange = undefined,
125
+ sortKey = $bindable(null),
126
+ sortDirection = $bindable('asc'),
127
+ onsort = undefined,
128
+ manualSort = false
129
+ }: TableProps = $props();
73
130
 
74
131
  let searchQuery = $state('');
75
- let sortColumn = $state<string | null>(null);
76
- let sortDirection = $state<'asc' | 'desc'>('asc');
77
132
 
78
133
  let _selectionWarned = false;
79
- $effect(() => { if (selectable && !rowKey && !_selectionWarned) { _selectionWarned = true; console.warn('Table: rowKey is required when selectable is true'); } });
134
+ $effect(() => {
135
+ if (selectable && !rowKey && !_selectionWarned) {
136
+ _selectionWarned = true;
137
+ console.warn('Table: rowKey is required when selectable is true');
138
+ }
139
+ });
140
+
141
+ let _expandWarned = false;
142
+ $effect(() => {
143
+ if (expandedContent && !rowKey && !_expandWarned) {
144
+ _expandWarned = true;
145
+ console.warn('Table: rowKey is required when expandedContent is provided');
146
+ }
147
+ });
148
+
149
+ const expandable = $derived(!!expandedContent && !!rowKey);
150
+
151
+ // Prune expandedKeys when rows changes to remove entries no longer present
152
+ $effect(() => {
153
+ if (!expandable || expandedKeys.length === 0) return;
154
+ const validKeys = new Set(rows.map((r) => getValue(r, rowKey!)));
155
+ const pruned = expandedKeys.filter((k) => validKeys.has(k));
156
+ if (pruned.length !== expandedKeys.length) {
157
+ expandedKeys = pruned;
158
+ }
159
+ });
160
+
161
+ // Set of expanded row keys for O(1) lookup
162
+ const expandedKeySet = $derived(new Set(expandedKeys));
163
+
164
+ function isRowExpanded(row: any): boolean {
165
+ if (!expandable) return false;
166
+ return expandedKeySet.has(getValue(row, rowKey!));
167
+ }
168
+
169
+ function rowCanExpand(row: any, index: number): boolean {
170
+ if (!expandable) return false;
171
+ return canExpand ? canExpand(row, index) : true;
172
+ }
173
+
174
+ function toggleExpand(row: any, index: number) {
175
+ if (!rowCanExpand(row, index)) return;
176
+
177
+ const key = getValue(row, rowKey!);
178
+ const expanded = !expandedKeySet.has(key);
179
+ if (expanded) {
180
+ expandedKeys = [...expandedKeys, key];
181
+ } else {
182
+ expandedKeys = expandedKeys.filter((k) => k !== key);
183
+ }
184
+ onexpand?.(row, index, expanded);
185
+ }
80
186
 
81
- // Prune selectedRows when rows change to remove entries no longer present
187
+ // Prune selectedRows when rows changes to remove entries no longer present
82
188
  $effect(() => {
83
189
  if (!selectable || !rowKey || selectedRows.length === 0) return;
84
- const validKeys = new Set(rows.map(r => getValue(r, rowKey)));
85
- const pruned = selectedRows.filter(r => validKeys.has(getValue(r, rowKey)));
190
+ const validKeys = new Set(rows.map((r) => getValue(r, rowKey)));
191
+ const pruned = selectedRows.filter((r) => validKeys.has(getValue(r, rowKey)));
86
192
  if (pruned.length !== selectedRows.length) {
87
193
  selectedRows = pruned;
88
194
  onselect?.(selectedRows);
@@ -92,64 +198,101 @@
92
198
  // Set of selected row keys for O(1) lookup
93
199
  const selectedKeys = $derived.by(() => {
94
200
  if (!selectable || !rowKey) return new Set<any>();
95
- return new Set(selectedRows.map(r => getValue(r, rowKey)));
201
+ return new Set(selectedRows.map((r) => getValue(r, rowKey)));
96
202
  });
97
203
 
204
+ // Check if a row is selected
98
205
  function isRowSelected(row: any): boolean {
99
206
  if (!selectable || !rowKey) return false;
100
207
  return selectedKeys.has(getValue(row, rowKey));
101
208
  }
102
209
 
210
+ // Toggle individual row selection
103
211
  function toggleRow(row: any) {
104
212
  if (!selectable || !rowKey) return;
105
213
 
106
214
  const key = getValue(row, rowKey);
107
215
  if (selectedKeys.has(key)) {
108
- selectedRows = selectedRows.filter(r => getValue(r, rowKey) !== key);
216
+ // Deselect: remove from selectedRows
217
+ selectedRows = selectedRows.filter((r) => getValue(r, rowKey) !== key);
109
218
  } else {
219
+ // Select: add to selectedRows
110
220
  selectedRows = [...selectedRows, row];
111
221
  }
112
222
  onselect?.(selectedRows);
113
223
  }
114
224
 
225
+ // Handle a row click. Middle-click and ctrl/cmd/shift-click are treated as
226
+ // open-in-new-tab intent when a rowHref is provided.
227
+ function handleRowClick(row: any, index: number, event: MouseEvent) {
228
+ const href = rowHref?.(row, index);
229
+ const newTabIntent = event.button === 1 || event.ctrlKey || event.metaKey || event.shiftKey;
230
+
231
+ // Expansion/selection toggling only applies to a plain primary click without
232
+ // nav intent. Expansion wins on rows that can expand; selection stays
233
+ // available via the checkbox and still handles non-expandable rows.
234
+ if (expandable && !href && event.button !== 1 && rowCanExpand(row, index)) {
235
+ toggleExpand(row, index);
236
+ } else if (selectable && !href && event.button !== 1) {
237
+ toggleRow(row);
238
+ }
239
+
240
+ onrowclick?.(row, index);
241
+
242
+ if (href) {
243
+ event.preventDefault();
244
+ if (newTabIntent) window.open(href, '_blank', 'noopener');
245
+ else navigateTo(href);
246
+ }
247
+ }
248
+
249
+ // Toggle all visible rows (the current page when paginated, else all filtered rows)
115
250
  function toggleAll() {
116
251
  if (!selectable || !rowKey) return;
117
252
 
118
- const allSelected = filteredRows.length > 0 && filteredRows.every(row => selectedKeys.has(getValue(row, rowKey!)));
253
+ // Check if all visible rows are selected
254
+ const allSelected =
255
+ pagedRows.length > 0 && pagedRows.every((row) => selectedKeys.has(getValue(row, rowKey!)));
119
256
 
120
257
  if (allSelected) {
121
- // Deselect all filtered rows, preserve off-screen selections
122
- const filteredKeys = new Set(filteredRows.map(r => getValue(r, rowKey!)));
123
- selectedRows = selectedRows.filter(r => !filteredKeys.has(getValue(r, rowKey!)));
258
+ // Deselect all visible rows
259
+ const visibleKeys = new Set(pagedRows.map((r) => getValue(r, rowKey!)));
260
+ selectedRows = selectedRows.filter((r) => !visibleKeys.has(getValue(r, rowKey!)));
124
261
  } else {
125
- // Select all filtered rows, merge with existing
126
- const existingKeys = new Set(selectedRows.map(r => getValue(r, rowKey!)));
127
- const newSelections = filteredRows.filter(r => !existingKeys.has(getValue(r, rowKey!)));
262
+ // Select all visible rows (merge with existing selections elsewhere)
263
+ const existingKeys = new Set(selectedRows.map((r) => getValue(r, rowKey!)));
264
+ const newSelections = pagedRows.filter((r) => !existingKeys.has(getValue(r, rowKey!)));
128
265
  selectedRows = [...selectedRows, ...newSelections];
129
266
  }
130
267
  onselect?.(selectedRows);
131
268
  }
132
269
 
270
+ // Derived states for select-all checkbox
133
271
  const isAllSelected = $derived.by(() => {
134
- if (!selectable || !rowKey || filteredRows.length === 0) return false;
135
- return filteredRows.every(row => selectedKeys.has(getValue(row, rowKey)));
272
+ if (!selectable || !rowKey || pagedRows.length === 0) return false;
273
+ return pagedRows.every((row) => selectedKeys.has(getValue(row, rowKey)));
136
274
  });
137
275
 
138
276
  const isSomeSelected = $derived.by(() => {
139
- if (!selectable || !rowKey || filteredRows.length === 0) return false;
140
- const selectedCount = filteredRows.filter(row => selectedKeys.has(getValue(row, rowKey))).length;
141
- return selectedCount > 0 && selectedCount < filteredRows.length;
277
+ if (!selectable || !rowKey || pagedRows.length === 0) return false;
278
+ const selectedCount = pagedRows.filter((row) => selectedKeys.has(getValue(row, rowKey))).length;
279
+ return selectedCount > 0 && selectedCount < pagedRows.length;
142
280
  });
143
281
 
144
282
  function handleSort(column: Column) {
145
283
  if (!column.sortable) return;
146
284
 
147
- if (sortColumn === column.key) {
285
+ if (sortKey === column.key) {
148
286
  sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
149
287
  } else {
150
- sortColumn = column.key;
288
+ sortKey = column.key;
151
289
  sortDirection = 'asc';
152
290
  }
291
+
292
+ // A new sort order invalidates the current page position
293
+ if (paginated && page !== 1) page = 1;
294
+
295
+ onsort?.(column.key, sortDirection);
153
296
  }
154
297
 
155
298
  function getValue(row: any, key: string): any {
@@ -166,8 +309,8 @@
166
309
 
167
310
  // Apply search filter
168
311
  if (searchable && searchQuery) {
169
- filtered = filtered.filter(row => {
170
- return columns.some(column => {
312
+ filtered = filtered.filter((row) => {
313
+ return columns.some((column) => {
171
314
  const value = getValue(row, column.key);
172
315
  if (value === null || value === undefined) return false;
173
316
  return String(value).toLowerCase().includes(searchQuery.toLowerCase());
@@ -175,11 +318,11 @@
175
318
  });
176
319
  }
177
320
 
178
- // Apply sorting
179
- if (sortColumn) {
321
+ // Apply sorting — skipped when the caller sorts the rows itself (e.g. server-side)
322
+ if (sortKey && !manualSort) {
180
323
  filtered.sort((a, b) => {
181
- const aValue = getValue(a, sortColumn!);
182
- const bValue = getValue(b, sortColumn!);
324
+ const aValue = getValue(a, sortKey!);
325
+ const bValue = getValue(b, sortKey!);
183
326
 
184
327
  if (aValue === null || aValue === undefined) return 1;
185
328
  if (bValue === null || bValue === undefined) return -1;
@@ -198,15 +341,51 @@
198
341
  return filtered;
199
342
  });
200
343
 
344
+ // Pagination — applied after search filtering and sorting
345
+ const paginated = $derived(!!pageSize && pageSize > 0);
346
+ const totalPages = $derived(
347
+ paginated ? Math.max(1, Math.ceil(filteredRows.length / pageSize!)) : 1
348
+ );
349
+
350
+ // Clamp page when filtering or a rows update shrinks the page count
351
+ $effect(() => {
352
+ if (page > totalPages) page = totalPages;
353
+ else if (page < 1) page = 1;
354
+ });
355
+
356
+ // A new search invalidates the current page position — jump back to the first page
357
+ let _lastQuery = '';
358
+ $effect(() => {
359
+ if (searchQuery !== _lastQuery) {
360
+ _lastQuery = searchQuery;
361
+ if (page !== 1) page = 1;
362
+ }
363
+ });
364
+
365
+ const pagedRows = $derived.by(() => {
366
+ if (!paginated) return filteredRows;
367
+ const start = (page - 1) * pageSize!;
368
+ return filteredRows.slice(start, start + pageSize!);
369
+ });
370
+
371
+ const pageStart = $derived(paginated ? (page - 1) * pageSize! + 1 : 1);
372
+ const pageEnd = $derived(
373
+ paginated ? Math.min(page * pageSize!, filteredRows.length) : filteredRows.length
374
+ );
375
+
201
376
  const getSortIcon = (column: Column) => {
202
377
  if (!column.sortable) return '';
203
- if (sortColumn !== column.key) return iconArrowsSort;
378
+ if (sortKey !== column.key) return iconArrowsSort;
204
379
  return sortDirection === 'asc' ? iconChevronUp : iconChevronDown;
205
380
  };
206
381
 
207
382
  const tableClasses = $derived(
208
383
  `pui-table ${tableClass} ${striped ? 'striped' : ''} ${hover ? 'hover' : ''}`
209
384
  );
385
+
386
+ const totalColumns = $derived(
387
+ columns.length + (expandable ? 1 : 0) + (selectable && rowKey ? 1 : 0) + (buttons ? 1 : 0)
388
+ );
210
389
  </script>
211
390
 
212
391
  <div class="table-container">
@@ -225,12 +404,17 @@
225
404
  <table class={tableClasses}>
226
405
  <thead>
227
406
  <tr>
407
+ {#if expandable}
408
+ <th class="table-header expand-cell" style="width: 40px"></th>
409
+ {/if}
228
410
  {#if selectable && rowKey}
229
411
  <th class="table-header checkbox-cell" style="width: 40px">
230
412
  <Checkbox
231
413
  controlled
232
414
  checked={isAllSelected}
415
+ indeterminate={isSomeSelected}
233
416
  onchange={() => toggleAll()}
417
+ ariaLabel="Select all rows"
234
418
  />
235
419
  </th>
236
420
  {/if}
@@ -244,10 +428,7 @@
244
428
  <span>{column.label}</span>
245
429
  {#if column.sortable}
246
430
  <div class="sort-icon">
247
- <Icon
248
- svg={getSortIcon(column)}
249
- size="14px"
250
- />
431
+ <Icon svg={getSortIcon(column)} size="14px" />
251
432
  </div>
252
433
  {/if}
253
434
  </div>
@@ -261,38 +442,70 @@
261
442
  <tbody>
262
443
  {#if loading}
263
444
  <tr>
264
- <td colspan={columns.length + (selectable && rowKey ? 1 : 0) + (buttons ? 1 : 0)} class="loading-cell">
445
+ <td colspan={totalColumns} class="loading-cell">
265
446
  <div class="loading-content">Loading...</div>
266
447
  </td>
267
448
  </tr>
268
449
  {:else if filteredRows.length === 0}
269
450
  <tr>
270
- <td colspan={columns.length + (selectable && rowKey ? 1 : 0) + (buttons ? 1 : 0)} class="empty-cell">
451
+ <td colspan={totalColumns} class="empty-cell">
271
452
  <div class="empty-content">{emptyMessage}</div>
272
453
  </td>
273
454
  </tr>
274
455
  {:else}
275
- {#each filteredRows as row, index}
456
+ {#each pagedRows as row, index}
457
+ {@const expanded = isRowExpanded(row)}
276
458
  <tr
277
- class="{onrowclick || selectable ? 'clickable-row' : ''} {selectable && isRowSelected(row) ? 'selected' : ''}"
278
- onclick={() => {
279
- if (selectable) toggleRow(row);
280
- onrowclick?.(row, index);
459
+ class={(onrowclick ||
460
+ selectable ||
461
+ rowHref ||
462
+ (expandable && rowCanExpand(row, index))
463
+ ? 'clickable-row'
464
+ : '') +
465
+ (selectable && isRowSelected(row) ? ' selected' : '') +
466
+ (expanded ? ' expanded' : '')}
467
+ class:row-even={index % 2 === 1}
468
+ onclick={(e) => handleRowClick(row, index, e)}
469
+ onmousedown={(e) => {
470
+ // Suppress the default middle-button action (autoscroll) so auxclick fires.
471
+ if (e.button === 1 && rowHref?.(row, index)) e.preventDefault();
472
+ }}
473
+ onauxclick={(e) => {
474
+ if (e.button === 1) handleRowClick(row, index, e);
281
475
  }}
282
476
  >
477
+ {#if expandable}
478
+ <td class="table-cell expand-cell" onclick={(e) => e.stopPropagation()}>
479
+ {#if rowCanExpand(row, index)}
480
+ <button
481
+ type="button"
482
+ class="expand-toggle"
483
+ class:expanded
484
+ aria-expanded={expanded}
485
+ aria-label={expanded ? 'Collapse row' : 'Expand row'}
486
+ onclick={() => toggleExpand(row, index)}
487
+ >
488
+ <Icon svg={iconChevronRight} size="16px" />
489
+ </button>
490
+ {/if}
491
+ </td>
492
+ {/if}
283
493
  {#if selectable && rowKey}
284
494
  <td class="table-cell checkbox-cell" onclick={(e) => e.stopPropagation()}>
285
495
  <Checkbox
286
496
  controlled
287
497
  checked={isRowSelected(row)}
288
498
  onchange={() => toggleRow(row)}
499
+ ariaLabel="Select row"
289
500
  />
290
501
  </td>
291
502
  {/if}
292
503
  {#each columns as column}
293
504
  {@const value = getValue(row, column.key)}
294
505
  <td class="table-cell {column.align || 'left'}">
295
- {#if column.render}
506
+ {#if column.cell}
507
+ {@render column.cell({ value, row, index })}
508
+ {:else if column.render}
296
509
  {@html column.render(value, row)}
297
510
  {:else if typeof value === 'function'}
298
511
  {@render value()}
@@ -307,12 +520,28 @@
307
520
  </td>
308
521
  {/if}
309
522
  </tr>
523
+ {#if expanded && expandedContent}
524
+ <tr class="detail-row" class:row-even={index % 2 === 1}>
525
+ <td class="detail-cell" colspan={totalColumns}>
526
+ {@render expandedContent(row, index)}
527
+ </td>
528
+ </tr>
529
+ {/if}
310
530
  {/each}
311
531
  {/if}
312
532
  </tbody>
313
533
  </table>
314
534
  </div>
315
535
 
536
+ {#if paginated && !loading && totalPages > 1}
537
+ <div class="table-pagination">
538
+ <span class="pagination-info">
539
+ Showing {pageStart}–{pageEnd} of {filteredRows.length}
540
+ </span>
541
+ <Pagination bind:selectedPage={page} {totalPages} onPageChange={(p) => onpagechange?.(p)} />
542
+ </div>
543
+ {/if}
544
+
316
545
  {@render children?.()}
317
546
  </div>
318
547
 
@@ -322,45 +551,45 @@
322
551
  }
323
552
 
324
553
  .table-search {
325
- margin-bottom: var(--pui-spacing-4);
554
+ margin-bottom: 1rem;
326
555
  max-width: 400px;
327
556
  }
328
557
 
329
558
  .table-wrapper {
330
559
  width: 100%;
331
560
  overflow-x: auto;
332
- border: 1px solid var(--pui-border-default);
333
- background-color: var(--pui-input-bg);
561
+ border: 1px solid var(--border-color);
562
+ background-color: var(--input-bg-color);
334
563
  }
335
564
 
336
565
  .table-wrapper.rounded {
337
- border-radius: var(--pui-radius-lg);
566
+ border-radius: 0.5rem;
338
567
  }
339
568
 
340
569
  .pui-table {
341
570
  width: 100%;
342
571
  border-collapse: collapse;
343
- font-size: var(--pui-font-size-base);
572
+ font-size: 0.875rem;
344
573
  }
345
574
 
346
575
  .table-header {
347
- padding: var(--pui-spacing-2);
576
+ padding: 0.5rem;
348
577
  text-align: left;
349
- font-weight: var(--pui-font-weight-semibold);
350
- color: var(--pui-text-primary);
351
- background-color: var(--pui-bg-surface-raised);
352
- border-bottom: 2px solid var(--pui-border-default);
578
+ font-weight: 600;
579
+ color: var(--text-color);
580
+ background-color: var(--bg-color);
581
+ border-bottom: 2px solid var(--border-color);
353
582
  user-select: none;
354
583
  white-space: nowrap;
355
584
  }
356
585
 
357
586
  .table-header.sortable {
358
587
  cursor: pointer;
359
- transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
588
+ transition: background-color 0.2s;
360
589
  }
361
590
 
362
591
  .table-header.sortable:hover {
363
- background-color: var(--pui-bg-hover);
592
+ background-color: var(--hover-bg);
364
593
  }
365
594
 
366
595
  .table-header.center {
@@ -374,7 +603,7 @@
374
603
  .header-content {
375
604
  display: flex;
376
605
  align-items: center;
377
- gap: var(--pui-spacing-2);
606
+ gap: 0.5rem;
378
607
  justify-content: space-between;
379
608
  }
380
609
 
@@ -382,7 +611,7 @@
382
611
  display: inline-flex;
383
612
  align-items: center;
384
613
  opacity: 0.5;
385
- transition: opacity var(--pui-transition-fast) var(--pui-ease-out);
614
+ transition: opacity 0.2s;
386
615
  }
387
616
 
388
617
  .table-header.sortable:hover .sort-icon {
@@ -390,10 +619,10 @@
390
619
  }
391
620
 
392
621
  .table-cell {
393
- padding: var(--pui-spacing-2);
622
+ padding: 0.5rem;
394
623
  text-align: left;
395
- color: var(--pui-text-primary);
396
- border-bottom: 1px solid var(--pui-border-default);
624
+ color: var(--text-color);
625
+ border-bottom: 1px solid var(--border-color);
397
626
  }
398
627
 
399
628
  .table-cell.center {
@@ -407,7 +636,7 @@
407
636
  .checkbox-cell {
408
637
  width: 40px;
409
638
  text-align: center;
410
- padding: var(--pui-spacing-1);
639
+ padding: 0.25rem;
411
640
  }
412
641
 
413
642
  .checkbox-cell :global(.checkbox-container) {
@@ -419,23 +648,73 @@
419
648
  margin: 2px;
420
649
  }
421
650
 
422
- .pui-table.striped tbody tr:nth-child(even) {
423
- background-color: var(--pui-bg-hover);
651
+ .expand-cell {
652
+ width: 40px;
653
+ text-align: center;
654
+ padding: 0.25rem;
424
655
  }
425
656
 
426
- .pui-table.hover tbody tr:hover {
427
- background-color: var(--pui-bg-hover);
428
- transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
657
+ .expand-toggle {
658
+ display: inline-flex;
659
+ align-items: center;
660
+ justify-content: center;
661
+ width: 24px;
662
+ height: 24px;
663
+ padding: 0;
664
+ border: none;
665
+ background: transparent;
666
+ color: var(--text-color);
667
+ cursor: pointer;
668
+ border-radius: 0.25rem;
669
+ transition:
670
+ transform 0.15s ease,
671
+ background-color 0.2s;
429
672
  }
430
673
 
431
- .pui-table tbody tr.selected,
432
- .pui-table.striped tbody tr.selected:nth-child(even) {
433
- background-color: var(--pui-bg-active);
674
+ .expand-toggle:hover {
675
+ background-color: var(--hover-bg);
676
+ }
677
+
678
+ .expand-toggle.expanded {
679
+ transform: rotate(90deg);
680
+ }
681
+
682
+ /* An expanded row and its detail row read as one unit: the parent's border
683
+ * moves to the bottom of the detail row. */
684
+ tbody tr.expanded td {
685
+ border-bottom: none;
686
+ }
687
+
688
+ .detail-cell {
689
+ padding: 0.5rem;
690
+ color: var(--text-color);
691
+ border-bottom: 1px solid var(--border-color);
434
692
  }
435
693
 
694
+ .pui-table.striped tbody tr.row-even {
695
+ background-color: var(--stripe-bg);
696
+ }
697
+
698
+ .pui-table.hover tbody tr:not(.detail-row):hover {
699
+ background-color: var(--hover-bg);
700
+ transition: background-color 0.2s;
701
+ }
702
+
703
+ .pui-table tbody tr.selected,
704
+ .pui-table.striped tbody tr.selected.row-even {
705
+ background-color: rgba(var(--ui-tertiary-rgbc), 0.15);
706
+ }
436
707
  .pui-table tbody tr.selected:hover,
437
708
  .pui-table.hover tbody tr.selected:hover {
438
- background-color: var(--pui-bg-active);
709
+ background-color: rgba(var(--ui-tertiary-rgbc), 0.22);
710
+ }
711
+ :global(.dark) .pui-table tbody tr.selected,
712
+ :global(.dark) .pui-table.striped tbody tr.selected.row-even {
713
+ background-color: rgba(var(--ui-tertiary-rgbc), 0.12);
714
+ }
715
+ :global(.dark) .pui-table tbody tr.selected:hover,
716
+ :global(.dark) .pui-table.hover tbody tr.selected:hover {
717
+ background-color: rgba(var(--ui-tertiary-rgbc), 0.16);
439
718
  }
440
719
 
441
720
  .clickable-row {
@@ -443,8 +722,8 @@
443
722
  }
444
723
 
445
724
  .clickable-row:hover {
446
- background-color: var(--pui-bg-hover);
447
- transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
725
+ background-color: var(--hover-bg);
726
+ transition: background-color 0.2s;
448
727
  }
449
728
 
450
729
  tbody tr:last-child td {
@@ -453,9 +732,9 @@
453
732
 
454
733
  .empty-cell,
455
734
  .loading-cell {
456
- padding: var(--pui-spacing-8);
735
+ padding: 2rem;
457
736
  text-align: center;
458
- color: var(--pui-text-muted);
737
+ color: var(--text-muted);
459
738
  border-bottom: none;
460
739
  }
461
740
 
@@ -464,21 +743,45 @@
464
743
  font-style: italic;
465
744
  }
466
745
 
467
- /* Icon styles for sort indicators */
746
+ .table-pagination {
747
+ display: flex;
748
+ align-items: center;
749
+ justify-content: space-between;
750
+ gap: 1rem;
751
+ flex-wrap: wrap;
752
+ margin-top: 1rem;
753
+ }
754
+
755
+ .pagination-info {
756
+ color: var(--text-muted);
757
+ font-size: 0.875rem;
758
+ }
759
+
760
+ :global(:root) {
761
+ --stripe-bg: rgba(0, 0, 0, 0.02);
762
+ --hover-bg: rgba(0, 0, 0, 0.04);
763
+ --text-muted: #6b7280;
764
+ }
765
+
766
+ :global(.dark) {
767
+ --stripe-bg: rgba(255, 255, 255, 0.02);
768
+ --hover-bg: rgba(255, 255, 255, 0.04);
769
+ --text-muted: #9ca3af;
770
+ }
771
+
468
772
  :global(.pui-table .sort-icon svg) {
469
773
  width: 14px;
470
774
  height: 14px;
471
775
  }
472
776
 
473
- /* Responsive adjustments */
474
777
  @media (max-width: 640px) {
475
778
  .table-header,
476
779
  .table-cell {
477
- padding: var(--pui-spacing-2) var(--pui-spacing-3);
780
+ padding: 0.5rem 0.75rem;
478
781
  }
479
782
 
480
783
  .pui-table {
481
- font-size: var(--pui-font-size-xs);
784
+ font-size: 0.75rem;
482
785
  }
483
786
  }
484
787
  </style>