@tetrascience-npm/tetrascience-react-ui 0.5.0 → 0.6.0-beta.78.1

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 (192) hide show
  1. package/README.md +84 -37
  2. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +2 -0
  3. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -0
  4. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +140 -0
  5. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -0
  6. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.cjs +2 -0
  7. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.cjs.map +1 -0
  8. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.js +126 -0
  9. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.js.map +1 -0
  10. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +2 -0
  11. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -0
  12. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +341 -0
  13. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -0
  14. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs +2 -0
  15. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs.map +1 -0
  16. package/dist/components/composed/PlateMapEditor/PlateMapForm.js +43 -0
  17. package/dist/components/composed/PlateMapEditor/PlateMapForm.js.map +1 -0
  18. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs +2 -0
  19. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs.map +1 -0
  20. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js +154 -0
  21. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js.map +1 -0
  22. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs +2 -0
  23. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs.map +1 -0
  24. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js +44 -0
  25. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js.map +1 -0
  26. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.cjs +2 -0
  27. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.cjs.map +1 -0
  28. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.js +136 -0
  29. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.js.map +1 -0
  30. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +2 -0
  31. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -0
  32. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +389 -0
  33. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -0
  34. package/dist/components/composed/PlateMapEditor/PlateZoomControl.cjs +2 -0
  35. package/dist/components/composed/PlateMapEditor/PlateZoomControl.cjs.map +1 -0
  36. package/dist/components/composed/PlateMapEditor/PlateZoomControl.js +54 -0
  37. package/dist/components/composed/PlateMapEditor/PlateZoomControl.js.map +1 -0
  38. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.cjs +2 -0
  39. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.cjs.map +1 -0
  40. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.js +96 -0
  41. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.js.map +1 -0
  42. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +2 -0
  43. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -0
  44. package/dist/components/composed/PlateMapEditor/WellLegend.js +58 -0
  45. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -0
  46. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +2 -0
  47. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -0
  48. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +421 -0
  49. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -0
  50. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +2 -0
  51. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -0
  52. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +177 -0
  53. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -0
  54. package/dist/components/composed/PlateMapEditor/autoFill.cjs +2 -0
  55. package/dist/components/composed/PlateMapEditor/autoFill.cjs.map +1 -0
  56. package/dist/components/composed/PlateMapEditor/autoFill.js +41 -0
  57. package/dist/components/composed/PlateMapEditor/autoFill.js.map +1 -0
  58. package/dist/components/composed/PlateMapEditor/csvPlateTriage.cjs +4 -0
  59. package/dist/components/composed/PlateMapEditor/csvPlateTriage.cjs.map +1 -0
  60. package/dist/components/composed/PlateMapEditor/csvPlateTriage.js +103 -0
  61. package/dist/components/composed/PlateMapEditor/csvPlateTriage.js.map +1 -0
  62. package/dist/components/composed/PlateMapEditor/helpers.cjs +2 -0
  63. package/dist/components/composed/PlateMapEditor/helpers.cjs.map +1 -0
  64. package/dist/components/composed/PlateMapEditor/helpers.js +11 -0
  65. package/dist/components/composed/PlateMapEditor/helpers.js.map +1 -0
  66. package/dist/components/composed/PlateMapEditor/wellGrid.cjs +2 -0
  67. package/dist/components/composed/PlateMapEditor/wellGrid.cjs.map +1 -0
  68. package/dist/components/composed/PlateMapEditor/wellGrid.js +56 -0
  69. package/dist/components/composed/PlateMapEditor/wellGrid.js.map +1 -0
  70. package/dist/components/composed/ProcessFlow/ProcessFlow.cjs +2 -0
  71. package/dist/components/composed/ProcessFlow/ProcessFlow.cjs.map +1 -0
  72. package/dist/components/composed/ProcessFlow/ProcessFlow.js +543 -0
  73. package/dist/components/composed/ProcessFlow/ProcessFlow.js.map +1 -0
  74. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.cjs +2 -0
  75. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.cjs.map +1 -0
  76. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.js +84 -0
  77. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.js.map +1 -0
  78. package/dist/components/ui/accordion.cjs +1 -1
  79. package/dist/components/ui/accordion.cjs.map +1 -1
  80. package/dist/components/ui/accordion.js +1 -1
  81. package/dist/components/ui/accordion.js.map +1 -1
  82. package/dist/components/ui/badge.cjs +1 -1
  83. package/dist/components/ui/badge.cjs.map +1 -1
  84. package/dist/components/ui/badge.js +18 -18
  85. package/dist/components/ui/badge.js.map +1 -1
  86. package/dist/components/ui/button.cjs +1 -1
  87. package/dist/components/ui/button.cjs.map +1 -1
  88. package/dist/components/ui/button.js +16 -16
  89. package/dist/components/ui/button.js.map +1 -1
  90. package/dist/components/ui/calendar.cjs +1 -1
  91. package/dist/components/ui/calendar.cjs.map +1 -1
  92. package/dist/components/ui/calendar.js +5 -5
  93. package/dist/components/ui/calendar.js.map +1 -1
  94. package/dist/components/ui/card.cjs +1 -1
  95. package/dist/components/ui/card.cjs.map +1 -1
  96. package/dist/components/ui/card.js +1 -1
  97. package/dist/components/ui/card.js.map +1 -1
  98. package/dist/components/ui/checkbox.cjs +1 -1
  99. package/dist/components/ui/checkbox.cjs.map +1 -1
  100. package/dist/components/ui/checkbox.js +9 -9
  101. package/dist/components/ui/checkbox.js.map +1 -1
  102. package/dist/components/ui/combobox.cjs +1 -1
  103. package/dist/components/ui/combobox.cjs.map +1 -1
  104. package/dist/components/ui/combobox.js +5 -5
  105. package/dist/components/ui/combobox.js.map +1 -1
  106. package/dist/components/ui/data-table/data-table-group.cjs +2 -0
  107. package/dist/components/ui/data-table/data-table-group.cjs.map +1 -0
  108. package/dist/components/ui/data-table/data-table-group.js +118 -0
  109. package/dist/components/ui/data-table/data-table-group.js.map +1 -0
  110. package/dist/components/ui/data-table/data-table-pagination.cjs +1 -1
  111. package/dist/components/ui/data-table/data-table-pagination.cjs.map +1 -1
  112. package/dist/components/ui/data-table/data-table-pagination.js +22 -22
  113. package/dist/components/ui/data-table/data-table-pagination.js.map +1 -1
  114. package/dist/components/ui/data-table/data-table.cjs +1 -1
  115. package/dist/components/ui/data-table/data-table.cjs.map +1 -1
  116. package/dist/components/ui/data-table/data-table.js +567 -316
  117. package/dist/components/ui/data-table/data-table.js.map +1 -1
  118. package/dist/components/ui/dialog.cjs +1 -1
  119. package/dist/components/ui/dialog.cjs.map +1 -1
  120. package/dist/components/ui/dialog.js +13 -13
  121. package/dist/components/ui/dialog.js.map +1 -1
  122. package/dist/components/ui/input-group.cjs +1 -1
  123. package/dist/components/ui/input-group.cjs.map +1 -1
  124. package/dist/components/ui/input-group.js +29 -29
  125. package/dist/components/ui/input-group.js.map +1 -1
  126. package/dist/components/ui/input-otp.cjs +1 -1
  127. package/dist/components/ui/input-otp.cjs.map +1 -1
  128. package/dist/components/ui/input-otp.js +10 -10
  129. package/dist/components/ui/input-otp.js.map +1 -1
  130. package/dist/components/ui/input.cjs +1 -1
  131. package/dist/components/ui/input.cjs.map +1 -1
  132. package/dist/components/ui/input.js +7 -7
  133. package/dist/components/ui/input.js.map +1 -1
  134. package/dist/components/ui/item.cjs +1 -1
  135. package/dist/components/ui/item.cjs.map +1 -1
  136. package/dist/components/ui/item.js +17 -17
  137. package/dist/components/ui/item.js.map +1 -1
  138. package/dist/components/ui/navigation-menu.cjs +1 -1
  139. package/dist/components/ui/navigation-menu.cjs.map +1 -1
  140. package/dist/components/ui/navigation-menu.js +24 -24
  141. package/dist/components/ui/navigation-menu.js.map +1 -1
  142. package/dist/components/ui/popover.cjs +2 -0
  143. package/dist/components/ui/popover.cjs.map +1 -0
  144. package/dist/components/ui/popover.js +45 -0
  145. package/dist/components/ui/popover.js.map +1 -0
  146. package/dist/components/ui/radio-group.cjs +1 -1
  147. package/dist/components/ui/radio-group.cjs.map +1 -1
  148. package/dist/components/ui/radio-group.js +16 -16
  149. package/dist/components/ui/radio-group.js.map +1 -1
  150. package/dist/components/ui/scroll-area.cjs +1 -1
  151. package/dist/components/ui/scroll-area.cjs.map +1 -1
  152. package/dist/components/ui/scroll-area.js +6 -6
  153. package/dist/components/ui/scroll-area.js.map +1 -1
  154. package/dist/components/ui/select.cjs +1 -1
  155. package/dist/components/ui/select.cjs.map +1 -1
  156. package/dist/components/ui/select.js +48 -48
  157. package/dist/components/ui/select.js.map +1 -1
  158. package/dist/components/ui/slider.cjs +1 -1
  159. package/dist/components/ui/slider.cjs.map +1 -1
  160. package/dist/components/ui/slider.js +22 -22
  161. package/dist/components/ui/slider.js.map +1 -1
  162. package/dist/components/ui/switch.cjs +1 -1
  163. package/dist/components/ui/switch.cjs.map +1 -1
  164. package/dist/components/ui/switch.js +14 -14
  165. package/dist/components/ui/switch.js.map +1 -1
  166. package/dist/components/ui/table.cjs +1 -1
  167. package/dist/components/ui/table.cjs.map +1 -1
  168. package/dist/components/ui/table.js +2 -2
  169. package/dist/components/ui/table.js.map +1 -1
  170. package/dist/components/ui/tabs.cjs +1 -1
  171. package/dist/components/ui/tabs.cjs.map +1 -1
  172. package/dist/components/ui/tabs.js +9 -9
  173. package/dist/components/ui/tabs.js.map +1 -1
  174. package/dist/components/ui/textarea.cjs +1 -1
  175. package/dist/components/ui/textarea.cjs.map +1 -1
  176. package/dist/components/ui/textarea.js +6 -6
  177. package/dist/components/ui/textarea.js.map +1 -1
  178. package/dist/components/ui/toggle.cjs +1 -1
  179. package/dist/components/ui/toggle.cjs.map +1 -1
  180. package/dist/components/ui/toggle.js +13 -13
  181. package/dist/components/ui/toggle.js.map +1 -1
  182. package/dist/index.cjs +1 -1
  183. package/dist/index.css +1 -1
  184. package/dist/index.d.ts +830 -3
  185. package/dist/index.js +649 -593
  186. package/dist/index.js.map +1 -1
  187. package/dist/index.tailwind.css +1 -1
  188. package/dist/utils/colors.cjs +1 -1
  189. package/dist/utils/colors.cjs.map +1 -1
  190. package/dist/utils/colors.js +43 -21
  191. package/dist/utils/colors.js.map +1 -1
  192. package/package.json +1 -1
package/README.md CHANGED
@@ -25,9 +25,9 @@ This library provides:
25
25
  ## Compatibility
26
26
 
27
27
  | Library version | React | Node.js | TDP (server utilities) |
28
- | --- | --- | --- | --- |
29
- | v0.5.x | 19+ | 18+ | v4.x+ |
30
- | v0.4.x | 19+ | 18+ | v4.x+ |
28
+ | --------------- | ----- | ------- | ---------------------- |
29
+ | v0.5.x | 19+ | 18+ | v4.x+ |
30
+ | v0.4.x | 19+ | 18+ | v4.x+ |
31
31
 
32
32
  > **Note:** The client-side components have no TDP version dependency.
33
33
  > The `/server` utilities (JWT auth, provider helpers) require a running TDP instance of v4.x or later.
@@ -43,10 +43,10 @@ yarn add @tetrascience-npm/tetrascience-react-ui
43
43
 
44
44
  ```tsx
45
45
  // 1. Import the CSS once at your app root (required)
46
- import '@tetrascience-npm/tetrascience-react-ui/index.css';
46
+ import "@tetrascience-npm/tetrascience-react-ui/index.css";
47
47
 
48
48
  // 2. Import components
49
- import { Button, Card, CardHeader, CardContent } from '@tetrascience-npm/tetrascience-react-ui';
49
+ import { Button, Card, CardHeader, CardContent } from "@tetrascience-npm/tetrascience-react-ui";
50
50
 
51
51
  function App() {
52
52
  return (
@@ -67,9 +67,9 @@ This library uses **Tailwind CSS 4** with design tokens defined as CSS custom pr
67
67
 
68
68
  ### CSS Import Options
69
69
 
70
- | Import path | Use case |
71
- | --- | --- |
72
- | `@tetrascience-npm/tetrascience-react-ui/index.css` | **Pre-built CSS** — use this for most apps. Import once at your app root. |
70
+ | Import path | Use case |
71
+ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
72
+ | `@tetrascience-npm/tetrascience-react-ui/index.css` | **Pre-built CSS** — use this for most apps. Import once at your app root. |
73
73
  | `@tetrascience-npm/tetrascience-react-ui/index.tailwind.css` | **Tailwind source** — for apps that run their own Tailwind build and want to extend/override tokens. |
74
74
 
75
75
  Most consumers only need `index.css`:
@@ -100,7 +100,58 @@ Accordion, Alert, AlertDialog, AspectRatio, Avatar, Badge, Breadcrumb, Button, B
100
100
 
101
101
  TetraScience-specific compositions built from UI primitives:
102
102
 
103
- AppHeader, AppLayout, AssistantModal, CodeScriptEditorButton, LaunchContent, Main, Navbar, ProtocolConfiguration, ProtocolYamlCard, PythonEditorModal, Sidebar, TdpLink, TdpSearch, TdpUrl
103
+ AppHeader, AppLayout, AssistantModal, CodeScriptEditorButton, LaunchContent, Main, Navbar, ProcessFlow, ProtocolConfiguration, ProtocolYamlCard, PythonEditorModal, Sidebar, TdpLink, TdpSearch, TdpUrl
104
+
105
+ #### ProcessFlow
106
+
107
+ Use `ProcessFlow` to render parent-owned multi-step workflow state such as uploads, validation pipelines, review flows, processing stages, and setup sequences. Import it from the package and keep all workflow transitions and side effects in the consuming app.
108
+
109
+ ```tsx
110
+ import {
111
+ PROCESS_FLOW_STEP_STATUSES,
112
+ ProcessFlow,
113
+ type ProcessFlowStep,
114
+ type ProcessFlowStepStatus,
115
+ } from "@tetrascience-npm/tetrascience-react-ui";
116
+
117
+ const steps: ProcessFlowStep[] = [
118
+ { id: "upload", label: "Upload", description: "Choose source files", status: "completed" },
119
+ { id: "validate", label: "Validate", description: "Check schema and lineage", status: "active" },
120
+ { id: "publish", label: "Publish", description: "Send downstream", status: "pending" },
121
+ ];
122
+
123
+ function WorkflowProgress() {
124
+ return (
125
+ <ProcessFlow
126
+ steps={steps}
127
+ selectedStepId="validate"
128
+ onStepSelect={(step, details) => {
129
+ console.log(step.id, details.status);
130
+ }}
131
+ />
132
+ );
133
+ }
134
+
135
+ const allStatuses: readonly ProcessFlowStepStatus[] = PROCESS_FLOW_STEP_STATUSES;
136
+ ```
137
+
138
+ Expected contract:
139
+
140
+ - `status` is independently controlled per step: `pending`, `active`, `completed`, `error`, or `disabled`.
141
+ - `selectedStepId` means the step the user is viewing or has clicked; it is separate from the `active` workflow state.
142
+ - `onStepSelect` emits user selection only. It does not mean a workflow step completed.
143
+ - Parent workflow code owns completion, error handling, retries, analytics, and other side effects.
144
+ - `description` is shown by default. Pass `showDescriptions={false}` to hide all descriptions.
145
+ - Descriptions auto-hide at narrow container widths (≤40rem) for mobile layouts.
146
+ - The component fills 100% of its container width — size it by controlling the container.
147
+ - Selected completed steps render with a green label; selected active steps render with a blue label.
148
+ - Use `connections` and per-step `position` only for simple branching/configurable flows.
149
+
150
+ For AI-assisted consuming apps, add a short instruction like this to the app's `AGENTS.md` or `CLAUDE.md`:
151
+
152
+ ```md
153
+ Use `ProcessFlow` from `@tetrascience-npm/tetrascience-react-ui` for multi-step workflow visualization. Do not build a custom stepper for upload, validation, review, approval, processing, or setup flows. Parent components own the workflow state and pass `steps: ProcessFlowStep[]`; each step status must be one of `PROCESS_FLOW_STEP_STATUSES`. Use `selectedStepId` only for the viewed/selected step. Keep completion/error side effects in the parent workflow code, not inside `ProcessFlow`.
154
+ ```
104
155
 
105
156
  ### Charts (`charts/`)
106
157
 
@@ -117,7 +168,7 @@ Beyond UI components, this library includes server-side helper functions for bui
117
168
  **JWT Token Manager** - Manages JWT token retrieval for data apps:
118
169
 
119
170
  ```typescript
120
- import { jwtManager } from '@tetrascience-npm/tetrascience-react-ui/server';
171
+ import { jwtManager } from "@tetrascience-npm/tetrascience-react-ui/server";
121
172
 
122
173
  // In Express middleware
123
174
  app.use(async (req, res, next) => {
@@ -146,12 +197,8 @@ TypeScript equivalents of the Python helpers from `ts-lib-ui-kit-streamlit` for
146
197
  **Getting Provider Configurations:**
147
198
 
148
199
  ```typescript
149
- import { TDPClient } from '@tetrascience-npm/ts-connectors-sdk';
150
- import {
151
- getProviderConfigurations,
152
- buildProvider,
153
- jwtManager,
154
- } from '@tetrascience-npm/tetrascience-react-ui/server';
200
+ import { TDPClient } from "@tetrascience-npm/ts-connectors-sdk";
201
+ import { getProviderConfigurations, buildProvider, jwtManager } from "@tetrascience-npm/tetrascience-react-ui/server";
155
202
 
156
203
  // Get user's auth token from request (e.g., in Express middleware)
157
204
  const userToken = await jwtManager.getTokenFromExpressRequest(req);
@@ -160,7 +207,7 @@ const userToken = await jwtManager.getTokenFromExpressRequest(req);
160
207
  // Other fields (tdpEndpoint, connectorId, orgSlug) are read from environment variables
161
208
  const client = new TDPClient({
162
209
  authToken: userToken,
163
- artifactType: 'data-app',
210
+ artifactType: "data-app",
164
211
  });
165
212
  await client.init();
166
213
 
@@ -172,7 +219,7 @@ for (const config of providers) {
172
219
 
173
220
  // Build a database connection from the config
174
221
  const provider = await buildProvider(config);
175
- const results = await provider.query('SELECT * FROM my_table LIMIT 10');
222
+ const results = await provider.query("SELECT * FROM my_table LIMIT 10");
176
223
  await provider.close();
177
224
  }
178
225
  ```
@@ -185,21 +232,21 @@ import {
185
232
  buildDatabricksProvider,
186
233
  getTdpAthenaProvider,
187
234
  type ProviderConfiguration,
188
- } from '@tetrascience-npm/tetrascience-react-ui/server';
235
+ } from "@tetrascience-npm/tetrascience-react-ui/server";
189
236
 
190
237
  // Snowflake
191
238
  const snowflakeProvider = await buildSnowflakeProvider(config);
192
- const data = await snowflakeProvider.query('SELECT * FROM users');
239
+ const data = await snowflakeProvider.query("SELECT * FROM users");
193
240
  await snowflakeProvider.close();
194
241
 
195
242
  // Databricks
196
243
  const databricksProvider = await buildDatabricksProvider(config);
197
- const data = await databricksProvider.query('SELECT * FROM events');
244
+ const data = await databricksProvider.query("SELECT * FROM events");
198
245
  await databricksProvider.close();
199
246
 
200
247
  // TDP Athena (uses environment configuration)
201
248
  const athenaProvider = await getTdpAthenaProvider();
202
- const data = await athenaProvider.query('SELECT * FROM files');
249
+ const data = await athenaProvider.query("SELECT * FROM files");
203
250
  await athenaProvider.close();
204
251
  ```
205
252
 
@@ -211,15 +258,15 @@ import {
211
258
  MissingTableError,
212
259
  ProviderConnectionError,
213
260
  InvalidProviderConfigurationError,
214
- } from '@tetrascience-npm/tetrascience-react-ui/server';
261
+ } from "@tetrascience-npm/tetrascience-react-ui/server";
215
262
 
216
263
  try {
217
- const results = await provider.query('SELECT * FROM missing_table');
264
+ const results = await provider.query("SELECT * FROM missing_table");
218
265
  } catch (error) {
219
266
  if (error instanceof MissingTableError) {
220
- console.error('Table not found:', error.message);
267
+ console.error("Table not found:", error.message);
221
268
  } else if (error instanceof QueryError) {
222
- console.error('Query failed:', error.message);
269
+ console.error("Query failed:", error.message);
223
270
  }
224
271
  }
225
272
  ```
@@ -242,20 +289,20 @@ The TDP connector key/value store lets data apps persist small pieces of state (
242
289
  **Reading and writing values with the user's JWT token:**
243
290
 
244
291
  ```typescript
245
- import { TDPClient } from '@tetrascience-npm/ts-connectors-sdk';
246
- import { jwtManager } from '@tetrascience-npm/tetrascience-react-ui/server';
292
+ import { TDPClient } from "@tetrascience-npm/ts-connectors-sdk";
293
+ import { jwtManager } from "@tetrascience-npm/tetrascience-react-ui/server";
247
294
 
248
295
  // In an Express route handler:
249
- app.get('/api/kv/:key', async (req, res) => {
296
+ app.get("/api/kv/:key", async (req, res) => {
250
297
  // 1. Get the user's JWT from request cookies
251
298
  const userToken = await jwtManager.getTokenFromExpressRequest(req);
252
- if (!userToken) return res.status(401).json({ error: 'Not authenticated' });
299
+ if (!userToken) return res.status(401).json({ error: "Not authenticated" });
253
300
 
254
301
  // 2. Create a TDPClient authenticated as the user
255
302
  // (CONNECTOR_ID, TDP_ENDPOINT, ORG_SLUG are read from env vars)
256
303
  const client = new TDPClient({
257
304
  authToken: userToken,
258
- artifactType: 'data-app',
305
+ artifactType: "data-app",
259
306
  });
260
307
  await client.init();
261
308
 
@@ -264,13 +311,13 @@ app.get('/api/kv/:key', async (req, res) => {
264
311
  res.json({ key: req.params.key, value });
265
312
  });
266
313
 
267
- app.put('/api/kv/:key', async (req, res) => {
314
+ app.put("/api/kv/:key", async (req, res) => {
268
315
  const userToken = await jwtManager.getTokenFromExpressRequest(req);
269
- if (!userToken) return res.status(401).json({ error: 'Not authenticated' });
316
+ if (!userToken) return res.status(401).json({ error: "Not authenticated" });
270
317
 
271
318
  const client = new TDPClient({
272
319
  authToken: userToken,
273
- artifactType: 'data-app',
320
+ artifactType: "data-app",
274
321
  });
275
322
  await client.init();
276
323
 
@@ -283,7 +330,7 @@ app.put('/api/kv/:key', async (req, res) => {
283
330
  **Reading multiple values at once:**
284
331
 
285
332
  ```typescript
286
- const values = await client.getValues(['theme', 'locale', 'last-run']);
333
+ const values = await client.getValues(["theme", "locale", "last-run"]);
287
334
  // values[0] → theme, values[1] → locale, values[2] → last-run
288
335
  ```
289
336
 
@@ -315,8 +362,8 @@ Frontend: use `<TdpSearch columns={...} />` with default `apiEndpoint="/api/sear
315
362
  Full TypeScript support with exported types:
316
363
 
317
364
  ```tsx
318
- import { Button } from '@tetrascience-npm/tetrascience-react-ui';
319
- import type { ButtonProps, BarGraphProps, BarDataSeries } from '@tetrascience-npm/tetrascience-react-ui';
365
+ import { Button } from "@tetrascience-npm/tetrascience-react-ui";
366
+ import type { ButtonProps, BarGraphProps, BarDataSeries } from "@tetrascience-npm/tetrascience-react-ui";
320
367
  ```
321
368
 
322
369
  ## Examples
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),x=require("lucide-react"),u=require("../../ui/button.cjs"),w=require("../../ui/input.cjs"),h=require("../../ui/popover.cjs"),n=require("../../ui/select.cjs"),z=require("../../../lib/utils.cjs"),P={contains:"contains",equals:"equals",not_equals:"not equals",starts_with:"starts with",ends_with:"ends with",is_empty:"is empty",is_not_empty:"is not empty"},R=["is_empty","is_not_empty"],f=["contains","equals","not_equals","starts_with","ends_with","is_empty","is_not_empty"];function O(r,d){const l=d?.[0]??"contains";return{id:crypto.randomUUID(),columnId:r,operator:l,value:""}}function A({columns:r,columnLabel:d,filters:l,onFiltersChange:o,triggerLabel:m="Filter",className:g}){const _=t=>d?.(t)??t,v=r[0],y=v?.columnId??"",C=()=>o([...l,O(y,v?.operators)]),I=t=>o(l.filter(c=>c.id!==t)),p=(t,c)=>o(l.map(a=>a.id===t?{...a,...c}:a)),N=()=>o([]),i=l.length;return e.jsxs(h.Popover,{children:[e.jsx(h.PopoverTrigger,{asChild:!0,children:e.jsxs(u.Button,{type:"button",variant:"outline",size:"sm","data-slot":"manifest-filter",className:z.cn(g),"aria-label":i>0?`${m} (${i} active)`:m,children:[e.jsx(x.ListFilterIcon,{className:"size-3.5"}),m,i>0?e.jsx("span",{className:"flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground",children:i}):null]})}),e.jsx(h.PopoverContent,{align:"end",className:"min-w-80",children:e.jsxs("div",{className:"flex flex-col gap-2",children:[l.map(t=>{const a=r.find(s=>s.columnId===t.columnId)?.operators??f,S=R.includes(t.operator);return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(n.Select,{value:t.columnId,onValueChange:s=>{const j=r.find(b=>b.columnId===s)?.operators??f,q=j.includes(t.operator)?t.operator:j[0]??"contains";p(t.id,{columnId:s,operator:q,value:""})},children:[e.jsx(n.SelectTrigger,{size:"sm",className:"w-36",children:e.jsx(n.SelectValue,{})}),e.jsx(n.SelectContent,{children:r.map(s=>e.jsx(n.SelectItem,{value:s.columnId,children:s.label??_(s.columnId)},s.columnId))})]}),e.jsxs(n.Select,{value:t.operator,onValueChange:s=>p(t.id,{operator:s,value:""}),children:[e.jsx(n.SelectTrigger,{size:"sm",className:"w-32",children:e.jsx(n.SelectValue,{})}),e.jsx(n.SelectContent,{children:a.map(s=>e.jsx(n.SelectItem,{value:s,children:P[s]},s))})]}),S?e.jsx("div",{className:"h-8 w-40","aria-hidden":!0}):e.jsx(w.Input,{className:"w-40",placeholder:"Value…",value:t.value,onChange:s=>p(t.id,{value:s.target.value})}),e.jsx(u.Button,{type:"button",variant:"ghost",size:"icon",className:"size-8 shrink-0 text-muted-foreground hover:text-foreground",onClick:()=>I(t.id),"aria-label":"Remove filter",children:e.jsx(x.XIcon,{className:"size-3.5"})})]},t.id)}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(u.Button,{type:"button",variant:"outline",size:"sm",onClick:C,disabled:r.length===0,children:[e.jsx(x.PlusIcon,{className:"size-3.5"}),"Add filter"]}),l.length>0?e.jsx(u.Button,{type:"button",variant:"ghost",size:"sm",className:"text-muted-foreground",onClick:N,children:"Clear all"}):null]})]})})]})}exports.ManifestFilterPopover=A;
2
+ //# sourceMappingURL=ManifestFilterPopover.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ManifestFilterPopover.cjs","sources":["../../../../src/components/composed/PlateMapEditor/ManifestFilterPopover.tsx"],"sourcesContent":["import { ListFilterIcon, PlusIcon, XIcon } from \"lucide-react\";\n\nimport type { FilterColumnConfig, FilterCondition, FilterOperator } from \"@/components/ui/data-table/data-table\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n contains: \"contains\",\n equals: \"equals\",\n not_equals: \"not equals\",\n starts_with: \"starts with\",\n ends_with: \"ends with\",\n is_empty: \"is empty\",\n is_not_empty: \"is not empty\",\n};\n\nconst VALUE_FREE_OPERATORS: FilterOperator[] = [\"is_empty\", \"is_not_empty\"];\n\nconst DEFAULT_OPERATORS: FilterOperator[] = [\n \"contains\",\n \"equals\",\n \"not_equals\",\n \"starts_with\",\n \"ends_with\",\n \"is_empty\",\n \"is_not_empty\",\n];\n\nfunction makeCondition(columnId: string, allowedOperators?: FilterOperator[]): FilterCondition {\n const operator = allowedOperators?.[0] ?? \"contains\";\n return { id: crypto.randomUUID(), columnId, operator, value: \"\" };\n}\n\nexport interface ManifestFilterPopoverProps {\n /** Filterable columns. Operator subsets can be specified per column. */\n columns: FilterColumnConfig[];\n /** Column id → display label resolver. */\n columnLabel?: (columnId: string) => string;\n filters: FilterCondition[];\n onFiltersChange: (next: FilterCondition[]) => void;\n triggerLabel?: string;\n className?: string;\n}\n\nexport function ManifestFilterPopover({\n columns,\n columnLabel,\n filters,\n onFiltersChange,\n triggerLabel = \"Filter\",\n className,\n}: ManifestFilterPopoverProps) {\n const labelFor = (columnId: string): string => columnLabel?.(columnId) ?? columnId;\n const firstColumn = columns[0];\n const firstColumnId = firstColumn?.columnId ?? \"\";\n\n const addFilter = () => onFiltersChange([...filters, makeCondition(firstColumnId, firstColumn?.operators)]);\n const removeFilter = (id: string) => onFiltersChange(filters.filter((cond) => cond.id !== id));\n const updateFilter = (id: string, patch: Partial<FilterCondition>) =>\n onFiltersChange(filters.map((cond) => (cond.id === id ? { ...cond, ...patch } : cond)));\n const clearAll = () => onFiltersChange([]);\n\n const activeCount = filters.length;\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-slot=\"manifest-filter\"\n className={cn(className)}\n aria-label={activeCount > 0 ? `${triggerLabel} (${activeCount} active)` : triggerLabel}\n >\n <ListFilterIcon className=\"size-3.5\" />\n {triggerLabel}\n {activeCount > 0 ? (\n <span className=\"flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground\">\n {activeCount}\n </span>\n ) : null}\n </Button>\n </PopoverTrigger>\n\n <PopoverContent align=\"end\" className=\"min-w-80\">\n <div className=\"flex flex-col gap-2\">\n {filters.map((condition) => {\n const colConfig = columns.find((c) => c.columnId === condition.columnId);\n const operators = colConfig?.operators ?? DEFAULT_OPERATORS;\n const isValueFree = VALUE_FREE_OPERATORS.includes(condition.operator);\n return (\n <div key={condition.id} className=\"flex items-center gap-2\">\n <Select\n value={condition.columnId}\n onValueChange={(value) => {\n const nextConfig = columns.find((c) => c.columnId === value);\n const nextOperators = nextConfig?.operators ?? DEFAULT_OPERATORS;\n const nextOperator = nextOperators.includes(condition.operator)\n ? condition.operator\n : (nextOperators[0] ?? \"contains\");\n updateFilter(condition.id, { columnId: value, operator: nextOperator, value: \"\" });\n }}\n >\n <SelectTrigger size=\"sm\" className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {columns.map((c) => (\n <SelectItem key={c.columnId} value={c.columnId}>\n {c.label ?? labelFor(c.columnId)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(value) =>\n updateFilter(condition.id, { operator: value as FilterOperator, value: \"\" })\n }\n >\n <SelectTrigger size=\"sm\" className=\"w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {isValueFree ? (\n <div className=\"h-8 w-40\" aria-hidden />\n ) : (\n <Input\n className=\"w-40\"\n placeholder=\"Value…\"\n value={condition.value}\n onChange={(event) => updateFilter(condition.id, { value: event.target.value })}\n />\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-8 shrink-0 text-muted-foreground hover:text-foreground\"\n onClick={() => removeFilter(condition.id)}\n aria-label=\"Remove filter\"\n >\n <XIcon className=\"size-3.5\" />\n </Button>\n </div>\n );\n })}\n\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={addFilter}\n disabled={columns.length === 0}\n >\n <PlusIcon className=\"size-3.5\" />\n Add filter\n </Button>\n {filters.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-muted-foreground\"\n onClick={clearAll}\n >\n Clear all\n </Button>\n ) : null}\n </div>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["OPERATOR_LABELS","VALUE_FREE_OPERATORS","DEFAULT_OPERATORS","makeCondition","columnId","allowedOperators","operator","ManifestFilterPopover","columns","columnLabel","filters","onFiltersChange","triggerLabel","className","labelFor","firstColumn","firstColumnId","addFilter","removeFilter","id","cond","updateFilter","patch","clearAll","activeCount","Popover","jsx","PopoverTrigger","jsxs","Button","cn","ListFilterIcon","PopoverContent","condition","operators","c","isValueFree","Select","value","nextOperators","nextOperator","SelectTrigger","SelectValue","SelectContent","SelectItem","op","Input","event","XIcon","PlusIcon"],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ import { jsxs as l, jsx as t } from "react/jsx-runtime";
2
+ import { ListFilterIcon as R, XIcon as S, PlusIcon as F } from "lucide-react";
3
+ import { Button as m } from "../../ui/button.js";
4
+ import { Input as q } from "../../ui/input.js";
5
+ import { Popover as T, PopoverTrigger as V, PopoverContent as k } from "../../ui/popover.js";
6
+ import { Select as v, SelectTrigger as g, SelectValue as _, SelectContent as x, SelectItem as y } from "../../ui/select.js";
7
+ import { cn as U } from "../../../lib/utils.js";
8
+ const j = {
9
+ contains: "contains",
10
+ equals: "equals",
11
+ not_equals: "not equals",
12
+ starts_with: "starts with",
13
+ ends_with: "ends with",
14
+ is_empty: "is empty",
15
+ is_not_empty: "is not empty"
16
+ }, L = ["is_empty", "is_not_empty"], C = [
17
+ "contains",
18
+ "equals",
19
+ "not_equals",
20
+ "starts_with",
21
+ "ends_with",
22
+ "is_empty",
23
+ "is_not_empty"
24
+ ];
25
+ function B(n, u) {
26
+ const r = u?.[0] ?? "contains";
27
+ return { id: crypto.randomUUID(), columnId: n, operator: r, value: "" };
28
+ }
29
+ function Q({
30
+ columns: n,
31
+ columnLabel: u,
32
+ filters: r,
33
+ onFiltersChange: o,
34
+ triggerLabel: d = "Filter",
35
+ className: I
36
+ }) {
37
+ const N = (e) => u?.(e) ?? e, h = n[0], w = h?.columnId ?? "", z = () => o([...r, B(w, h?.operators)]), b = (e) => o(r.filter((c) => c.id !== e)), p = (e, c) => o(r.map((s) => s.id === e ? { ...s, ...c } : s)), A = () => o([]), i = r.length;
38
+ return /* @__PURE__ */ l(T, { children: [
39
+ /* @__PURE__ */ t(V, { asChild: !0, children: /* @__PURE__ */ l(
40
+ m,
41
+ {
42
+ type: "button",
43
+ variant: "outline",
44
+ size: "sm",
45
+ "data-slot": "manifest-filter",
46
+ className: U(I),
47
+ "aria-label": i > 0 ? `${d} (${i} active)` : d,
48
+ children: [
49
+ /* @__PURE__ */ t(R, { className: "size-3.5" }),
50
+ d,
51
+ i > 0 ? /* @__PURE__ */ t("span", { className: "flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground", children: i }) : null
52
+ ]
53
+ }
54
+ ) }),
55
+ /* @__PURE__ */ t(k, { align: "end", className: "min-w-80", children: /* @__PURE__ */ l("div", { className: "flex flex-col gap-2", children: [
56
+ r.map((e) => {
57
+ const s = n.find((a) => a.columnId === e.columnId)?.operators ?? C, E = L.includes(e.operator);
58
+ return /* @__PURE__ */ l("div", { className: "flex items-center gap-2", children: [
59
+ /* @__PURE__ */ l(
60
+ v,
61
+ {
62
+ value: e.columnId,
63
+ onValueChange: (a) => {
64
+ const f = n.find((P) => P.columnId === a)?.operators ?? C, O = f.includes(e.operator) ? e.operator : f[0] ?? "contains";
65
+ p(e.id, { columnId: a, operator: O, value: "" });
66
+ },
67
+ children: [
68
+ /* @__PURE__ */ t(g, { size: "sm", className: "w-36", children: /* @__PURE__ */ t(_, {}) }),
69
+ /* @__PURE__ */ t(x, { children: n.map((a) => /* @__PURE__ */ t(y, { value: a.columnId, children: a.label ?? N(a.columnId) }, a.columnId)) })
70
+ ]
71
+ }
72
+ ),
73
+ /* @__PURE__ */ l(
74
+ v,
75
+ {
76
+ value: e.operator,
77
+ onValueChange: (a) => p(e.id, { operator: a, value: "" }),
78
+ children: [
79
+ /* @__PURE__ */ t(g, { size: "sm", className: "w-32", children: /* @__PURE__ */ t(_, {}) }),
80
+ /* @__PURE__ */ t(x, { children: s.map((a) => /* @__PURE__ */ t(y, { value: a, children: j[a] }, a)) })
81
+ ]
82
+ }
83
+ ),
84
+ E ? /* @__PURE__ */ t("div", { className: "h-8 w-40", "aria-hidden": !0 }) : /* @__PURE__ */ t(
85
+ q,
86
+ {
87
+ className: "w-40",
88
+ placeholder: "Value…",
89
+ value: e.value,
90
+ onChange: (a) => p(e.id, { value: a.target.value })
91
+ }
92
+ ),
93
+ /* @__PURE__ */ t(
94
+ m,
95
+ {
96
+ type: "button",
97
+ variant: "ghost",
98
+ size: "icon",
99
+ className: "size-8 shrink-0 text-muted-foreground hover:text-foreground",
100
+ onClick: () => b(e.id),
101
+ "aria-label": "Remove filter",
102
+ children: /* @__PURE__ */ t(S, { className: "size-3.5" })
103
+ }
104
+ )
105
+ ] }, e.id);
106
+ }),
107
+ /* @__PURE__ */ l("div", { className: "flex items-center gap-2", children: [
108
+ /* @__PURE__ */ l(
109
+ m,
110
+ {
111
+ type: "button",
112
+ variant: "outline",
113
+ size: "sm",
114
+ onClick: z,
115
+ disabled: n.length === 0,
116
+ children: [
117
+ /* @__PURE__ */ t(F, { className: "size-3.5" }),
118
+ "Add filter"
119
+ ]
120
+ }
121
+ ),
122
+ r.length > 0 ? /* @__PURE__ */ t(
123
+ m,
124
+ {
125
+ type: "button",
126
+ variant: "ghost",
127
+ size: "sm",
128
+ className: "text-muted-foreground",
129
+ onClick: A,
130
+ children: "Clear all"
131
+ }
132
+ ) : null
133
+ ] })
134
+ ] }) })
135
+ ] });
136
+ }
137
+ export {
138
+ Q as ManifestFilterPopover
139
+ };
140
+ //# sourceMappingURL=ManifestFilterPopover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ManifestFilterPopover.js","sources":["../../../../src/components/composed/PlateMapEditor/ManifestFilterPopover.tsx"],"sourcesContent":["import { ListFilterIcon, PlusIcon, XIcon } from \"lucide-react\";\n\nimport type { FilterColumnConfig, FilterCondition, FilterOperator } from \"@/components/ui/data-table/data-table\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n contains: \"contains\",\n equals: \"equals\",\n not_equals: \"not equals\",\n starts_with: \"starts with\",\n ends_with: \"ends with\",\n is_empty: \"is empty\",\n is_not_empty: \"is not empty\",\n};\n\nconst VALUE_FREE_OPERATORS: FilterOperator[] = [\"is_empty\", \"is_not_empty\"];\n\nconst DEFAULT_OPERATORS: FilterOperator[] = [\n \"contains\",\n \"equals\",\n \"not_equals\",\n \"starts_with\",\n \"ends_with\",\n \"is_empty\",\n \"is_not_empty\",\n];\n\nfunction makeCondition(columnId: string, allowedOperators?: FilterOperator[]): FilterCondition {\n const operator = allowedOperators?.[0] ?? \"contains\";\n return { id: crypto.randomUUID(), columnId, operator, value: \"\" };\n}\n\nexport interface ManifestFilterPopoverProps {\n /** Filterable columns. Operator subsets can be specified per column. */\n columns: FilterColumnConfig[];\n /** Column id → display label resolver. */\n columnLabel?: (columnId: string) => string;\n filters: FilterCondition[];\n onFiltersChange: (next: FilterCondition[]) => void;\n triggerLabel?: string;\n className?: string;\n}\n\nexport function ManifestFilterPopover({\n columns,\n columnLabel,\n filters,\n onFiltersChange,\n triggerLabel = \"Filter\",\n className,\n}: ManifestFilterPopoverProps) {\n const labelFor = (columnId: string): string => columnLabel?.(columnId) ?? columnId;\n const firstColumn = columns[0];\n const firstColumnId = firstColumn?.columnId ?? \"\";\n\n const addFilter = () => onFiltersChange([...filters, makeCondition(firstColumnId, firstColumn?.operators)]);\n const removeFilter = (id: string) => onFiltersChange(filters.filter((cond) => cond.id !== id));\n const updateFilter = (id: string, patch: Partial<FilterCondition>) =>\n onFiltersChange(filters.map((cond) => (cond.id === id ? { ...cond, ...patch } : cond)));\n const clearAll = () => onFiltersChange([]);\n\n const activeCount = filters.length;\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-slot=\"manifest-filter\"\n className={cn(className)}\n aria-label={activeCount > 0 ? `${triggerLabel} (${activeCount} active)` : triggerLabel}\n >\n <ListFilterIcon className=\"size-3.5\" />\n {triggerLabel}\n {activeCount > 0 ? (\n <span className=\"flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground\">\n {activeCount}\n </span>\n ) : null}\n </Button>\n </PopoverTrigger>\n\n <PopoverContent align=\"end\" className=\"min-w-80\">\n <div className=\"flex flex-col gap-2\">\n {filters.map((condition) => {\n const colConfig = columns.find((c) => c.columnId === condition.columnId);\n const operators = colConfig?.operators ?? DEFAULT_OPERATORS;\n const isValueFree = VALUE_FREE_OPERATORS.includes(condition.operator);\n return (\n <div key={condition.id} className=\"flex items-center gap-2\">\n <Select\n value={condition.columnId}\n onValueChange={(value) => {\n const nextConfig = columns.find((c) => c.columnId === value);\n const nextOperators = nextConfig?.operators ?? DEFAULT_OPERATORS;\n const nextOperator = nextOperators.includes(condition.operator)\n ? condition.operator\n : (nextOperators[0] ?? \"contains\");\n updateFilter(condition.id, { columnId: value, operator: nextOperator, value: \"\" });\n }}\n >\n <SelectTrigger size=\"sm\" className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {columns.map((c) => (\n <SelectItem key={c.columnId} value={c.columnId}>\n {c.label ?? labelFor(c.columnId)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(value) =>\n updateFilter(condition.id, { operator: value as FilterOperator, value: \"\" })\n }\n >\n <SelectTrigger size=\"sm\" className=\"w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {isValueFree ? (\n <div className=\"h-8 w-40\" aria-hidden />\n ) : (\n <Input\n className=\"w-40\"\n placeholder=\"Value…\"\n value={condition.value}\n onChange={(event) => updateFilter(condition.id, { value: event.target.value })}\n />\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-8 shrink-0 text-muted-foreground hover:text-foreground\"\n onClick={() => removeFilter(condition.id)}\n aria-label=\"Remove filter\"\n >\n <XIcon className=\"size-3.5\" />\n </Button>\n </div>\n );\n })}\n\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={addFilter}\n disabled={columns.length === 0}\n >\n <PlusIcon className=\"size-3.5\" />\n Add filter\n </Button>\n {filters.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-muted-foreground\"\n onClick={clearAll}\n >\n Clear all\n </Button>\n ) : null}\n </div>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["OPERATOR_LABELS","VALUE_FREE_OPERATORS","DEFAULT_OPERATORS","makeCondition","columnId","allowedOperators","operator","ManifestFilterPopover","columns","columnLabel","filters","onFiltersChange","triggerLabel","className","labelFor","firstColumn","firstColumnId","addFilter","removeFilter","id","cond","updateFilter","patch","clearAll","activeCount","Popover","jsx","PopoverTrigger","jsxs","Button","cn","ListFilterIcon","PopoverContent","condition","operators","c","isValueFree","Select","value","nextOperators","nextOperator","SelectTrigger","SelectValue","SelectContent","SelectItem","op","Input","event","XIcon","PlusIcon"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),d=require("lucide-react"),C=require("react"),A=require("./csvPlateTriage.cjs"),F=require("./helpers.cjs"),I=require("../../ui/button.cjs"),n=require("../../ui/dropdown-menu.cjs"),z=require("../../../lib/utils.cjs");function _(t){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const r in t)if(r!=="default"){const i=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(a,r,i.get?i:{enumerable:!0,get:()=>t[r]})}}return a.default=t,Object.freeze(a)}const x=_(C);function b({inputRef:t,accept:a,onPick:r,triageCsv:i}){return e.jsx("input",{ref:t,type:"file",accept:a,hidden:!0,onChange:o=>{const u=o.currentTarget,s=o.target.files?.[0];if(!s){u.value="";return}(async()=>{const c=i?await A.triagePlateMapCsvFile(s):void 0;await(c?r?.(s,c):r?.(s)),u.value=""})()}})}function G({templates:t,templateId:a,onTemplateChange:r,onClearTemplate:i,hasEntries:o,onImportCsv:u,onExportCsv:s,onImportTemplate:c,onExportTemplate:j,label:g="Actions",align:D="start",side:m="bottom",csvAccept:S=".csv,text/csv",templateAccept:k="application/json",importTemplateLabel:y="Import template (JSON)",exportTemplateLabel:R="Save template",importCsvLabel:v="Import plate map (CSV)",exportCsvLabel:q="Export plate map (CSV)",clearLabel:N="Clear template",className:O}){const f=x.useRef(null),M=x.useRef(null),h=x.useMemo(()=>F.groupTemplateOptions(t),[t]),w=o===!1;return h.length>0||!!c||!!j||!!u||!!s||!!i?e.jsxs(e.Fragment,{children:[e.jsxs(n.DropdownMenu,{children:[e.jsx(n.DropdownMenuTrigger,{asChild:!0,children:e.jsxs(I.Button,{type:"button",variant:"outline",size:"sm",className:z.cn("min-w-24 justify-between",O),children:[e.jsx("span",{children:g}),e.jsx(d.ChevronDown,{"aria-hidden":!0})]})}),e.jsxs(n.DropdownMenuContent,{align:D,side:m,className:"w-64",children:[h.map(([p,P])=>e.jsxs(n.DropdownMenuGroup,{children:[p?e.jsx(n.DropdownMenuLabel,{children:p}):null,P.map(l=>e.jsxs(n.DropdownMenuItem,{disabled:l.disabled,onClick:()=>r?.(l.id),children:[l.id===a?e.jsx(d.Check,{"aria-hidden":!0}):e.jsx("span",{className:"size-4","aria-hidden":!0}),e.jsxs("span",{className:"flex min-w-0 flex-col",children:[e.jsx("span",{className:"truncate",children:l.label}),l.description?e.jsx("span",{className:"truncate text-xs text-muted-foreground",children:l.description}):null]})]},l.id))]},p||"templates")),h.length>0&&(c||j||u||s||i)?e.jsx(n.DropdownMenuSeparator,{}):null,c?e.jsxs(n.DropdownMenuItem,{onClick:()=>f.current?.click(),children:[e.jsx(d.LayoutTemplate,{"aria-hidden":!0}),y]}):null,j?e.jsxs(n.DropdownMenuItem,{disabled:w,onClick:()=>j(),children:[e.jsx(d.SaveAll,{"aria-hidden":!0}),R]}):null,(c||j)&&(u||s)?e.jsx(n.DropdownMenuSeparator,{}):null,u?e.jsxs(n.DropdownMenuItem,{onClick:()=>M.current?.click(),children:[e.jsx(d.Upload,{"aria-hidden":!0}),v]}):null,s?e.jsxs(n.DropdownMenuItem,{disabled:w,onClick:()=>s(),children:[e.jsx(d.Download,{"aria-hidden":!0}),q]}):null,i?e.jsxs(e.Fragment,{children:[e.jsx(n.DropdownMenuSeparator,{}),e.jsxs(n.DropdownMenuItem,{variant:"destructive",disabled:!a&&!o,onClick:()=>i(),children:[e.jsx(d.Trash2,{"aria-hidden":!0}),N]})]}):null]})]}),e.jsx(b,{inputRef:f,accept:k,onPick:c}),e.jsx(b,{inputRef:M,accept:S,onPick:u,triageCsv:!0})]}):null}exports.PlateMapActionsMenu=G;
2
+ //# sourceMappingURL=PlateMapActionsMenu.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlateMapActionsMenu.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapActionsMenu.tsx"],"sourcesContent":["import { Check, ChevronDown, Download, LayoutTemplate, SaveAll, Trash2, Upload } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { triagePlateMapCsvFile } from \"./csvPlateTriage\";\nimport { groupTemplateOptions } from \"./helpers\";\n\nimport type { ImportExportHandlers, PlateMapCsvTriage, TemplateOption } from \"./types\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateMapActionsMenuProps extends ImportExportHandlers {\n templates?: TemplateOption[];\n templateId?: string;\n onTemplateChange?: (id: string) => void;\n onClearTemplate?: () => void;\n /** Disable export/save actions when there is nothing to export. */\n hasEntries?: boolean;\n label?: React.ReactNode;\n align?: \"start\" | \"center\" | \"end\";\n side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n csvAccept?: string;\n templateAccept?: string;\n importTemplateLabel?: string;\n exportTemplateLabel?: string;\n importCsvLabel?: string;\n exportCsvLabel?: string;\n clearLabel?: string;\n className?: string;\n}\n\nfunction HiddenFileInput({\n inputRef,\n accept,\n onPick,\n triageCsv,\n}: {\n inputRef: React.RefObject<HTMLInputElement | null>;\n accept: string;\n onPick?: (file: File, triage?: PlateMapCsvTriage) => void | Promise<void>;\n triageCsv?: boolean;\n}) {\n return (\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n hidden\n onChange={(event) => {\n const input = event.currentTarget;\n const file = event.target.files?.[0];\n if (!file) {\n input.value = \"\";\n return;\n }\n\n void (async () => {\n const triage = triageCsv ? await triagePlateMapCsvFile(file) : undefined;\n await (triage ? onPick?.(file, triage) : onPick?.(file));\n input.value = \"\";\n })();\n }}\n />\n );\n}\n\nexport function PlateMapActionsMenu({\n templates,\n templateId,\n onTemplateChange,\n onClearTemplate,\n hasEntries,\n onImportCsv,\n onExportCsv,\n onImportTemplate,\n onExportTemplate,\n label = \"Actions\",\n align = \"start\",\n side = \"bottom\",\n csvAccept = \".csv,text/csv\",\n templateAccept = \"application/json\",\n importTemplateLabel = \"Import template (JSON)\",\n exportTemplateLabel = \"Save template\",\n importCsvLabel = \"Import plate map (CSV)\",\n exportCsvLabel = \"Export plate map (CSV)\",\n clearLabel = \"Clear template\",\n className,\n}: PlateMapActionsMenuProps) {\n const templateInputRef = React.useRef<HTMLInputElement>(null);\n const csvInputRef = React.useRef<HTMLInputElement>(null);\n const templateGroups = React.useMemo(() => groupTemplateOptions(templates), [templates]);\n const disableEntryExport = hasEntries === false;\n\n const hasMenuItems =\n templateGroups.length > 0 ||\n !!onImportTemplate ||\n !!onExportTemplate ||\n !!onImportCsv ||\n !!onExportCsv ||\n !!onClearTemplate;\n\n if (!hasMenuItems) return null;\n\n return (\n <>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className={cn(\"min-w-24 justify-between\", className)}>\n <span>{label}</span>\n <ChevronDown aria-hidden />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align} side={side} className=\"w-64\">\n {templateGroups.map(([group, options]) => (\n <DropdownMenuGroup key={group || \"templates\"}>\n {group ? <DropdownMenuLabel>{group}</DropdownMenuLabel> : null}\n {options.map((template) => (\n <DropdownMenuItem\n key={template.id}\n disabled={template.disabled}\n onClick={() => onTemplateChange?.(template.id)}\n >\n {template.id === templateId ? <Check aria-hidden /> : <span className=\"size-4\" aria-hidden />}\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate\">{template.label}</span>\n {template.description ? (\n <span className=\"truncate text-xs text-muted-foreground\">{template.description}</span>\n ) : null}\n </span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuGroup>\n ))}\n\n {templateGroups.length > 0 &&\n (onImportTemplate || onExportTemplate || onImportCsv || onExportCsv || onClearTemplate) ? (\n <DropdownMenuSeparator />\n ) : null}\n\n {onImportTemplate ? (\n <DropdownMenuItem onClick={() => templateInputRef.current?.click()}>\n <LayoutTemplate aria-hidden />\n {importTemplateLabel}\n </DropdownMenuItem>\n ) : null}\n {onExportTemplate ? (\n <DropdownMenuItem disabled={disableEntryExport} onClick={() => onExportTemplate()}>\n <SaveAll aria-hidden />\n {exportTemplateLabel}\n </DropdownMenuItem>\n ) : null}\n\n {(onImportTemplate || onExportTemplate) && (onImportCsv || onExportCsv) ? <DropdownMenuSeparator /> : null}\n\n {onImportCsv ? (\n <DropdownMenuItem onClick={() => csvInputRef.current?.click()}>\n <Upload aria-hidden />\n {importCsvLabel}\n </DropdownMenuItem>\n ) : null}\n {onExportCsv ? (\n <DropdownMenuItem disabled={disableEntryExport} onClick={() => onExportCsv()}>\n <Download aria-hidden />\n {exportCsvLabel}\n </DropdownMenuItem>\n ) : null}\n\n {onClearTemplate ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n disabled={!templateId && !hasEntries}\n onClick={() => onClearTemplate()}\n >\n <Trash2 aria-hidden />\n {clearLabel}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n <HiddenFileInput inputRef={templateInputRef} accept={templateAccept} onPick={onImportTemplate} />\n <HiddenFileInput inputRef={csvInputRef} accept={csvAccept} onPick={onImportCsv} triageCsv />\n </>\n );\n}\n"],"names":["HiddenFileInput","inputRef","accept","onPick","triageCsv","jsx","event","input","file","triage","triagePlateMapCsvFile","PlateMapActionsMenu","templates","templateId","onTemplateChange","onClearTemplate","hasEntries","onImportCsv","onExportCsv","onImportTemplate","onExportTemplate","label","align","side","csvAccept","templateAccept","importTemplateLabel","exportTemplateLabel","importCsvLabel","exportCsvLabel","clearLabel","className","templateInputRef","React","csvInputRef","templateGroups","groupTemplateOptions","disableEntryExport","jsxs","Fragment","DropdownMenu","DropdownMenuTrigger","Button","cn","ChevronDown","DropdownMenuContent","group","options","DropdownMenuGroup","DropdownMenuLabel","template","DropdownMenuItem","Check","DropdownMenuSeparator","LayoutTemplate","SaveAll","Upload","Download","Trash2"],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import { jsxs as n, Fragment as k, jsx as e } from "react/jsx-runtime";
2
+ import { ChevronDown as G, Check as L, LayoutTemplate as O, SaveAll as V, Upload as B, Download as H, Trash2 as J } from "lucide-react";
3
+ import * as m from "react";
4
+ import { triagePlateMapCsvFile as U } from "./csvPlateTriage.js";
5
+ import { groupTemplateOptions as q } from "./helpers.js";
6
+ import { Button as K } from "../../ui/button.js";
7
+ import { DropdownMenu as Q, DropdownMenuTrigger as W, DropdownMenuContent as X, DropdownMenuGroup as Y, DropdownMenuLabel as Z, DropdownMenuItem as c, DropdownMenuSeparator as w } from "../../ui/dropdown-menu.js";
8
+ import { cn as _ } from "../../../lib/utils.js";
9
+ function D({
10
+ inputRef: s,
11
+ accept: o,
12
+ onPick: h,
13
+ triageCsv: l
14
+ }) {
15
+ return /* @__PURE__ */ e(
16
+ "input",
17
+ {
18
+ ref: s,
19
+ type: "file",
20
+ accept: o,
21
+ hidden: !0,
22
+ onChange: (d) => {
23
+ const r = d.currentTarget, i = d.target.files?.[0];
24
+ if (!i) {
25
+ r.value = "";
26
+ return;
27
+ }
28
+ (async () => {
29
+ const t = l ? await U(i) : void 0;
30
+ await (t ? h?.(i, t) : h?.(i)), r.value = "";
31
+ })();
32
+ }
33
+ }
34
+ );
35
+ }
36
+ function te({
37
+ templates: s,
38
+ templateId: o,
39
+ onTemplateChange: h,
40
+ onClearTemplate: l,
41
+ hasEntries: d,
42
+ onImportCsv: r,
43
+ onExportCsv: i,
44
+ onImportTemplate: t,
45
+ onExportTemplate: u,
46
+ label: N = "Actions",
47
+ align: R = "start",
48
+ side: v = "bottom",
49
+ csvAccept: y = ".csv,text/csv",
50
+ templateAccept: S = "application/json",
51
+ importTemplateLabel: C = "Import template (JSON)",
52
+ exportTemplateLabel: j = "Save template",
53
+ importCsvLabel: x = "Import plate map (CSV)",
54
+ exportCsvLabel: A = "Export plate map (CSV)",
55
+ clearLabel: F = "Clear template",
56
+ className: P
57
+ }) {
58
+ const M = m.useRef(null), b = m.useRef(null), f = m.useMemo(() => q(s), [s]), g = d === !1;
59
+ return f.length > 0 || !!t || !!u || !!r || !!i || !!l ? /* @__PURE__ */ n(k, { children: [
60
+ /* @__PURE__ */ n(Q, { children: [
61
+ /* @__PURE__ */ e(W, { asChild: !0, children: /* @__PURE__ */ n(K, { type: "button", variant: "outline", size: "sm", className: _("min-w-24 justify-between", P), children: [
62
+ /* @__PURE__ */ e("span", { children: N }),
63
+ /* @__PURE__ */ e(G, { "aria-hidden": !0 })
64
+ ] }) }),
65
+ /* @__PURE__ */ n(X, { align: R, side: v, className: "w-64", children: [
66
+ f.map(([p, z]) => /* @__PURE__ */ n(Y, { children: [
67
+ p ? /* @__PURE__ */ e(Z, { children: p }) : null,
68
+ z.map((a) => /* @__PURE__ */ n(
69
+ c,
70
+ {
71
+ disabled: a.disabled,
72
+ onClick: () => h?.(a.id),
73
+ children: [
74
+ a.id === o ? /* @__PURE__ */ e(L, { "aria-hidden": !0 }) : /* @__PURE__ */ e("span", { className: "size-4", "aria-hidden": !0 }),
75
+ /* @__PURE__ */ n("span", { className: "flex min-w-0 flex-col", children: [
76
+ /* @__PURE__ */ e("span", { className: "truncate", children: a.label }),
77
+ a.description ? /* @__PURE__ */ e("span", { className: "truncate text-xs text-muted-foreground", children: a.description }) : null
78
+ ] })
79
+ ]
80
+ },
81
+ a.id
82
+ ))
83
+ ] }, p || "templates")),
84
+ f.length > 0 && (t || u || r || i || l) ? /* @__PURE__ */ e(w, {}) : null,
85
+ t ? /* @__PURE__ */ n(c, { onClick: () => M.current?.click(), children: [
86
+ /* @__PURE__ */ e(O, { "aria-hidden": !0 }),
87
+ C
88
+ ] }) : null,
89
+ u ? /* @__PURE__ */ n(c, { disabled: g, onClick: () => u(), children: [
90
+ /* @__PURE__ */ e(V, { "aria-hidden": !0 }),
91
+ j
92
+ ] }) : null,
93
+ (t || u) && (r || i) ? /* @__PURE__ */ e(w, {}) : null,
94
+ r ? /* @__PURE__ */ n(c, { onClick: () => b.current?.click(), children: [
95
+ /* @__PURE__ */ e(B, { "aria-hidden": !0 }),
96
+ x
97
+ ] }) : null,
98
+ i ? /* @__PURE__ */ n(c, { disabled: g, onClick: () => i(), children: [
99
+ /* @__PURE__ */ e(H, { "aria-hidden": !0 }),
100
+ A
101
+ ] }) : null,
102
+ l ? /* @__PURE__ */ n(k, { children: [
103
+ /* @__PURE__ */ e(w, {}),
104
+ /* @__PURE__ */ n(
105
+ c,
106
+ {
107
+ variant: "destructive",
108
+ disabled: !o && !d,
109
+ onClick: () => l(),
110
+ children: [
111
+ /* @__PURE__ */ e(J, { "aria-hidden": !0 }),
112
+ F
113
+ ]
114
+ }
115
+ )
116
+ ] }) : null
117
+ ] })
118
+ ] }),
119
+ /* @__PURE__ */ e(D, { inputRef: M, accept: S, onPick: t }),
120
+ /* @__PURE__ */ e(D, { inputRef: b, accept: y, onPick: r, triageCsv: !0 })
121
+ ] }) : null;
122
+ }
123
+ export {
124
+ te as PlateMapActionsMenu
125
+ };
126
+ //# sourceMappingURL=PlateMapActionsMenu.js.map