@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
@@ -0,0 +1,79 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+ import { DatePickerInput } from "./date-picker-input";
4
+
5
+ function DatePickerInputWrapper(props: {
6
+ placeholder?: string;
7
+ disabled?: boolean;
8
+ }) {
9
+ const [value, setValue] = useState("");
10
+ return (
11
+ <div className="w-48">
12
+ <DatePickerInput value={value} onChange={setValue} {...props} />
13
+ </div>
14
+ );
15
+ }
16
+
17
+ function DatePickerInputPrefilledWrapper() {
18
+ const [value, setValue] = useState("15.06.2025");
19
+ return (
20
+ <div className="w-48">
21
+ <DatePickerInput value={value} onChange={setValue} />
22
+ </div>
23
+ );
24
+ }
25
+
26
+ function DatePickerRangeWrapper() {
27
+ const [from, setFrom] = useState("");
28
+ const [to, setTo] = useState("");
29
+ return (
30
+ <div className="flex items-center gap-2">
31
+ <DatePickerInput
32
+ value={from}
33
+ onChange={setFrom}
34
+ className="w-40"
35
+ placeholder="From"
36
+ />
37
+ <span className="text-text-secondary">—</span>
38
+ <DatePickerInput
39
+ value={to}
40
+ onChange={setTo}
41
+ className="w-40"
42
+ placeholder="To"
43
+ />
44
+ </div>
45
+ );
46
+ }
47
+
48
+ const meta = {
49
+ title: "Component/DatePickerInput",
50
+ component: DatePickerInputWrapper,
51
+ parameters: {
52
+ layout: "centered",
53
+ },
54
+ } satisfies Meta<typeof DatePickerInputWrapper>;
55
+
56
+ export default meta;
57
+ type Story = StoryObj<typeof meta>;
58
+
59
+ export const Default: Story = {};
60
+
61
+ export const WithPlaceholder: Story = {
62
+ args: {
63
+ placeholder: "Select a date...",
64
+ },
65
+ };
66
+
67
+ export const Disabled: Story = {
68
+ args: {
69
+ disabled: true,
70
+ },
71
+ };
72
+
73
+ export const Prefilled: StoryObj = {
74
+ render: () => <DatePickerInputPrefilledWrapper />,
75
+ };
76
+
77
+ export const DateRange: StoryObj = {
78
+ render: () => <DatePickerRangeWrapper />,
79
+ };
@@ -0,0 +1,104 @@
1
+ import { CalendarIcon } from "lucide-react";
2
+ import * as React from "react";
3
+ import { Button } from "#shadcn/components/ui/button";
4
+ import { Calendar } from "#shadcn/components/ui/calendar";
5
+ import { Input } from "#shadcn/components/ui/input";
6
+ import {
7
+ Popover,
8
+ PopoverContent,
9
+ PopoverTrigger,
10
+ } from "#shadcn/components/ui/popover";
11
+ import { cn } from "#shadcn/lib/utils";
12
+
13
+ function formatDate(date: Date | undefined): string {
14
+ if (!date) return "";
15
+ const d = date.getDate().toString().padStart(2, "0");
16
+ const m = (date.getMonth() + 1).toString().padStart(2, "0");
17
+ const y = date.getFullYear();
18
+ return `${d}.${m}.${y}`;
19
+ }
20
+
21
+ function parseDate(value: string): Date | undefined {
22
+ if (!value) return undefined;
23
+ const parts = value.split(".");
24
+ if (parts.length === 3) {
25
+ const [d, m, y] = parts;
26
+ const date = new Date(`${y}-${m}-${d}`);
27
+ if (!Number.isNaN(date.getTime())) return date;
28
+ }
29
+ const date = new Date(value);
30
+ if (!Number.isNaN(date.getTime())) return date;
31
+ return undefined;
32
+ }
33
+
34
+ interface DatePickerInputProps {
35
+ value: string;
36
+ onChange: (value: string) => void;
37
+ placeholder?: string;
38
+ className?: string;
39
+ disabled?: boolean;
40
+ }
41
+
42
+ function DatePickerInput({
43
+ value,
44
+ onChange,
45
+ placeholder = "dd.mm.yyyy",
46
+ className,
47
+ disabled,
48
+ }: DatePickerInputProps) {
49
+ const [open, setOpen] = React.useState(false);
50
+ const selectedDate = parseDate(value);
51
+ const [month, setMonth] = React.useState<Date>(selectedDate ?? new Date());
52
+
53
+ return (
54
+ <div className={cn("relative", className)}>
55
+ <Input
56
+ type="text"
57
+ placeholder={placeholder}
58
+ value={value}
59
+ disabled={disabled}
60
+ onChange={(e) => onChange(e.target.value)}
61
+ onKeyDown={(e) => {
62
+ if (e.key === "ArrowDown") {
63
+ e.preventDefault();
64
+ setOpen(true);
65
+ }
66
+ }}
67
+ rightSlot={
68
+ <Popover open={open} onOpenChange={setOpen}>
69
+ <PopoverTrigger asChild>
70
+ <Button
71
+ variant="ghost"
72
+ size="small"
73
+ className="p-0 size-5"
74
+ aria-label="Select date"
75
+ disabled={disabled}
76
+ onClick={(e) => {
77
+ e.preventDefault();
78
+ setOpen((o) => !o);
79
+ }}
80
+ >
81
+ <CalendarIcon className="size-3.5" />
82
+ </Button>
83
+ </PopoverTrigger>
84
+ <PopoverContent className="w-auto p-0" align="end" sideOffset={10}>
85
+ <Calendar
86
+ mode="single"
87
+ selected={selectedDate}
88
+ month={month}
89
+ onMonthChange={(m) => setMonth(m)}
90
+ onSelect={(date) => {
91
+ onChange(formatDate(date));
92
+ if (date) setMonth(date);
93
+ setOpen(false);
94
+ }}
95
+ />
96
+ </PopoverContent>
97
+ </Popover>
98
+ }
99
+ />
100
+ </div>
101
+ );
102
+ }
103
+
104
+ export { DatePickerInput, type DatePickerInputProps };
package/src/index.tsx CHANGED
@@ -4,6 +4,7 @@ export * from "./components/button-dropdown";
4
4
  export * from "./components/code-editor";
5
5
  export * from "./components/copy-icon";
6
6
  export * from "./components/data-table";
7
+ export * from "./components/date-picker-input";
7
8
  export * from "./components/fhir-structure-view";
8
9
  export * from "./components/icon-button";
9
10
  export * from "./components/operation-outcome-view";
@@ -118,7 +118,7 @@ function ChartTooltipContent({
118
118
  color,
119
119
  nameKey,
120
120
  labelKey,
121
- }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
121
+ }: RechartsPrimitive.TooltipContentProps &
122
122
  React.ComponentProps<"div"> & {
123
123
  hideLabel?: boolean;
124
124
  hideIndicator?: boolean;
@@ -186,7 +186,7 @@ function ChartTooltipContent({
186
186
 
187
187
  return (
188
188
  <div
189
- key={item.dataKey}
189
+ key={String(item.dataKey ?? index)}
190
190
  className={cn(
191
191
  "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
192
192
  indicator === "dot" && "items-center",
@@ -257,7 +257,10 @@ function ChartLegendContent({
257
257
  verticalAlign = "bottom",
258
258
  nameKey,
259
259
  }: React.ComponentProps<"div"> &
260
- Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
260
+ Pick<
261
+ RechartsPrimitive.DefaultLegendContentProps,
262
+ "payload" | "verticalAlign"
263
+ > & {
261
264
  hideIcon?: boolean;
262
265
  nameKey?: string;
263
266
  }) {
@@ -15,7 +15,7 @@ type Story = StoryObj<typeof meta>;
15
15
  export const Demo = {
16
16
  render: () => (
17
17
  <ResizablePanelGroup
18
- direction="horizontal"
18
+ orientation="horizontal"
19
19
  className="max-w-md rounded-lg border md:min-w-[450px]"
20
20
  >
21
21
  <ResizablePanel defaultSize={50}>
@@ -25,7 +25,7 @@ export const Demo = {
25
25
  </ResizablePanel>
26
26
  <ResizableHandle />
27
27
  <ResizablePanel defaultSize={50}>
28
- <ResizablePanelGroup direction="vertical">
28
+ <ResizablePanelGroup orientation="vertical">
29
29
  <ResizablePanel defaultSize={25}>
30
30
  <div className="flex h-full items-center justify-center p-6">
31
31
  <span className="font-semibold">Two</span>
@@ -1,6 +1,6 @@
1
1
  import { Controls, Primary, Title } from "@storybook/addon-docs/blocks";
2
2
  import type { Meta, StoryObj } from "@storybook/react-vite";
3
- import { action } from "storybook/internal/actions";
3
+ import { action } from "storybook/actions";
4
4
  import {
5
5
  Tabs,
6
6
  TabsAddButton,
@@ -443,17 +443,175 @@ function TabScrollRightButton({
443
443
  );
444
444
  }
445
445
 
446
+ type DragState = {
447
+ index: number;
448
+ startX: number;
449
+ offsetX: number;
450
+ currentIndex: number;
451
+ widths: number[];
452
+ lefts: number[];
453
+ };
454
+
455
+ const DRAG_THRESHOLD = 5;
456
+
457
+ function useTabReorder(
458
+ onReorder: ((fromIndex: number, toIndex: number) => void) | undefined,
459
+ ) {
460
+ const [drag, setDrag] = React.useState<DragState | null>(null);
461
+ const dragRef = React.useRef<DragState | null>(null);
462
+ const pendingRef = React.useRef(false);
463
+ const itemsRef = React.useRef<(HTMLDivElement | null)[]>([]);
464
+
465
+ const handlePointerDown = React.useCallback(
466
+ (e: React.PointerEvent, index: number) => {
467
+ if (!onReorder || e.button !== 0) return;
468
+ const items = itemsRef.current;
469
+ const widths = items.map((el) => el?.offsetWidth ?? 0);
470
+ const lefts: number[] = [];
471
+ let acc = 0;
472
+ for (const w of widths) {
473
+ lefts.push(acc);
474
+ acc += w;
475
+ }
476
+ dragRef.current = {
477
+ index,
478
+ startX: e.clientX,
479
+ offsetX: 0,
480
+ currentIndex: index,
481
+ widths,
482
+ lefts,
483
+ };
484
+ pendingRef.current = true;
485
+ (e.target as HTMLElement).setPointerCapture(e.pointerId);
486
+ },
487
+ [onReorder],
488
+ );
489
+
490
+ const handlePointerMove = React.useCallback((e: React.PointerEvent) => {
491
+ const d = dragRef.current;
492
+ if (!d) return;
493
+ const dx = e.clientX - d.startX;
494
+ if (pendingRef.current) {
495
+ if (Math.abs(dx) < DRAG_THRESHOLD) return;
496
+ pendingRef.current = false;
497
+ }
498
+ const draggedLeft = d.lefts[d.index] ?? 0;
499
+ const draggedWidth = d.widths[d.index] ?? 0;
500
+ const draggedRightEdge = draggedLeft + draggedWidth + dx;
501
+ const draggedLeftEdge = draggedLeft + dx;
502
+ let newIndex = d.index;
503
+ const TRIGGER_RATIO = 0.3;
504
+ for (let i = 0; i < d.lefts.length; i++) {
505
+ if (i === d.index) continue;
506
+ const left = d.lefts[i] ?? 0;
507
+ const width = d.widths[i] ?? 0;
508
+ if (i > d.index) {
509
+ // Dragging right: trigger when right edge enters 30% of target
510
+ if (draggedRightEdge > left + width * TRIGGER_RATIO) newIndex = i;
511
+ } else {
512
+ // Dragging left: trigger when left edge enters 30% from right
513
+ if (draggedLeftEdge < left + width * (1 - TRIGGER_RATIO))
514
+ newIndex = Math.min(newIndex, i);
515
+ }
516
+ }
517
+ const next: DragState = { ...d, offsetX: dx, currentIndex: newIndex };
518
+ dragRef.current = next;
519
+ setDrag(next);
520
+ }, []);
521
+
522
+ const handlePointerUp = React.useCallback(() => {
523
+ const d = dragRef.current;
524
+ if (d && !pendingRef.current && d.index !== d.currentIndex) {
525
+ onReorder?.(d.index, d.currentIndex);
526
+ }
527
+ dragRef.current = null;
528
+ pendingRef.current = false;
529
+ setDrag(null);
530
+ }, [onReorder]);
531
+
532
+ const getTransform = React.useCallback(
533
+ (index: number): React.CSSProperties => {
534
+ if (!drag || pendingRef.current) return {};
535
+ if (index === drag.index) {
536
+ return {
537
+ transform: `translateX(${drag.offsetX}px)`,
538
+ zIndex: 10,
539
+ position: "relative",
540
+ background: "var(--color-bg-primary)",
541
+ borderLeft: "1px solid var(--color-border-default)",
542
+ };
543
+ }
544
+ const from = drag.index;
545
+ const to = drag.currentIndex;
546
+ const draggedWidth = drag.widths[from] ?? 0;
547
+ if (from < to && index > from && index <= to) {
548
+ return {
549
+ transform: `translateX(${-draggedWidth}px)`,
550
+ transition: "transform 200ms ease",
551
+ };
552
+ }
553
+ if (from > to && index >= to && index < from) {
554
+ return {
555
+ transform: `translateX(${draggedWidth}px)`,
556
+ transition: "transform 200ms ease",
557
+ };
558
+ }
559
+ return { transition: "transform 200ms ease" };
560
+ },
561
+ [drag],
562
+ );
563
+
564
+ return {
565
+ drag,
566
+ itemsRef,
567
+ handlePointerDown,
568
+ handlePointerMove,
569
+ handlePointerUp,
570
+ getTransform,
571
+ };
572
+ }
573
+
446
574
  function TabsBrowserList({
447
575
  className,
448
576
  children,
577
+ onReorder,
449
578
  ...props
450
- }: React.ComponentProps<typeof TabsPrimitive.List>) {
579
+ }: React.ComponentProps<typeof TabsPrimitive.List> & {
580
+ onReorder?: (fromIndex: number, toIndex: number) => void;
581
+ }) {
451
582
  const tabsListRef = React.useRef<HTMLDivElement | null>(null);
452
583
 
453
584
  const [showScrollButtons, setShowScrollButtons] = React.useState(false);
454
585
  const [canScrollLeft, setCanScrollLeft] = React.useState(false);
455
586
  const [canScrollRight, setCanScrollRight] = React.useState(false);
456
587
 
588
+ const {
589
+ drag,
590
+ itemsRef,
591
+ handlePointerDown,
592
+ handlePointerMove,
593
+ handlePointerUp,
594
+ getTransform,
595
+ } = useTabReorder(onReorder);
596
+
597
+ const wrappedChildren =
598
+ onReorder && React.Children.count(children) > 1
599
+ ? React.Children.map(children, (child, index) => (
600
+ <div
601
+ ref={(el) => {
602
+ itemsRef.current[index] = el;
603
+ }}
604
+ style={getTransform(index)}
605
+ onPointerDown={(e) => handlePointerDown(e, index)}
606
+ onPointerMove={handlePointerMove}
607
+ onPointerUp={handlePointerUp}
608
+ className={cn(drag?.index === index && "cursor-grabbing")}
609
+ >
610
+ {child}
611
+ </div>
612
+ ))
613
+ : children;
614
+
457
615
  return (
458
616
  <React.Fragment>
459
617
  {showScrollButtons && (
@@ -506,7 +664,7 @@ function TabsBrowserList({
506
664
  {...props}
507
665
  ref={tabsListRef}
508
666
  >
509
- {children}
667
+ {wrappedChildren}
510
668
  </TabsList>
511
669
 
512
670
  {showScrollButtons && (
@@ -46,7 +46,7 @@ body {
46
46
  font-size: var(--font-size-xs);
47
47
  font-family: var(--font-family-sans);
48
48
  font-weight: var(--font-weight-normal);
49
- line-height: var(--font-leading-3);
49
+ line-height: var(--font-leading-4);
50
50
  }
51
51
  .typo-label-xs {
52
52
  font-size: var(--font-size-xs);
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=http.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.test.d.ts","sourceRoot":"","sources":["../../../../../../src/components/code-editor/http/grammar/http.test.ts"],"names":[],"mappings":""}