@devxcrew/react-ui 0.0.0-stage → 0.1.7

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 (326) hide show
  1. package/README.md +85 -2
  2. package/package.json +105 -4
  3. package/src/blocks/agent-chat-workspace/agent-chat-workspace.tsx +162 -0
  4. package/src/blocks/agent-chat-workspace/index.ts +2 -0
  5. package/src/blocks/agent-provider-settings/agent-provider-settings.tsx +183 -0
  6. package/src/blocks/agent-provider-settings/index.ts +2 -0
  7. package/src/blocks/agent-task-workspace/agent-task-workspace.tsx +48 -0
  8. package/src/blocks/agent-task-workspace/index.ts +2 -0
  9. package/src/blocks/analysis-context/analysis-context.tsx +6 -0
  10. package/src/blocks/analysis-context/index.ts +1 -0
  11. package/src/blocks/app-header/app-header.tsx +159 -0
  12. package/src/blocks/app-header/index.ts +2 -0
  13. package/src/blocks/archived-chat-workspace/archived-chat-workspace.tsx +29 -0
  14. package/src/blocks/archived-chat-workspace/index.ts +2 -0
  15. package/src/blocks/auth/README.md +32 -0
  16. package/src/blocks/auth/admin-login-page.tsx +15 -0
  17. package/src/blocks/auth/admin-password-forgot-page.tsx +5 -0
  18. package/src/blocks/auth/auth-page-layout.tsx +90 -0
  19. package/src/blocks/auth/client-login-page.tsx +20 -0
  20. package/src/blocks/auth/client-password-forgot-page.tsx +5 -0
  21. package/src/blocks/auth/identity-management-desk.tsx +530 -0
  22. package/src/blocks/auth/index.ts +12 -0
  23. package/src/blocks/auth/login-page.tsx +206 -0
  24. package/src/blocks/auth/password-forgot-page.tsx +60 -0
  25. package/src/blocks/auth/portal-pages.tsx +32 -0
  26. package/src/blocks/auth/privileged-desk.tsx +55 -0
  27. package/src/blocks/auth/register-page.tsx +117 -0
  28. package/src/blocks/auth/session-boundary.tsx +296 -0
  29. package/src/blocks/auth/super-admin-login-page.tsx +15 -0
  30. package/src/blocks/auth/super-admin-password-forgot-page.tsx +5 -0
  31. package/src/blocks/blog/blog-card.tsx +156 -0
  32. package/src/blocks/blog/blog-reader.tsx +175 -0
  33. package/src/blocks/blog/blog-types.ts +47 -0
  34. package/src/blocks/blog/index.ts +3 -0
  35. package/src/blocks/chat-composer/chat-composer.tsx +140 -0
  36. package/src/blocks/chat-composer/index.ts +1 -0
  37. package/src/blocks/chat-history/chat-history.tsx +59 -0
  38. package/src/blocks/chat-history/index.ts +1 -0
  39. package/src/blocks/chat-response-progress/chat-response-progress.tsx +34 -0
  40. package/src/blocks/chat-response-progress/index.ts +2 -0
  41. package/src/blocks/chat-runtime-controls/chat-runtime-controls.tsx +56 -0
  42. package/src/blocks/chat-runtime-controls/index.ts +2 -0
  43. package/src/blocks/chat-runtime-trace/chat-runtime-trace.tsx +71 -0
  44. package/src/blocks/chat-runtime-trace/index.ts +2 -0
  45. package/src/blocks/codex-connection-settings/codex-connection-settings.tsx +42 -0
  46. package/src/blocks/codex-connection-settings/index.ts +1 -0
  47. package/src/blocks/dropzone/dropzone-file-item.tsx +113 -0
  48. package/src/blocks/dropzone/dropzone-types.ts +32 -0
  49. package/src/blocks/dropzone/dropzone.tsx +216 -0
  50. package/src/blocks/dropzone/index.ts +3 -0
  51. package/src/blocks/ecommerce/cart/cart-drawer.tsx +228 -0
  52. package/src/blocks/ecommerce/cart/cart-types.ts +27 -0
  53. package/src/blocks/ecommerce/cart/index.ts +2 -0
  54. package/src/blocks/ecommerce/categories/categories-types.ts +23 -0
  55. package/src/blocks/ecommerce/categories/category-showcase.tsx +125 -0
  56. package/src/blocks/ecommerce/categories/index.ts +2 -0
  57. package/src/blocks/ecommerce/checkout/checkout-types.ts +48 -0
  58. package/src/blocks/ecommerce/checkout/checkout-wizard.tsx +468 -0
  59. package/src/blocks/ecommerce/checkout/index.ts +2 -0
  60. package/src/blocks/ecommerce/comparison/comparison-types.ts +28 -0
  61. package/src/blocks/ecommerce/comparison/index.ts +2 -0
  62. package/src/blocks/ecommerce/comparison/product-comparison.tsx +174 -0
  63. package/src/blocks/ecommerce/coupon-wallet/coupon-wallet-types.ts +21 -0
  64. package/src/blocks/ecommerce/coupon-wallet/coupon-wallet.tsx +154 -0
  65. package/src/blocks/ecommerce/coupon-wallet/index.ts +2 -0
  66. package/src/blocks/ecommerce/delivery-tracker/delivery-tracker-types.ts +20 -0
  67. package/src/blocks/ecommerce/delivery-tracker/delivery-tracker.tsx +169 -0
  68. package/src/blocks/ecommerce/delivery-tracker/index.ts +2 -0
  69. package/src/blocks/ecommerce/index.ts +12 -0
  70. package/src/blocks/ecommerce/payment-methods/index.ts +2 -0
  71. package/src/blocks/ecommerce/payment-methods/payment-method-selector.tsx +224 -0
  72. package/src/blocks/ecommerce/payment-methods/payment-methods-types.ts +21 -0
  73. package/src/blocks/ecommerce/price-history/index.ts +2 -0
  74. package/src/blocks/ecommerce/price-history/price-history-chart.tsx +217 -0
  75. package/src/blocks/ecommerce/price-history/price-history-types.ts +16 -0
  76. package/src/blocks/ecommerce/reviews/index.ts +2 -0
  77. package/src/blocks/ecommerce/reviews/reviews-section.tsx +309 -0
  78. package/src/blocks/ecommerce/reviews/reviews-types.ts +26 -0
  79. package/src/blocks/ecommerce/wishlist/index.ts +2 -0
  80. package/src/blocks/ecommerce/wishlist/wishlist-grid.tsx +149 -0
  81. package/src/blocks/ecommerce/wishlist/wishlist-types.ts +19 -0
  82. package/src/blocks/execution-status/README.md +25 -0
  83. package/src/blocks/execution-status/execution-status.tsx +135 -0
  84. package/src/blocks/execution-status/index.ts +2 -0
  85. package/src/blocks/file-tree/file-icons.tsx +84 -0
  86. package/src/blocks/file-tree/file-tree-node.tsx +158 -0
  87. package/src/blocks/file-tree/file-tree-types.ts +33 -0
  88. package/src/blocks/file-tree/file-tree.tsx +163 -0
  89. package/src/blocks/file-tree/index.ts +5 -0
  90. package/src/blocks/filter-builder/filter-builder.tsx +169 -0
  91. package/src/blocks/filter-builder/filter-row.tsx +164 -0
  92. package/src/blocks/filter-builder/filter-types.ts +61 -0
  93. package/src/blocks/filter-builder/index.ts +12 -0
  94. package/src/blocks/footer/footer-types.ts +35 -0
  95. package/src/blocks/footer/index.ts +2 -0
  96. package/src/blocks/footer/site-footer.tsx +212 -0
  97. package/src/blocks/form/form-block.tsx +113 -0
  98. package/src/blocks/form/form-lookup-field.tsx +60 -0
  99. package/src/blocks/form/index.ts +4 -0
  100. package/src/blocks/handover-stack/handover-stack.tsx +41 -0
  101. package/src/blocks/handover-stack/index.ts +1 -0
  102. package/src/blocks/idea-handover/idea-handover-readiness.ts +41 -0
  103. package/src/blocks/idea-handover/idea-handover-workspace.tsx +150 -0
  104. package/src/blocks/idea-handover/index.ts +4 -0
  105. package/src/blocks/kanban/index.ts +11 -0
  106. package/src/blocks/kanban/kanban-board.tsx +146 -0
  107. package/src/blocks/kanban/kanban-card.tsx +149 -0
  108. package/src/blocks/kanban/kanban-column.tsx +102 -0
  109. package/src/blocks/kanban/kanban-types.ts +38 -0
  110. package/src/blocks/loader/global-loader.tsx +90 -0
  111. package/src/blocks/loader/index.ts +1 -0
  112. package/src/blocks/mascot/README.md +21 -0
  113. package/src/blocks/mascot/index.ts +2 -0
  114. package/src/blocks/mascot/mascot.tsx +202 -0
  115. package/src/blocks/master-list/README.md +21 -0
  116. package/src/blocks/master-list/index.ts +12 -0
  117. package/src/blocks/master-list/master-form.tsx +157 -0
  118. package/src/blocks/master-list/master-list-desk.tsx +370 -0
  119. package/src/blocks/master-list/master-list.tsx +189 -0
  120. package/src/blocks/master-list/types.ts +18 -0
  121. package/src/blocks/mermaid-preview/index.ts +1 -0
  122. package/src/blocks/mermaid-preview/mermaid-preview.tsx +164 -0
  123. package/src/blocks/notifications/README.md +7 -0
  124. package/src/blocks/notifications/index.ts +1 -0
  125. package/src/blocks/notifications/notification-center-page.tsx +73 -0
  126. package/src/blocks/pricing/index.ts +7 -0
  127. package/src/blocks/pricing/pricing-table.tsx +173 -0
  128. package/src/blocks/pricing/pricing-types.ts +34 -0
  129. package/src/blocks/product-card/index.ts +2 -0
  130. package/src/blocks/product-card/product-card-types.ts +35 -0
  131. package/src/blocks/product-card/product-card.tsx +183 -0
  132. package/src/blocks/table/data-table-block.tsx +313 -0
  133. package/src/blocks/table/data-table-filter-menu.tsx +48 -0
  134. package/src/blocks/table/data-table-icon-menu-trigger.tsx +44 -0
  135. package/src/blocks/table/data-table-menu-content.tsx +32 -0
  136. package/src/blocks/table/data-table-pagination.tsx +149 -0
  137. package/src/blocks/table/data-table-row-actions.tsx +53 -0
  138. package/src/blocks/table/data-table-status.tsx +42 -0
  139. package/src/blocks/table/data-table-toolbar.tsx +124 -0
  140. package/src/blocks/table/data-table-totals.tsx +31 -0
  141. package/src/blocks/table/index.ts +13 -0
  142. package/src/blocks/workspace/index.ts +19 -0
  143. package/src/blocks/workspace/workspace-action-card.tsx +73 -0
  144. package/src/blocks/workspace/workspace-application-header.tsx +67 -0
  145. package/src/blocks/workspace/workspace-entity-card.tsx +48 -0
  146. package/src/blocks/workspace/workspace-health-summary.tsx +15 -0
  147. package/src/blocks/workspace/workspace-metric-card.tsx +97 -0
  148. package/src/blocks/workspace/workspace-page-header.tsx +43 -0
  149. package/src/blocks/workspace/workspace-publish-status.tsx +15 -0
  150. package/src/blocks/workspace/workspace-route-checklist.tsx +28 -0
  151. package/src/blocks/workspace/workspace-runtime-status.tsx +48 -0
  152. package/src/blocks/workspace/workspace-section-card.tsx +42 -0
  153. package/src/components/accordion.tsx +65 -0
  154. package/src/components/alert-dialog.tsx +160 -0
  155. package/src/components/alert.tsx +68 -0
  156. package/src/components/aspect-ratio.tsx +22 -0
  157. package/src/components/attachment.tsx +195 -0
  158. package/src/components/avatar.tsx +90 -0
  159. package/src/components/badge.tsx +55 -0
  160. package/src/components/breadcrumb.tsx +100 -0
  161. package/src/components/bubble.tsx +123 -0
  162. package/src/components/button-group.tsx +79 -0
  163. package/src/components/button.tsx +71 -0
  164. package/src/components/calendar.tsx +185 -0
  165. package/src/components/card.tsx +100 -0
  166. package/src/components/carousel.tsx +229 -0
  167. package/src/components/chart.tsx +338 -0
  168. package/src/components/checkbox.tsx +27 -0
  169. package/src/components/collapsible.tsx +15 -0
  170. package/src/components/combobox.tsx +269 -0
  171. package/src/components/command.tsx +181 -0
  172. package/src/components/compact-model-switcher.tsx +128 -0
  173. package/src/components/context-menu.tsx +244 -0
  174. package/src/components/dialog.tsx +138 -0
  175. package/src/components/direction.tsx +1 -0
  176. package/src/components/drawer.tsx +208 -0
  177. package/src/components/dropdown-menu.tsx +257 -0
  178. package/src/components/empty.tsx +93 -0
  179. package/src/components/field.tsx +232 -0
  180. package/src/components/hover-card.tsx +45 -0
  181. package/src/components/input-group.tsx +146 -0
  182. package/src/components/input-otp.tsx +83 -0
  183. package/src/components/input.tsx +19 -0
  184. package/src/components/item.tsx +188 -0
  185. package/src/components/kbd.tsx +26 -0
  186. package/src/components/label.tsx +17 -0
  187. package/src/components/markdown-content.tsx +113 -0
  188. package/src/components/marker.tsx +67 -0
  189. package/src/components/menubar.tsx +267 -0
  190. package/src/components/message-scroller.tsx +91 -0
  191. package/src/components/message.tsx +84 -0
  192. package/src/components/native-select.tsx +54 -0
  193. package/src/components/navigation-menu.tsx +160 -0
  194. package/src/components/pagination.tsx +118 -0
  195. package/src/components/popover.tsx +74 -0
  196. package/src/components/progress.tsx +82 -0
  197. package/src/components/questionnaire.tsx +150 -0
  198. package/src/components/radio-group.tsx +35 -0
  199. package/src/components/resizable.tsx +41 -0
  200. package/src/components/rich-text-editor.tsx +604 -0
  201. package/src/components/scroll-area.tsx +47 -0
  202. package/src/components/select.tsx +189 -0
  203. package/src/components/separator.tsx +18 -0
  204. package/src/components/sheet.tsx +125 -0
  205. package/src/components/sidebar.tsx +693 -0
  206. package/src/components/skeleton.tsx +13 -0
  207. package/src/components/slider.tsx +51 -0
  208. package/src/components/sonner.tsx +45 -0
  209. package/src/components/sparkline.tsx +231 -0
  210. package/src/components/spinner.tsx +20 -0
  211. package/src/components/status-badge.tsx +114 -0
  212. package/src/components/switch.tsx +31 -0
  213. package/src/components/table.tsx +91 -0
  214. package/src/components/tabs.tsx +72 -0
  215. package/src/components/textarea.tsx +17 -0
  216. package/src/components/toast.tsx +222 -0
  217. package/src/components/toggle-group.tsx +85 -0
  218. package/src/components/toggle.tsx +42 -0
  219. package/src/components/tooltip.tsx +53 -0
  220. package/src/design-system/README.md +55 -0
  221. package/src/design-system/action-intents.ts +21 -0
  222. package/src/design-system/agent-manifest.ts +83 -0
  223. package/src/design-system/block-registry.ts +272 -0
  224. package/src/design-system/component-registry.ts +142 -0
  225. package/src/design-system/contracts.ts +69 -0
  226. package/src/design-system/defaults.ts +3 -0
  227. package/src/design-system/index.ts +29 -0
  228. package/src/design-system/mcp-server.ts +156 -0
  229. package/src/design-system/page-registry.ts +74 -0
  230. package/src/design-system/selection.ts +67 -0
  231. package/src/design-system/template-registry.ts +33 -0
  232. package/src/features/interface-topology/index.ts +14 -0
  233. package/src/features/interface-topology/interface-topology.types.ts +40 -0
  234. package/src/features/interface-topology/topology-control.tsx +18 -0
  235. package/src/features/interface-topology/topology-inspector.tsx +228 -0
  236. package/src/features/interface-topology/topology-marker.tsx +47 -0
  237. package/src/features/interface-topology/topology-region.tsx +44 -0
  238. package/src/features/interface-topology/use-interface-topology.ts +178 -0
  239. package/src/hooks/use-mobile.ts +19 -0
  240. package/src/hooks/use-scroll-follow.ts +22 -0
  241. package/src/index.ts +11 -0
  242. package/src/layouts/README.md +65 -0
  243. package/src/layouts/agent-workspace/agent-activity-rail.tsx +101 -0
  244. package/src/layouts/agent-workspace/agent-workspace.tsx +27 -0
  245. package/src/layouts/agent-workspace/agent-workspace.types.ts +28 -0
  246. package/src/layouts/agent-workspace/index.ts +7 -0
  247. package/src/layouts/blog-header/blog-header-types.ts +25 -0
  248. package/src/layouts/blog-header/blog-header.tsx +172 -0
  249. package/src/layouts/blog-header/index.ts +2 -0
  250. package/src/layouts/documentation-workspace/README.md +8 -0
  251. package/src/layouts/documentation-workspace/documentation-workspace.tsx +34 -0
  252. package/src/layouts/documentation-workspace/index.ts +1 -0
  253. package/src/layouts/ecommerce-header/ecommerce-actions.tsx +89 -0
  254. package/src/layouts/ecommerce-header/ecommerce-announcement.tsx +43 -0
  255. package/src/layouts/ecommerce-header/ecommerce-drawer.tsx +97 -0
  256. package/src/layouts/ecommerce-header/ecommerce-header-types.ts +59 -0
  257. package/src/layouts/ecommerce-header/ecommerce-header.tsx +47 -0
  258. package/src/layouts/ecommerce-header/ecommerce-navigation.tsx +98 -0
  259. package/src/layouts/ecommerce-header/ecommerce-search.tsx +86 -0
  260. package/src/layouts/ecommerce-header/index.ts +2 -0
  261. package/src/layouts/main-workspace/index.ts +28 -0
  262. package/src/layouts/mdi/index.ts +1 -0
  263. package/src/layouts/mdi/mdi.tsx +20 -0
  264. package/src/layouts/mdi-main/index.ts +27 -0
  265. package/src/layouts/mdi-main/mdi-app-catalog.generated.ts +159 -0
  266. package/src/layouts/mdi-main/mdi-app-catalog.ts +76 -0
  267. package/src/layouts/mdi-main/mdi-app-switcher.tsx +107 -0
  268. package/src/layouts/mdi-main/mdi-feature-settings.tsx +135 -0
  269. package/src/layouts/mdi-main/mdi-global-search.tsx +192 -0
  270. package/src/layouts/mdi-main/mdi-main.tsx +241 -0
  271. package/src/layouts/mdi-main/mdi-notifications-menu.tsx +197 -0
  272. package/src/layouts/mdi-main/mdi-profile-menu.tsx +79 -0
  273. package/src/layouts/mdi-main/mdi-sidebar-navigation.tsx +226 -0
  274. package/src/layouts/mdi-main/mdi-sidebar.tsx +110 -0
  275. package/src/layouts/mdi-main/mdi-status-bar.tsx +26 -0
  276. package/src/layouts/mdi-main/mdi-top-menu-control.ts +4 -0
  277. package/src/layouts/mdi-main/mdi-top-menu.tsx +117 -0
  278. package/src/layouts/mdi-main/mdi-topology.tsx +297 -0
  279. package/src/layouts/mdi-main/mdi-tweak-panel.tsx +104 -0
  280. package/src/layouts/mdi-main/mdi-types.ts +131 -0
  281. package/src/layouts/mdi-main/use-mdi-features.ts +36 -0
  282. package/src/layouts/mdi-main/use-mdi-sidebar-state.ts +59 -0
  283. package/src/layouts/site-header/announcement-bar.tsx +55 -0
  284. package/src/layouts/site-header/category-strip.tsx +41 -0
  285. package/src/layouts/site-header/index.ts +16 -0
  286. package/src/layouts/site-header/main-navbar.tsx +179 -0
  287. package/src/layouts/site-header/mobile-drawer.tsx +140 -0
  288. package/src/layouts/site-header/site-header-types.ts +63 -0
  289. package/src/layouts/site-header/site-header.tsx +58 -0
  290. package/src/lib/random-id.ts +19 -0
  291. package/src/lib/utils.ts +6 -0
  292. package/src/providers/pdf-provider.ts +137 -0
  293. package/src/styles/globals.css +374 -0
  294. package/src/templates/README.md +12 -0
  295. package/src/templates/dashboard-01/components/app-sidebar.tsx +177 -0
  296. package/src/templates/dashboard-01/components/chart-area-interactive.tsx +276 -0
  297. package/src/templates/dashboard-01/components/data-table-cell-viewer.tsx +213 -0
  298. package/src/templates/dashboard-01/components/data-table.tsx +577 -0
  299. package/src/templates/dashboard-01/components/nav-documents.tsx +79 -0
  300. package/src/templates/dashboard-01/components/nav-main.tsx +55 -0
  301. package/src/templates/dashboard-01/components/nav-secondary.tsx +39 -0
  302. package/src/templates/dashboard-01/components/nav-user.tsx +97 -0
  303. package/src/templates/dashboard-01/components/section-cards.tsx +99 -0
  304. package/src/templates/dashboard-01/components/site-header.tsx +14 -0
  305. package/src/templates/dashboard-01/data.json +614 -0
  306. package/src/templates/dashboard-01/index.ts +7 -0
  307. package/src/templates/documentation-sidebar/app-sidebar.tsx +95 -0
  308. package/src/templates/documentation-sidebar/index.ts +2 -0
  309. package/src/templates/sidebar-07/components/app-sidebar.tsx +175 -0
  310. package/src/templates/sidebar-07/components/nav-main.tsx +64 -0
  311. package/src/templates/sidebar-07/components/nav-projects.tsx +79 -0
  312. package/src/templates/sidebar-07/components/nav-user.tsx +105 -0
  313. package/src/templates/sidebar-07/components/team-switcher.tsx +94 -0
  314. package/src/templates/sidebar-07/index.ts +1 -0
  315. package/src/templates/ui-page/index.ts +7 -0
  316. package/src/templates/ui-page/ui-browser-frame.tsx +27 -0
  317. package/src/templates/ui-page/ui-template-code.tsx +48 -0
  318. package/src/templates/ui-page/ui-template-header.tsx +87 -0
  319. package/src/templates/ui-page/ui-template-navigation.tsx +60 -0
  320. package/src/templates/ui-page/ui-template-page.tsx +97 -0
  321. package/src/theme/index.ts +12 -0
  322. package/src/theme/theme-config.ts +30 -0
  323. package/src/theme/theme-provider.tsx +142 -0
  324. package/src/theme/theme-selector.tsx +75 -0
  325. package/src/tokens/index.ts +15 -0
  326. package/src/vite-env.d.ts +3 -0
@@ -0,0 +1,124 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Columns3, Filter, Search } from 'lucide-react';
3
+ import { DropdownMenu, DropdownMenuCheckboxItem } from '@devxcrew/react-ui/components/dropdown-menu';
4
+ import { Input } from '@devxcrew/react-ui/components/input';
5
+ import { DataTableIconMenuTrigger } from './data-table-icon-menu-trigger';
6
+ import { DataTableMenuContent, dataTableMenuOptionClass } from './data-table-menu-content';
7
+ import { TopologyRegion, type InterfaceTopologyController } from '@devxcrew/react-ui/features/interface-topology';
8
+
9
+ export type DataTableColumnControl = {
10
+ id: string;
11
+ label: string;
12
+ onVisibleChange: (visible: boolean) => void;
13
+ visible: boolean;
14
+ };
15
+
16
+ export function DataTableToolbar({
17
+ columns,
18
+ filters,
19
+ onSearchChange,
20
+ search,
21
+ searchPlaceholder,
22
+ topology,
23
+ topologyIds,
24
+ filterOptions,
25
+ filterValue,
26
+ onFilterChange,
27
+ }: {
28
+ columns: DataTableColumnControl[];
29
+ filters?: ReactNode;
30
+ onSearchChange: (value: string) => void;
31
+ search: string;
32
+ searchPlaceholder: string;
33
+ topology?: InterfaceTopologyController;
34
+ topologyIds?: { searchBar?: string; columnFilter?: string };
35
+ filterOptions?: readonly { label: string; value: string }[];
36
+ filterValue?: string;
37
+ onFilterChange?: (value: string) => void;
38
+ }) {
39
+ const searchBar = (
40
+ <label className="relative w-full min-w-0 sm:max-w-3xl">
41
+ <Search className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
42
+ <span className="sr-only">{searchPlaceholder}</span>
43
+ <Input
44
+ className="h-8 pl-9"
45
+ onChange={(event) => onSearchChange(event.target.value)}
46
+ placeholder={searchPlaceholder}
47
+ value={search}
48
+ />
49
+ </label>
50
+ );
51
+ const filterMenu =
52
+ filterOptions?.length && onFilterChange ? (
53
+ <DropdownMenu>
54
+ <DataTableIconMenuTrigger active={Boolean(filterValue)} label="Filter">
55
+ <Filter />
56
+ </DataTableIconMenuTrigger>
57
+ <DataTableMenuContent actionLabel="Clear" onAction={() => onFilterChange('')} title="Filter options">
58
+ <DropdownMenuCheckboxItem
59
+ checked={!filterValue}
60
+ className={dataTableMenuOptionClass}
61
+ onCheckedChange={() => onFilterChange('')}
62
+ >
63
+ All records
64
+ </DropdownMenuCheckboxItem>
65
+ {filterOptions.map((option) => (
66
+ <DropdownMenuCheckboxItem
67
+ checked={filterValue === option.value}
68
+ className={dataTableMenuOptionClass}
69
+ key={option.value}
70
+ onCheckedChange={() => onFilterChange(option.value)}
71
+ >
72
+ {option.label}
73
+ </DropdownMenuCheckboxItem>
74
+ ))}
75
+ </DataTableMenuContent>
76
+ </DropdownMenu>
77
+ ) : null;
78
+ const columnFilter = (
79
+ <DropdownMenu>
80
+ <DataTableIconMenuTrigger label="Columns">
81
+ <Columns3 />
82
+ </DataTableIconMenuTrigger>
83
+ <DataTableMenuContent
84
+ actionLabel="Show all"
85
+ onAction={() => columns.forEach((column) => column.onVisibleChange(true))}
86
+ title="Visible columns"
87
+ >
88
+ {columns.map((column) => (
89
+ <DropdownMenuCheckboxItem
90
+ checked={column.visible}
91
+ className={dataTableMenuOptionClass}
92
+ key={column.id}
93
+ onCheckedChange={(visible) => column.onVisibleChange(Boolean(visible))}
94
+ >
95
+ {column.label}
96
+ </DropdownMenuCheckboxItem>
97
+ ))}
98
+ </DataTableMenuContent>
99
+ </DropdownMenu>
100
+ );
101
+
102
+ return (
103
+ <div className="flex flex-col gap-2 rounded-md border bg-card p-2 shadow-sm sm:flex-row sm:items-center">
104
+ {topology && topologyIds?.searchBar ? (
105
+ <TopologyRegion as="div" className="w-full min-w-0 sm:max-w-3xl" id={topologyIds.searchBar} topology={topology}>
106
+ {searchBar}
107
+ </TopologyRegion>
108
+ ) : (
109
+ searchBar
110
+ )}
111
+ <div className="flex flex-wrap items-center gap-2 sm:ml-auto">
112
+ {filters}
113
+ {filterMenu}
114
+ {topology && topologyIds?.columnFilter ? (
115
+ <TopologyRegion as="div" id={topologyIds.columnFilter} topology={topology}>
116
+ {columnFilter}
117
+ </TopologyRegion>
118
+ ) : (
119
+ columnFilter
120
+ )}
121
+ </div>
122
+ </div>
123
+ );
124
+ }
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react'
2
+ import { cn } from '@devxcrew/react-ui/lib/utils'
3
+
4
+ export type DataTableTotal = {
5
+ label: string
6
+ value: ReactNode
7
+ }
8
+
9
+ export function DataTableTotals({
10
+ className,
11
+ items,
12
+ }: {
13
+ className?: string
14
+ items: DataTableTotal[]
15
+ }) {
16
+ return (
17
+ <div
18
+ className={cn(
19
+ 'grid gap-x-8 gap-y-2 rounded-md border bg-card px-4 py-3 text-sm shadow-sm sm:grid-cols-2 xl:grid-cols-4',
20
+ className,
21
+ )}
22
+ >
23
+ {items.map((item) => (
24
+ <div className="flex items-center justify-between gap-4" key={item.label}>
25
+ <span className="text-muted-foreground">{item.label}</span>
26
+ <strong className="font-semibold tabular-nums text-foreground">{item.value}</strong>
27
+ </div>
28
+ ))}
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ createDataTableColumnHelper,
3
+ dataTableFeatures,
4
+ DataTableBlock,
5
+ type DataTableBlockProps,
6
+ type DataTableColumn,
7
+ } from './data-table-block'
8
+ export { DataTableFilterMenu, type DataTableFilterOption } from './data-table-filter-menu'
9
+ export { DataTablePagination } from './data-table-pagination'
10
+ export { DataTableRowActions, type DataTableRowAction } from './data-table-row-actions'
11
+ export { DataTableStatus, type DataTableStatusTone } from './data-table-status'
12
+ export { DataTableToolbar, type DataTableColumnControl } from './data-table-toolbar'
13
+ export { DataTableTotals, type DataTableTotal } from './data-table-totals'
@@ -0,0 +1,19 @@
1
+ export { WorkspaceActionCard } from './workspace-action-card'
2
+ export { WorkspaceMetricCard, WorkspaceMetricGrid } from './workspace-metric-card'
3
+ export type { WorkspaceMetricTone, WorkspaceMetricTrend } from './workspace-metric-card'
4
+ export { WorkspacePageHeader } from './workspace-page-header'
5
+ export { WorkspaceSectionCard } from './workspace-section-card'
6
+ export { WorkspaceEntityCard } from './workspace-entity-card'
7
+ export type { WorkspaceEntityCardProps } from './workspace-entity-card'
8
+ export { WorkspaceHealthSummary } from './workspace-health-summary'
9
+ export type { WorkspaceHealthSummaryProps } from './workspace-health-summary'
10
+ export { WorkspacePublishStatus } from './workspace-publish-status'
11
+ export type { WorkspacePublishState } from './workspace-publish-status'
12
+ export { WorkspaceRouteChecklist } from './workspace-route-checklist'
13
+ export type { WorkspaceRouteCheck } from './workspace-route-checklist'
14
+ export { WorkspaceRuntimeStatus } from './workspace-runtime-status'
15
+ export type { WorkspaceRuntimeState, WorkspaceRuntimeStatusProps } from './workspace-runtime-status'
16
+ export {
17
+ WorkspaceApplicationHeader,
18
+ type WorkspaceApplicationHeaderProps,
19
+ } from './workspace-application-header'
@@ -0,0 +1,73 @@
1
+ import type { LucideIcon } from 'lucide-react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ import { Card, CardAction, CardDescription, CardHeader, CardTitle } from '../../components/card'
5
+ import { cn } from '../../lib/utils'
6
+
7
+ type WorkspaceActionCardProps = {
8
+ action?: ReactNode
9
+ className?: string
10
+ description?: ReactNode
11
+ icon: LucideIcon
12
+ iconClassName?: string
13
+ layout?: 'inline' | 'stacked'
14
+ title: ReactNode
15
+ }
16
+
17
+ export function WorkspaceActionCard({
18
+ action,
19
+ className,
20
+ description,
21
+ icon: Icon,
22
+ iconClassName,
23
+ layout = 'inline',
24
+ title,
25
+ }: WorkspaceActionCardProps) {
26
+ if (layout === 'stacked') {
27
+ return (
28
+ <Card
29
+ className={cn('h-full shadow-xs transition-colors hover:bg-muted/25', className)}
30
+ size="sm"
31
+ >
32
+ <CardHeader className="place-items-center text-center">
33
+ <span
34
+ className={cn(
35
+ 'grid size-9 place-items-center rounded-lg bg-primary/10 text-primary',
36
+ iconClassName,
37
+ )}
38
+ >
39
+ <Icon className="size-4" />
40
+ </span>
41
+ <CardTitle>{title}</CardTitle>
42
+ {description ? (
43
+ <CardDescription className="leading-5">{description}</CardDescription>
44
+ ) : null}
45
+ {action ? <CardAction>{action}</CardAction> : null}
46
+ </CardHeader>
47
+ </Card>
48
+ )
49
+ }
50
+
51
+ return (
52
+ <Card
53
+ className={cn('h-full shadow-xs transition-colors hover:bg-muted/25', className)}
54
+ size="sm"
55
+ >
56
+ <CardHeader className="grid grid-cols-[auto_1fr_auto] items-start gap-x-3">
57
+ <span
58
+ className={cn(
59
+ 'row-span-2 grid size-9 place-items-center rounded-lg bg-primary/10 text-primary',
60
+ iconClassName,
61
+ )}
62
+ >
63
+ <Icon className="size-4" />
64
+ </span>
65
+ <CardTitle className="col-start-2">{title}</CardTitle>
66
+ {description ? (
67
+ <CardDescription className="col-start-2 leading-5">{description}</CardDescription>
68
+ ) : null}
69
+ {action ? <CardAction className="col-start-3">{action}</CardAction> : null}
70
+ </CardHeader>
71
+ </Card>
72
+ )
73
+ }
@@ -0,0 +1,67 @@
1
+ import type { LucideIcon } from 'lucide-react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ import { cn } from '../../lib/utils'
5
+
6
+ export type WorkspaceApplicationHeaderProps = {
7
+ applicationIcon?: LucideIcon
8
+ applicationLogoUrl?: string
9
+ applicationName: ReactNode
10
+ className?: string
11
+ end?: ReactNode
12
+ meta?: ReactNode
13
+ status?: ReactNode
14
+ showIdentity?: boolean
15
+ showWorkspaceTitle?: boolean
16
+ start?: ReactNode
17
+ title?: ReactNode
18
+ workspaceTitle?: ReactNode
19
+ }
20
+
21
+ /** Title bar for the canvas below the global workspace menu. */
22
+ export function WorkspaceApplicationHeader({
23
+ applicationIcon: ApplicationIcon,
24
+ applicationLogoUrl,
25
+ applicationName,
26
+ className,
27
+ end,
28
+ meta,
29
+ status,
30
+ showIdentity = true,
31
+ showWorkspaceTitle = true,
32
+ start,
33
+ title,
34
+ workspaceTitle,
35
+ }: WorkspaceApplicationHeaderProps) {
36
+ return (
37
+ <header
38
+ aria-label="Workspace application header"
39
+ data-ui-component="WorkspaceApplicationHeader"
40
+ className={cn(
41
+ 'sticky top-0 z-20 flex h-10 shrink-0 items-center justify-between gap-3 border-b border-border bg-background px-3 text-[11px] leading-none text-foreground',
42
+ className,
43
+ )}
44
+ >
45
+ <div className="flex min-w-0 items-center gap-2">
46
+ {start}
47
+ {status}
48
+ {title ? <span className="truncate text-sm font-semibold">{title}</span> : null}
49
+ {showIdentity ? (
50
+ <>
51
+ {applicationLogoUrl ? (
52
+ <img alt="" className="size-3 shrink-0 rounded-sm object-contain" src={applicationLogoUrl} />
53
+ ) : ApplicationIcon ? (
54
+ <ApplicationIcon aria-hidden="true" className="size-3 shrink-0" />
55
+ ) : null}
56
+ <span className="truncate font-medium">{applicationName}</span>
57
+ </>
58
+ ) : null}
59
+ </div>
60
+ <div className="flex min-w-0 shrink-0 items-center gap-3 text-xs text-muted-foreground">
61
+ {meta}
62
+ {showWorkspaceTitle ? <span className="max-w-[45vw] truncate tracking-[0.12em] uppercase">{workspaceTitle}</span> : null}
63
+ {end}
64
+ </div>
65
+ </header>
66
+ )
67
+ }
@@ -0,0 +1,48 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '../../components/card'
4
+ import { cn } from '../../lib/utils'
5
+
6
+ export type WorkspaceEntityCardProps = {
7
+ action?: ReactNode
8
+ children?: ReactNode
9
+ className?: string
10
+ description?: ReactNode
11
+ eyebrow?: ReactNode
12
+ footer?: ReactNode
13
+ title: ReactNode
14
+ variant?: 'default' | 'interactive'
15
+ }
16
+
17
+ export function WorkspaceEntityCard({
18
+ action,
19
+ children,
20
+ className,
21
+ description,
22
+ eyebrow,
23
+ footer,
24
+ title,
25
+ variant = 'default',
26
+ }: WorkspaceEntityCardProps) {
27
+ return (
28
+ <Card
29
+ className={cn(
30
+ 'h-full min-w-0 shadow-xs',
31
+ variant === 'interactive' ? 'transition-shadow hover:shadow-md' : null,
32
+ className,
33
+ )}
34
+ size="sm"
35
+ >
36
+ <CardHeader className="grid grid-cols-[1fr_auto] items-start gap-3">
37
+ <div className="min-w-0">
38
+ {eyebrow ? <div className="mb-2 text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase">{eyebrow}</div> : null}
39
+ <CardTitle>{title}</CardTitle>
40
+ {description ? <p className="mt-2 text-sm leading-6 text-muted-foreground">{description}</p> : null}
41
+ </div>
42
+ {action ? <div className="justify-self-end">{action}</div> : null}
43
+ </CardHeader>
44
+ {children ? <CardContent>{children}</CardContent> : null}
45
+ {footer ? <CardFooter className="mt-auto justify-between gap-3">{footer}</CardFooter> : null}
46
+ </Card>
47
+ )
48
+ }
@@ -0,0 +1,15 @@
1
+ import type { LucideIcon } from 'lucide-react'
2
+
3
+ import { WorkspaceMetricCard, type WorkspaceMetricTone } from './workspace-metric-card'
4
+
5
+ export type WorkspaceHealthSummaryProps = {
6
+ description?: string
7
+ icon?: LucideIcon
8
+ label: string
9
+ tone?: WorkspaceMetricTone
10
+ value: string
11
+ }
12
+
13
+ export function WorkspaceHealthSummary({ description, icon, label, tone = 'neutral', value }: WorkspaceHealthSummaryProps) {
14
+ return <WorkspaceMetricCard description={description} icon={icon} label={label} size="compact" tone={tone} value={value} />
15
+ }
@@ -0,0 +1,97 @@
1
+ import { ArrowDownRightIcon, ArrowRightIcon, ArrowUpRightIcon, type LucideIcon } from 'lucide-react'
2
+ import type { HTMLAttributes, ReactNode } from 'react'
3
+
4
+ import { Card, CardContent, CardHeader } from '../../components/card'
5
+ import { cn } from '../../lib/utils'
6
+
7
+ export type WorkspaceMetricTone = 'accent' | 'danger' | 'neutral' | 'success' | 'warning'
8
+ export type WorkspaceMetricTrend = {
9
+ direction: 'down' | 'flat' | 'up'
10
+ label: string
11
+ }
12
+
13
+ type WorkspaceMetricCardProps = {
14
+ className?: string
15
+ description?: ReactNode
16
+ icon?: LucideIcon
17
+ label: ReactNode
18
+ size?: 'compact' | 'default'
19
+ tone?: WorkspaceMetricTone
20
+ trend?: WorkspaceMetricTrend
21
+ value: ReactNode
22
+ }
23
+
24
+ const iconToneClasses: Record<WorkspaceMetricTone, string> = {
25
+ accent: 'bg-primary/10 text-primary',
26
+ danger: 'bg-destructive/10 text-destructive',
27
+ neutral: 'bg-muted text-muted-foreground',
28
+ success: 'bg-success/10 text-success',
29
+ warning: 'bg-warning/15 text-warning-foreground',
30
+ }
31
+
32
+ const trendIcons: Record<WorkspaceMetricTrend['direction'], LucideIcon> = {
33
+ down: ArrowDownRightIcon,
34
+ flat: ArrowRightIcon,
35
+ up: ArrowUpRightIcon,
36
+ }
37
+
38
+ const trendClasses: Record<WorkspaceMetricTrend['direction'], string> = {
39
+ down: 'text-destructive',
40
+ flat: 'text-muted-foreground',
41
+ up: 'text-success',
42
+ }
43
+
44
+ export function WorkspaceMetricCard({
45
+ className,
46
+ description,
47
+ icon: Icon,
48
+ label,
49
+ size = 'default',
50
+ tone = 'neutral',
51
+ trend,
52
+ value,
53
+ }: WorkspaceMetricCardProps) {
54
+ const TrendIcon = trend ? trendIcons[trend.direction] : null
55
+
56
+ return (
57
+ <Card className={cn('min-w-0 shadow-xs', size === 'compact' ? 'py-2' : null, className)} size="sm">
58
+ <CardHeader className={cn('flex-row items-start justify-between gap-3', size === 'compact' ? 'px-3' : null)}>
59
+ <div className={cn('min-w-0 font-medium text-muted-foreground', size === 'compact' ? 'text-xs' : 'text-sm')}>{label}</div>
60
+ {Icon ? (
61
+ <span
62
+ className={cn(
63
+ 'grid shrink-0 place-items-center rounded-lg',
64
+ size === 'compact' ? 'size-7' : 'size-8',
65
+ iconToneClasses[tone],
66
+ )}
67
+ >
68
+ <Icon className="size-4" />
69
+ </span>
70
+ ) : null}
71
+ </CardHeader>
72
+ <CardContent className={cn('grid gap-2', size === 'compact' ? 'px-3 gap-1' : null)}>
73
+ <strong className={cn('font-semibold tracking-tight tabular-nums', size === 'compact' ? 'text-xl' : 'text-2xl')}>{value}</strong>
74
+ <div className="flex min-h-5 items-center gap-2 text-xs">
75
+ {trend && TrendIcon ? (
76
+ <span
77
+ className={cn(
78
+ 'inline-flex items-center gap-1 font-medium',
79
+ trendClasses[trend.direction],
80
+ )}
81
+ >
82
+ <TrendIcon className="size-3.5" />
83
+ {trend.label}
84
+ </span>
85
+ ) : null}
86
+ {description ? (
87
+ <span className="truncate text-muted-foreground">{description}</span>
88
+ ) : null}
89
+ </div>
90
+ </CardContent>
91
+ </Card>
92
+ )
93
+ }
94
+
95
+ export function WorkspaceMetricGrid({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
96
+ return <div className={cn('grid gap-4 sm:grid-cols-2 xl:grid-cols-4', className)} {...props} />
97
+ }
@@ -0,0 +1,43 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { cn } from '../../lib/utils'
4
+
5
+ type WorkspacePageHeaderProps = {
6
+ actions?: ReactNode
7
+ badge?: ReactNode
8
+ className?: string
9
+ description?: ReactNode
10
+ eyebrow?: ReactNode
11
+ title: ReactNode
12
+ }
13
+
14
+ export function WorkspacePageHeader({
15
+ actions,
16
+ badge,
17
+ className,
18
+ description,
19
+ eyebrow,
20
+ title,
21
+ }: WorkspacePageHeaderProps) {
22
+ return (
23
+ <header
24
+ className={cn('flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between', className)}
25
+ >
26
+ <div className="min-w-0 max-w-3xl">
27
+ {eyebrow ? (
28
+ <div className="pb-2 text-xs font-semibold tracking-widest text-primary uppercase">
29
+ {eyebrow}
30
+ </div>
31
+ ) : null}
32
+ <div className="flex flex-wrap items-center gap-2">
33
+ <h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">{title}</h1>
34
+ {badge}
35
+ </div>
36
+ {description ? (
37
+ <p className="pt-2 text-sm leading-6 text-muted-foreground">{description}</p>
38
+ ) : null}
39
+ </div>
40
+ {actions ? <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div> : null}
41
+ </header>
42
+ )
43
+ }
@@ -0,0 +1,15 @@
1
+ import { CheckCircle2Icon, CircleAlertIcon, CircleDashedIcon } from 'lucide-react'
2
+
3
+ import { Badge } from '../../components/badge'
4
+
5
+ export type WorkspacePublishState = 'draft' | 'published' | 'review'
6
+
7
+ export function WorkspacePublishStatus({ state }: { state: WorkspacePublishState }) {
8
+ const config = {
9
+ draft: { icon: CircleDashedIcon, label: 'Draft', variant: 'secondary' as const },
10
+ published: { icon: CheckCircle2Icon, label: 'Published', variant: 'studio-success' as const },
11
+ review: { icon: CircleAlertIcon, label: 'Needs review', variant: 'studio-warning' as const },
12
+ }[state]
13
+ const Icon = config.icon
14
+ return <Badge aria-label={`Publish status: ${config.label}`} variant={config.variant}><Icon aria-hidden="true" />{config.label}</Badge>
15
+ }
@@ -0,0 +1,28 @@
1
+ import { CheckCircle2Icon, CircleAlertIcon } from 'lucide-react'
2
+
3
+ import { Badge } from '../../components/badge'
4
+ import { Card, CardContent } from '../../components/card'
5
+
6
+ export type WorkspaceRouteCheck = { label: string; status: 'blocked' | 'ready' | 'pending' }
7
+
8
+ export function WorkspaceRouteChecklist({ items }: { items: WorkspaceRouteCheck[] }) {
9
+ return (
10
+ <div className="grid gap-3 sm:grid-cols-2">
11
+ {items.map((item) => {
12
+ const ready = item.status === 'ready'
13
+ const Icon = ready ? CheckCircle2Icon : CircleAlertIcon
14
+ return (
15
+ <Card key={item.label} size="sm" className="shadow-none">
16
+ <CardContent className="flex items-center gap-3 p-3 text-sm">
17
+ <Icon aria-hidden="true" className={`size-4 ${ready ? 'text-emerald-500' : 'text-amber-500'}`} />
18
+ <span className="flex-1">{item.label}</span>
19
+ <Badge variant={ready ? 'studio-success' : item.status === 'pending' ? 'studio-warning' : 'secondary'}>
20
+ {ready ? 'Ready' : item.status === 'pending' ? 'Pending' : 'Blocked'}
21
+ </Badge>
22
+ </CardContent>
23
+ </Card>
24
+ )
25
+ })}
26
+ </div>
27
+ )
28
+ }
@@ -0,0 +1,48 @@
1
+ import { CheckCircle2Icon, CircleAlertIcon, CircleOffIcon, type LucideIcon } from 'lucide-react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ import { Badge } from '../../components/badge'
5
+ import { cn } from '../../lib/utils'
6
+
7
+ export type WorkspaceRuntimeState = 'degraded' | 'live' | 'starting' | 'stopped'
8
+
9
+ export type WorkspaceRuntimeStatusProps = {
10
+ action?: ReactNode
11
+ checkedAt?: string
12
+ className?: string
13
+ httpStatus?: number | null
14
+ port?: number
15
+ responseTimeMs?: number | null
16
+ state: WorkspaceRuntimeState
17
+ }
18
+
19
+ const stateConfig: Record<WorkspaceRuntimeState, { icon: LucideIcon; label: string; variant: 'secondary' | 'studio-success' | 'studio-warning' }> = {
20
+ degraded: { icon: CircleAlertIcon, label: 'Degraded', variant: 'studio-warning' },
21
+ live: { icon: CheckCircle2Icon, label: 'Live', variant: 'studio-success' },
22
+ starting: { icon: CircleAlertIcon, label: 'Starting', variant: 'studio-warning' },
23
+ stopped: { icon: CircleOffIcon, label: 'Not running', variant: 'secondary' },
24
+ }
25
+
26
+ export function WorkspaceRuntimeStatus({ action, checkedAt, className, httpStatus, port, responseTimeMs, state }: WorkspaceRuntimeStatusProps) {
27
+ const config = stateConfig[state]
28
+ const Icon = config.icon
29
+ const detail = state === 'live'
30
+ ? [port ? `:${port}` : null, responseTimeMs == null ? null : `${responseTimeMs}ms`].filter(Boolean).join(' · ')
31
+ : state === 'degraded' && httpStatus
32
+ ? `HTTP ${httpStatus}`
33
+ : null
34
+
35
+ return (
36
+ <div aria-live="polite" className={cn('flex min-w-0 items-center justify-between gap-3', className)}>
37
+ <div className="flex min-w-0 items-center gap-2">
38
+ <Badge aria-label={`Runtime status: ${config.label}`} variant={config.variant}>
39
+ <Icon aria-hidden="true" />
40
+ {config.label}
41
+ </Badge>
42
+ {detail ? <span className="truncate text-xs text-muted-foreground">{detail}</span> : null}
43
+ {checkedAt ? <span className="sr-only">Last checked {checkedAt}</span> : null}
44
+ </div>
45
+ {action}
46
+ </div>
47
+ )
48
+ }
@@ -0,0 +1,42 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import {
4
+ Card,
5
+ CardAction,
6
+ CardContent,
7
+ CardDescription,
8
+ CardFooter,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '../../components/card'
12
+ import { cn } from '../../lib/utils'
13
+
14
+ type WorkspaceSectionCardProps = {
15
+ action?: ReactNode
16
+ children: ReactNode
17
+ className?: string
18
+ description?: ReactNode
19
+ footer?: ReactNode
20
+ title: ReactNode
21
+ }
22
+
23
+ export function WorkspaceSectionCard({
24
+ action,
25
+ children,
26
+ className,
27
+ description,
28
+ footer,
29
+ title,
30
+ }: WorkspaceSectionCardProps) {
31
+ return (
32
+ <Card className={cn('min-w-0 shadow-xs', className)}>
33
+ <CardHeader className="border-b">
34
+ <CardTitle>{title}</CardTitle>
35
+ {description ? <CardDescription>{description}</CardDescription> : null}
36
+ {action ? <CardAction>{action}</CardAction> : null}
37
+ </CardHeader>
38
+ <CardContent>{children}</CardContent>
39
+ {footer ? <CardFooter>{footer}</CardFooter> : null}
40
+ </Card>
41
+ )
42
+ }