@health-samurai/react-components 0.0.0-alpha.18 → 0.0.0-alpha.20

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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.css +51 -33
  3. package/dist/src/components/code-editor/fhir-autocomplete.d.ts +70 -0
  4. package/dist/src/components/code-editor/fhir-autocomplete.d.ts.map +1 -0
  5. package/dist/src/components/code-editor/fhir-autocomplete.js +1849 -0
  6. package/dist/src/components/code-editor/fhir-autocomplete.js.map +1 -0
  7. package/dist/src/components/code-editor/fhir-autocomplete.test.js +1099 -0
  8. package/dist/src/components/code-editor/fhir-autocomplete.test.js.map +1 -0
  9. package/dist/src/components/code-editor/http/index.d.ts +9 -1
  10. package/dist/src/components/code-editor/http/index.d.ts.map +1 -1
  11. package/dist/src/components/code-editor/http/index.js +423 -3
  12. package/dist/src/components/code-editor/http/index.js.map +1 -1
  13. package/dist/src/components/code-editor/index.d.ts +13 -4
  14. package/dist/src/components/code-editor/index.d.ts.map +1 -1
  15. package/dist/src/components/code-editor/index.js +505 -96
  16. package/dist/src/components/code-editor/index.js.map +1 -1
  17. package/dist/src/components/code-editor/json-ast.d.ts +46 -0
  18. package/dist/src/components/code-editor/json-ast.d.ts.map +1 -0
  19. package/dist/src/components/code-editor/json-ast.js +465 -0
  20. package/dist/src/components/code-editor/json-ast.js.map +1 -0
  21. package/dist/src/components/code-editor/json-ast.test.js +206 -0
  22. package/dist/src/components/code-editor/json-ast.test.js.map +1 -0
  23. package/dist/src/components/code-editor/sql-completion.d.ts +22 -0
  24. package/dist/src/components/code-editor/sql-completion.d.ts.map +1 -0
  25. package/dist/src/components/code-editor/sql-completion.js +895 -0
  26. package/dist/src/components/code-editor/sql-completion.js.map +1 -0
  27. package/dist/src/components/date-picker-input.d.ts +10 -0
  28. package/dist/src/components/date-picker-input.d.ts.map +1 -0
  29. package/dist/src/components/date-picker-input.js +90 -0
  30. package/dist/src/components/date-picker-input.js.map +1 -0
  31. package/dist/src/components/date-picker-input.stories.js +76 -0
  32. package/dist/src/components/date-picker-input.stories.js.map +1 -0
  33. package/dist/src/index.d.ts +1 -0
  34. package/dist/src/index.d.ts.map +1 -1
  35. package/dist/src/index.js +1 -0
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/shadcn/components/ui/alert-dialog.d.ts +1 -1
  38. package/dist/src/shadcn/components/ui/calendar.d.ts +1 -1
  39. package/dist/src/shadcn/components/ui/carousel.d.ts +1 -1
  40. package/dist/src/shadcn/components/ui/chart.d.ts +3 -3
  41. package/dist/src/shadcn/components/ui/chart.d.ts.map +1 -1
  42. package/dist/src/shadcn/components/ui/chart.js +1 -1
  43. package/dist/src/shadcn/components/ui/chart.js.map +1 -1
  44. package/dist/src/shadcn/components/ui/command.d.ts +1 -1
  45. package/dist/src/shadcn/components/ui/pagination.d.ts +1 -1
  46. package/dist/src/shadcn/components/ui/resizable.stories.js +2 -2
  47. package/dist/src/shadcn/components/ui/resizable.stories.js.map +1 -1
  48. package/dist/src/shadcn/components/ui/sidebar.d.ts +4 -4
  49. package/dist/src/shadcn/components/ui/tabs.d.ts +3 -1
  50. package/dist/src/shadcn/components/ui/tabs.d.ts.map +1 -1
  51. package/dist/src/shadcn/components/ui/tabs.js +129 -2
  52. package/dist/src/shadcn/components/ui/tabs.js.map +1 -1
  53. package/dist/src/shadcn/components/ui/tabs.stories.js +1 -1
  54. package/dist/src/shadcn/components/ui/tabs.stories.js.map +1 -1
  55. package/dist/src/shadcn/components/ui/toggle-group.d.ts +1 -1
  56. package/dist/src/typography.css +1 -1
  57. package/package.json +24 -19
  58. package/src/components/code-editor/fhir-autocomplete.test.ts +993 -0
  59. package/src/components/code-editor/fhir-autocomplete.ts +2321 -0
  60. package/src/components/code-editor/http/index.ts +339 -2
  61. package/src/components/code-editor/index.tsx +593 -102
  62. package/src/components/code-editor/json-ast.test.ts +230 -0
  63. package/src/components/code-editor/json-ast.ts +590 -0
  64. package/src/components/code-editor/sql-completion.ts +1105 -0
  65. package/src/components/date-picker-input.stories.tsx +79 -0
  66. package/src/components/date-picker-input.tsx +104 -0
  67. package/src/index.tsx +1 -0
  68. package/src/shadcn/components/ui/chart.tsx +6 -3
  69. package/src/shadcn/components/ui/resizable.stories.tsx +2 -2
  70. package/src/shadcn/components/ui/tabs.stories.tsx +1 -1
  71. package/src/shadcn/components/ui/tabs.tsx +160 -2
  72. package/src/typography.css +1 -1
  73. package/dist/src/components/code-editor/http/grammar/http.test.d.ts +0 -2
  74. package/dist/src/components/code-editor/http/grammar/http.test.d.ts.map +0 -1
@@ -4,11 +4,21 @@ import {
4
4
  type Language,
5
5
  LanguageSupport,
6
6
  LRLanguage,
7
+ syntaxTree,
7
8
  } from "@codemirror/language";
9
+ import { RangeSetBuilder } from "@codemirror/state";
10
+ import {
11
+ Decoration,
12
+ type DecorationSet,
13
+ EditorView,
14
+ ViewPlugin,
15
+ type ViewUpdate,
16
+ } from "@codemirror/view";
8
17
  import { parseMixed } from "@lezer/common";
9
18
  import { styleTags, tags } from "@lezer/highlight";
10
19
  import type { LRParser } from "@lezer/lr";
11
20
  import { parser } from "./grammar/http";
21
+ import { HttpRequestMethod } from "./grammar/http.terms";
12
22
 
13
23
  function makeParser(
14
24
  bodyLanguages: (contentType: string) => Language | null,
@@ -78,10 +88,337 @@ function makeParser(
78
88
  });
79
89
  }
80
90
 
81
- function http(bodyLanguages: (contentType: string) => Language | null) {
91
+ const methodDecorations: Record<string, Decoration> = {
92
+ GET: Decoration.mark({ class: "cm-http-method-get" }),
93
+ POST: Decoration.mark({ class: "cm-http-method-post" }),
94
+ PUT: Decoration.mark({ class: "cm-http-method-put" }),
95
+ PATCH: Decoration.mark({ class: "cm-http-method-patch" }),
96
+ DELETE: Decoration.mark({ class: "cm-http-method-delete" }),
97
+ };
98
+
99
+ function buildMethodDecorations(view: EditorView): DecorationSet {
100
+ const builder = new RangeSetBuilder<Decoration>();
101
+ const tree = syntaxTree(view.state);
102
+ tree.iterate({
103
+ enter(node) {
104
+ if (node.type.id === HttpRequestMethod) {
105
+ const text = view.state.sliceDoc(node.from, node.to).toUpperCase();
106
+ const deco = methodDecorations[text];
107
+ if (deco) {
108
+ builder.add(node.from, node.to, deco);
109
+ }
110
+ }
111
+ },
112
+ });
113
+ return builder.finish();
114
+ }
115
+
116
+ const httpMethodHighlighter = ViewPlugin.fromClass(
117
+ class {
118
+ decorations: DecorationSet;
119
+ constructor(view: EditorView) {
120
+ this.decorations = buildMethodDecorations(view);
121
+ }
122
+ update(update: ViewUpdate) {
123
+ if (update.docChanged || update.viewportChanged) {
124
+ this.decorations = buildMethodDecorations(update.view);
125
+ }
126
+ }
127
+ },
128
+ { decorations: (v) => v.decorations },
129
+ );
130
+
131
+ const httpMethodTheme = EditorView.baseTheme({
132
+ ".cm-http-method-get": {
133
+ color: "var(--color-utility-green)",
134
+ },
135
+ ".cm-http-method-post": {
136
+ color: "var(--color-utility-yellow)",
137
+ },
138
+ ".cm-http-method-put": {
139
+ color: "var(--color-utility-blue)",
140
+ },
141
+ ".cm-http-method-patch": {
142
+ color: "var(--color-utility-violet)",
143
+ },
144
+ ".cm-http-method-delete": {
145
+ color: "var(--color-utility-red)",
146
+ },
147
+ });
148
+
149
+ // ── HTTP header autocomplete ────────────────────────────────────────────
150
+
151
+ import type {
152
+ Completion,
153
+ CompletionContext,
154
+ CompletionResult,
155
+ } from "@codemirror/autocomplete";
156
+ import {
157
+ HttpHeaderName,
158
+ HttpHeaders,
159
+ HttpHeaderValue,
160
+ HttpRequestPath,
161
+ } from "./grammar/http.terms";
162
+
163
+ const COMMON_HEADERS: Completion[] = [
164
+ // Standard HTTP
165
+ { label: "Accept", type: "header", apply: "Accept: " },
166
+ { label: "Accept-Encoding", type: "header", apply: "Accept-Encoding: " },
167
+ { label: "Accept-Language", type: "header", apply: "Accept-Language: " },
168
+ { label: "Authorization", type: "header", apply: "Authorization: " },
169
+ { label: "Cache-Control", type: "header", apply: "Cache-Control: " },
170
+ { label: "Content-Type", type: "header", apply: "Content-Type: " },
171
+ { label: "Cookie", type: "header", apply: "Cookie: " },
172
+ { label: "Host", type: "header", apply: "Host: " },
173
+ { label: "If-Match", type: "header", apply: "If-Match: " },
174
+ { label: "If-Modified-Since", type: "header", apply: "If-Modified-Since: " },
175
+ { label: "If-None-Match", type: "header", apply: "If-None-Match: " },
176
+ { label: "Origin", type: "header", apply: "Origin: " },
177
+ { label: "Prefer", type: "header", apply: "Prefer: " },
178
+ // Aidbox-specific
179
+ { label: "x-audit", type: "header", apply: "x-audit: " },
180
+ { label: "x-correlation-id", type: "header", apply: "x-correlation-id: " },
181
+ { label: "x-debug", type: "header", apply: "x-debug: " },
182
+ {
183
+ label: "x-external-user-id",
184
+ type: "header",
185
+ apply: "x-external-user-id: ",
186
+ },
187
+ {
188
+ label: "x-max-isolation-level",
189
+ type: "header",
190
+ apply: "x-max-isolation-level: ",
191
+ },
192
+ { label: "x-original-uri", type: "header", apply: "x-original-uri: " },
193
+ { label: "x-request-id", type: "header", apply: "x-request-id: " },
194
+ { label: "x-use-ro-replica", type: "header", apply: "x-use-ro-replica: " },
195
+ { label: "su", type: "header", apply: "su: " },
196
+ { label: "traceparent", type: "header", apply: "traceparent: " },
197
+ ];
198
+
199
+ // Header value completions by header name
200
+ const HEADER_VALUES: Record<string, Completion[]> = {
201
+ "content-type": [
202
+ { label: "application/json", type: "text" },
203
+ { label: "application/fhir+json", type: "text" },
204
+ { label: "text/yaml", type: "text" },
205
+ { label: "application/ndjson", type: "text" },
206
+ { label: "application/gzip", type: "text" },
207
+ { label: "text/csv", type: "text" },
208
+ ],
209
+ accept: [
210
+ { label: "application/json", type: "text" },
211
+ { label: "application/yaml", type: "text" },
212
+ { label: "text/yaml", type: "text" },
213
+ ],
214
+ prefer: [
215
+ { label: "respond-async", type: "text" },
216
+ { label: "return=minimal", type: "text" },
217
+ { label: "return=representation", type: "text" },
218
+ { label: "return=OperationOutcome", type: "text" },
219
+ ],
220
+ "x-debug": [{ label: "policy", type: "text" }],
221
+ "x-max-isolation-level": [
222
+ { label: "read-committed", type: "text" },
223
+ { label: "repeatable-read", type: "text" },
224
+ { label: "serializable", type: "text" },
225
+ ],
226
+ "cache-control": [
227
+ { label: "no-cache", type: "text" },
228
+ { label: "no-store", type: "text" },
229
+ { label: "max-age=0", type: "text" },
230
+ ],
231
+ authorization: [
232
+ { label: "Bearer ", type: "text" },
233
+ { label: "Basic ", type: "text" },
234
+ ],
235
+ };
236
+
237
+ const HTTP_METHODS: Completion[] = [
238
+ "GET",
239
+ "POST",
240
+ "PUT",
241
+ "PATCH",
242
+ "DELETE",
243
+ ].map((method) => ({
244
+ label: method,
245
+ type: "keyword" as const,
246
+ apply: (view: EditorView, _c: Completion, from: number, to: number) => {
247
+ const line = view.state.doc.lineAt(from);
248
+ const afterTo = line.text.slice(to - line.from);
249
+ // Skip whitespace after the method word to avoid double spaces
250
+ const wsMatch = afterTo.match(/^(\s*)/);
251
+ const actualTo = to + (wsMatch?.[1]?.length ?? 0);
252
+ const rest = line.text.slice(actualTo - line.from);
253
+ const insert = rest.startsWith("/") ? `${method} ` : `${method} /`;
254
+ view.dispatch({
255
+ changes: { from, to: actualTo, insert },
256
+ selection: { anchor: from + insert.length },
257
+ });
258
+ },
259
+ }));
260
+
261
+ function httpCompletionSource(
262
+ context: CompletionContext,
263
+ ): CompletionResult | null {
264
+ const { state, pos } = context;
265
+ const tree = syntaxTree(state);
266
+ const node = tree.resolveInner(pos, -1);
267
+
268
+ const line = state.doc.lineAt(pos);
269
+ const beforeCursor = line.text.slice(0, pos - line.from);
270
+
271
+ // Request method completion — first line, typing method
272
+ if (
273
+ node.type.id === HttpRequestMethod ||
274
+ (line.number === 1 && /^\s*[a-zA-Z]*$/.test(beforeCursor))
275
+ ) {
276
+ const word = context.matchBefore(/[a-zA-Z]*/);
277
+ return {
278
+ from: word?.from ?? pos,
279
+ options: HTTP_METHODS,
280
+ validFor: /^[a-zA-Z]*$/i,
281
+ };
282
+ }
283
+
284
+ // Header value completion — after colon
285
+ const colonIdx = beforeCursor.indexOf(":");
286
+ if (colonIdx >= 0) {
287
+ const inHeaderValue = node.type.id === HttpHeaderValue;
288
+ const parentIsHeaders = node.parent?.type.id === HttpHeaders;
289
+ if (
290
+ !inHeaderValue &&
291
+ !parentIsHeaders &&
292
+ node.parent?.parent?.type.id !== HttpHeaders
293
+ )
294
+ return null;
295
+
296
+ const headerName = beforeCursor.slice(0, colonIdx).trim().toLowerCase();
297
+ const values = HEADER_VALUES[headerName];
298
+ if (!values) return null;
299
+
300
+ const word = context.matchBefore(/\S*/);
301
+ return {
302
+ from: word?.from ?? pos,
303
+ options: values,
304
+ validFor: /^\S*$/,
305
+ };
306
+ }
307
+
308
+ // Header name completion — before colon
309
+ const inHeaderName = node.type.id === HttpHeaderName;
310
+ const inHeaders = node.type.id === HttpHeaders;
311
+ const parentIsHeaders = node.parent?.type.id === HttpHeaders;
312
+
313
+ if (!inHeaderName && !inHeaders && !parentIsHeaders) return null;
314
+
315
+ const word = context.matchBefore(/[\w-]+/);
316
+ if (!word) return null;
317
+ return {
318
+ from: word.from,
319
+ options: COMMON_HEADERS,
320
+ validFor: /^[\w-]+$/,
321
+ };
322
+ }
323
+
324
+ export interface UrlSuggestion {
325
+ label: string;
326
+ value: string;
327
+ type?: string;
328
+ description?: string;
329
+ expression?: string;
330
+ }
331
+
332
+ export type GetUrlSuggestions = (
333
+ path: string,
334
+ method: string,
335
+ ) => UrlSuggestion[] | Promise<UrlSuggestion[]>;
336
+
337
+ function httpUrlCompletionSource(
338
+ getUrlSuggestions: GetUrlSuggestions,
339
+ ): (context: CompletionContext) => Promise<CompletionResult | null> {
340
+ return async (
341
+ context: CompletionContext,
342
+ ): Promise<CompletionResult | null> => {
343
+ const { state, pos } = context;
344
+ const tree = syntaxTree(state);
345
+ const node = tree.resolveInner(pos, -1);
346
+
347
+ if (node.type.id !== HttpRequestPath) return null;
348
+
349
+ const line = state.doc.lineAt(pos);
350
+ const lineText = line.text;
351
+ // Extract method from the beginning of the line
352
+ const methodMatch = lineText.match(/^\s*(\w+)\s+/);
353
+ if (!methodMatch?.[1]) return null;
354
+ const method = methodMatch[1].toUpperCase();
355
+
356
+ const pathStart = line.from + methodMatch[0].length;
357
+ const currentPath = state.sliceDoc(pathStart, pos);
358
+
359
+ const suggestions = await getUrlSuggestions(currentPath, method);
360
+ if (suggestions.length === 0) return null;
361
+
362
+ const options: Completion[] = suggestions.map((s) => {
363
+ const c: Completion = {
364
+ label: s.label,
365
+ type:
366
+ s.type === "resource-type"
367
+ ? "type"
368
+ : s.type === "operation"
369
+ ? "function"
370
+ : s.type === "search-param"
371
+ ? "search-param"
372
+ : "text",
373
+ };
374
+ if (s.type === "search-param") c.apply = `${s.label}=`;
375
+ else if (s.type === "path" && s.label === "fhir") c.apply = `${s.label}/`;
376
+ if (s.description) c.detail = s.description.toUpperCase();
377
+ if (s.expression) c.info = s.expression;
378
+ return c;
379
+ });
380
+
381
+ // Match from the last segment separator (/ or ? or &)
382
+ const hasQuery = currentPath.includes("?");
383
+ let from: number;
384
+ if (hasQuery) {
385
+ const lastSep = Math.max(
386
+ currentPath.lastIndexOf("?"),
387
+ currentPath.lastIndexOf("&"),
388
+ );
389
+ from = pathStart + lastSep + 1;
390
+ } else {
391
+ const lastSlash = currentPath.lastIndexOf("/");
392
+ from = pathStart + lastSlash + 1;
393
+ }
394
+
395
+ return {
396
+ from,
397
+ options,
398
+ validFor: /^[^\s&=]*/,
399
+ };
400
+ };
401
+ }
402
+
403
+ function http(
404
+ bodyLanguages: (contentType: string) => Language | null,
405
+ getUrlSuggestions?: GetUrlSuggestions,
406
+ ) {
82
407
  const parser = makeParser(bodyLanguages);
83
408
  const language = LRLanguage.define({ parser: parser });
84
- return new LanguageSupport(language, []);
409
+ const extensions = [
410
+ httpMethodHighlighter,
411
+ httpMethodTheme,
412
+ language.data.of({ autocomplete: httpCompletionSource }),
413
+ ];
414
+ if (getUrlSuggestions) {
415
+ extensions.push(
416
+ language.data.of({
417
+ autocomplete: httpUrlCompletionSource(getUrlSuggestions),
418
+ }),
419
+ );
420
+ }
421
+ return new LanguageSupport(language, extensions);
85
422
  }
86
423
 
87
424
  export { http };