@checkstack/ui 0.0.2

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 (68) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/bunfig.toml +2 -0
  3. package/package.json +40 -0
  4. package/src/components/Accordion.tsx +55 -0
  5. package/src/components/Alert.tsx +90 -0
  6. package/src/components/AmbientBackground.tsx +105 -0
  7. package/src/components/AnimatedCounter.tsx +54 -0
  8. package/src/components/BackLink.tsx +56 -0
  9. package/src/components/Badge.tsx +38 -0
  10. package/src/components/Button.tsx +55 -0
  11. package/src/components/Card.tsx +56 -0
  12. package/src/components/Checkbox.tsx +46 -0
  13. package/src/components/ColorPicker.tsx +69 -0
  14. package/src/components/CommandPalette.tsx +74 -0
  15. package/src/components/ConfirmationModal.tsx +134 -0
  16. package/src/components/DateRangeFilter.tsx +128 -0
  17. package/src/components/DateTimePicker.tsx +65 -0
  18. package/src/components/Dialog.tsx +134 -0
  19. package/src/components/DropdownMenu.tsx +96 -0
  20. package/src/components/DynamicForm/DynamicForm.tsx +126 -0
  21. package/src/components/DynamicForm/DynamicOptionsField.tsx +220 -0
  22. package/src/components/DynamicForm/FormField.tsx +690 -0
  23. package/src/components/DynamicForm/JsonField.tsx +98 -0
  24. package/src/components/DynamicForm/index.ts +11 -0
  25. package/src/components/DynamicForm/types.ts +95 -0
  26. package/src/components/DynamicForm/utils.ts +39 -0
  27. package/src/components/DynamicIcon.tsx +45 -0
  28. package/src/components/EditableText.tsx +141 -0
  29. package/src/components/EmptyState.tsx +32 -0
  30. package/src/components/HealthBadge.tsx +57 -0
  31. package/src/components/InfoBanner.tsx +97 -0
  32. package/src/components/Input.tsx +20 -0
  33. package/src/components/Label.tsx +17 -0
  34. package/src/components/LoadingSpinner.tsx +29 -0
  35. package/src/components/Markdown.tsx +206 -0
  36. package/src/components/NavItem.tsx +112 -0
  37. package/src/components/Page.tsx +58 -0
  38. package/src/components/PageLayout.tsx +83 -0
  39. package/src/components/PaginatedList.tsx +135 -0
  40. package/src/components/Pagination.tsx +195 -0
  41. package/src/components/PermissionDenied.tsx +31 -0
  42. package/src/components/PermissionGate.tsx +97 -0
  43. package/src/components/PluginConfigForm.tsx +91 -0
  44. package/src/components/SectionHeader.tsx +30 -0
  45. package/src/components/Select.tsx +157 -0
  46. package/src/components/StatusCard.tsx +78 -0
  47. package/src/components/StatusUpdateTimeline.tsx +222 -0
  48. package/src/components/StrategyConfigCard.tsx +333 -0
  49. package/src/components/SubscribeButton.tsx +96 -0
  50. package/src/components/Table.tsx +119 -0
  51. package/src/components/Tabs.tsx +141 -0
  52. package/src/components/TemplateEditor.test.ts +156 -0
  53. package/src/components/TemplateEditor.tsx +435 -0
  54. package/src/components/TerminalFeed.tsx +152 -0
  55. package/src/components/Textarea.tsx +22 -0
  56. package/src/components/ThemeProvider.tsx +76 -0
  57. package/src/components/Toast.tsx +118 -0
  58. package/src/components/ToastProvider.tsx +126 -0
  59. package/src/components/Toggle.tsx +47 -0
  60. package/src/components/Tooltip.tsx +20 -0
  61. package/src/components/UserMenu.tsx +79 -0
  62. package/src/hooks/usePagination.e2e.ts +275 -0
  63. package/src/hooks/usePagination.ts +231 -0
  64. package/src/index.ts +53 -0
  65. package/src/themes.css +204 -0
  66. package/src/utils/strip-markdown.ts +44 -0
  67. package/src/utils.ts +8 -0
  68. package/tsconfig.json +6 -0
@@ -0,0 +1,119 @@
1
+ import * as React from "react";
2
+ import { cn } from "../utils";
3
+
4
+ const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div className="relative w-full overflow-auto">
9
+ <table
10
+ ref={ref}
11
+ className={cn("w-full caption-bottom text-sm", className)}
12
+ {...props}
13
+ />
14
+ </div>
15
+ ));
16
+ Table.displayName = "Table";
17
+
18
+ const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
23
+ ));
24
+ TableHeader.displayName = "TableHeader";
25
+
26
+ const TableBody = React.forwardRef<
27
+ HTMLTableSectionElement,
28
+ React.HTMLAttributes<HTMLTableSectionElement>
29
+ >(({ className, ...props }, ref) => (
30
+ <tbody
31
+ ref={ref}
32
+ className={cn("[&_tr:last-child]:border-0", className)}
33
+ {...props}
34
+ />
35
+ ));
36
+ TableBody.displayName = "TableBody";
37
+
38
+ const TableFooter = React.forwardRef<
39
+ HTMLTableSectionElement,
40
+ React.HTMLAttributes<HTMLTableSectionElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <tfoot
43
+ ref={ref}
44
+ className={cn(
45
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ ));
51
+ TableFooter.displayName = "TableFooter";
52
+
53
+ const TableRow = React.forwardRef<
54
+ HTMLTableRowElement,
55
+ React.HTMLAttributes<HTMLTableRowElement>
56
+ >(({ className, ...props }, ref) => (
57
+ <tr
58
+ ref={ref}
59
+ className={cn(
60
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ ));
66
+ TableRow.displayName = "TableRow";
67
+
68
+ const TableHead = React.forwardRef<
69
+ HTMLTableCellElement,
70
+ React.ThHTMLAttributes<HTMLTableCellElement>
71
+ >(({ className, ...props }, ref) => (
72
+ <th
73
+ ref={ref}
74
+ className={cn(
75
+ "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [button&]:text-muted-foreground",
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+ ));
81
+ TableHead.displayName = "TableHead";
82
+
83
+ const TableCell = React.forwardRef<
84
+ HTMLTableCellElement,
85
+ React.TdHTMLAttributes<HTMLTableCellElement>
86
+ >(({ className, ...props }, ref) => (
87
+ <td
88
+ ref={ref}
89
+ className={cn(
90
+ "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ ));
96
+ TableCell.displayName = "TableCell";
97
+
98
+ const TableCaption = React.forwardRef<
99
+ HTMLTableCaptionElement,
100
+ React.HTMLAttributes<HTMLTableCaptionElement>
101
+ >(({ className, ...props }, ref) => (
102
+ <caption
103
+ ref={ref}
104
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
105
+ {...props}
106
+ />
107
+ ));
108
+ TableCaption.displayName = "TableCaption";
109
+
110
+ export {
111
+ Table,
112
+ TableHeader,
113
+ TableBody,
114
+ TableFooter,
115
+ TableHead,
116
+ TableRow,
117
+ TableCell,
118
+ TableCaption,
119
+ };
@@ -0,0 +1,141 @@
1
+ import React, { useRef } from "react";
2
+
3
+ export interface TabItem {
4
+ id: string;
5
+ label: string;
6
+ icon?: React.ReactNode;
7
+ }
8
+
9
+ export interface TabsProps {
10
+ items: TabItem[];
11
+ activeTab: string;
12
+ onTabChange: (tabId: string) => void;
13
+ className?: string;
14
+ }
15
+
16
+ export const Tabs: React.FC<TabsProps> = ({
17
+ items,
18
+ activeTab,
19
+ onTabChange,
20
+ className = "",
21
+ }) => {
22
+ const tabRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
23
+
24
+ const handleKeyDown = (event: React.KeyboardEvent, currentIndex: number) => {
25
+ let newIndex = currentIndex;
26
+
27
+ switch (event.key) {
28
+ case "ArrowLeft": {
29
+ newIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
30
+ event.preventDefault();
31
+ break;
32
+ }
33
+ case "ArrowRight": {
34
+ newIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
35
+ event.preventDefault();
36
+ break;
37
+ }
38
+ case "Home": {
39
+ newIndex = 0;
40
+ event.preventDefault();
41
+ break;
42
+ }
43
+ case "End": {
44
+ newIndex = items.length - 1;
45
+ event.preventDefault();
46
+ break;
47
+ }
48
+ default: {
49
+ // No action needed for other keys
50
+ break;
51
+ }
52
+ }
53
+
54
+ if (newIndex !== currentIndex) {
55
+ onTabChange(items[newIndex].id);
56
+ tabRefs.current.get(items[newIndex].id)?.focus();
57
+ }
58
+ };
59
+
60
+ return (
61
+ <div className={`relative ${className}`}>
62
+ <div
63
+ className="flex flex-col md:flex-row gap-1 p-1 bg-muted/30 rounded-lg"
64
+ role="tablist"
65
+ aria-label="Tabs"
66
+ >
67
+ {items.map((item, index) => {
68
+ const isActive = activeTab === item.id;
69
+ return (
70
+ <button
71
+ key={item.id}
72
+ ref={(el) => {
73
+ if (el) {
74
+ tabRefs.current.set(item.id, el);
75
+ } else {
76
+ tabRefs.current.delete(item.id);
77
+ }
78
+ }}
79
+ onClick={() => onTabChange(item.id)}
80
+ onKeyDown={(e) => handleKeyDown(e, index)}
81
+ className={`
82
+ relative px-4 py-2.5 flex items-center justify-center md:justify-start gap-2 rounded-md
83
+ text-sm font-medium transition-all duration-200
84
+ focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-0
85
+ ${
86
+ isActive
87
+ ? "bg-primary text-primary-foreground shadow-md shadow-primary/30"
88
+ : "text-muted-foreground hover:text-foreground hover:bg-background/50"
89
+ }
90
+ `}
91
+ role="tab"
92
+ aria-selected={isActive}
93
+ aria-controls={`tabpanel-${item.id}`}
94
+ tabIndex={isActive ? 0 : -1}
95
+ >
96
+ {item.icon && (
97
+ <span
98
+ className={`transition-all duration-200 ${
99
+ isActive ? "scale-110" : ""
100
+ }`}
101
+ >
102
+ {item.icon}
103
+ </span>
104
+ )}
105
+ <span>{item.label}</span>
106
+ </button>
107
+ );
108
+ })}
109
+ </div>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export interface TabPanelProps {
115
+ id: string;
116
+ activeTab: string;
117
+ children: React.ReactNode;
118
+ className?: string;
119
+ }
120
+
121
+ export const TabPanel: React.FC<TabPanelProps> = ({
122
+ id,
123
+ activeTab,
124
+ children,
125
+ className = "",
126
+ }) => {
127
+ const isActive = activeTab === id;
128
+
129
+ return isActive ? (
130
+ <div
131
+ id={`tabpanel-${id}`}
132
+ role="tabpanel"
133
+ aria-labelledby={`tab-${id}`}
134
+ className={`animate-in fade-in-0 duration-200 ${className}`}
135
+ >
136
+ {children}
137
+ </div>
138
+ ) : (
139
+ <></>
140
+ );
141
+ };
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { detectTemplateContext } from "./TemplateEditor";
3
+
4
+ describe("detectTemplateContext", () => {
5
+ describe("mustache syntax ({{}})", () => {
6
+ it("should detect context when cursor is immediately after {{", () => {
7
+ const result = detectTemplateContext("Hello {{", 8, "mustache");
8
+ expect(result).toEqual({
9
+ isInTemplate: true,
10
+ query: "",
11
+ startPos: 6,
12
+ });
13
+ });
14
+
15
+ it("should detect context with partial query", () => {
16
+ const result = detectTemplateContext("Hello {{pay", 11, "mustache");
17
+ expect(result).toEqual({
18
+ isInTemplate: true,
19
+ query: "pay",
20
+ startPos: 6,
21
+ });
22
+ });
23
+
24
+ it("should detect context with full path being typed", () => {
25
+ const result = detectTemplateContext(
26
+ "Hello {{payload.title",
27
+ 21,
28
+ "mustache"
29
+ );
30
+ expect(result).toEqual({
31
+ isInTemplate: true,
32
+ query: "payload.title",
33
+ startPos: 6,
34
+ });
35
+ });
36
+
37
+ it("should not detect context when template is closed", () => {
38
+ const result = detectTemplateContext(
39
+ "Hello {{name}} world",
40
+ 20,
41
+ "mustache"
42
+ );
43
+ expect(result).toEqual({
44
+ isInTemplate: false,
45
+ query: "",
46
+ startPos: -1,
47
+ });
48
+ });
49
+
50
+ it("should not detect context when cursor is before {{", () => {
51
+ const result = detectTemplateContext("Hello {{name}}", 3, "mustache");
52
+ expect(result).toEqual({
53
+ isInTemplate: false,
54
+ query: "",
55
+ startPos: -1,
56
+ });
57
+ });
58
+
59
+ it("should detect context in second template when first is closed", () => {
60
+ const result = detectTemplateContext(
61
+ "{{first}} and {{second",
62
+ 22,
63
+ "mustache"
64
+ );
65
+ expect(result).toEqual({
66
+ isInTemplate: true,
67
+ query: "second",
68
+ startPos: 14,
69
+ });
70
+ });
71
+
72
+ it("should not detect context when query contains newline", () => {
73
+ const result = detectTemplateContext("Hello {{\nworld", 14, "mustache");
74
+ expect(result).toEqual({
75
+ isInTemplate: false,
76
+ query: "",
77
+ startPos: -1,
78
+ });
79
+ });
80
+
81
+ it("should handle empty string", () => {
82
+ const result = detectTemplateContext("", 0, "mustache");
83
+ expect(result).toEqual({
84
+ isInTemplate: false,
85
+ query: "",
86
+ startPos: -1,
87
+ });
88
+ });
89
+
90
+ it("should handle text without any templates", () => {
91
+ const result = detectTemplateContext("Hello world", 11, "mustache");
92
+ expect(result).toEqual({
93
+ isInTemplate: false,
94
+ query: "",
95
+ startPos: -1,
96
+ });
97
+ });
98
+ });
99
+
100
+ describe("dollar syntax (${})", () => {
101
+ it("should detect context when cursor is immediately after ${", () => {
102
+ const result = detectTemplateContext("Hello ${", 8, "dollar");
103
+ expect(result).toEqual({
104
+ isInTemplate: true,
105
+ query: "",
106
+ startPos: 6,
107
+ });
108
+ });
109
+
110
+ it("should detect context with partial query", () => {
111
+ const result = detectTemplateContext("Hello ${user", 12, "dollar");
112
+ expect(result).toEqual({
113
+ isInTemplate: true,
114
+ query: "user",
115
+ startPos: 6,
116
+ });
117
+ });
118
+
119
+ it("should not detect context when template is closed", () => {
120
+ const result = detectTemplateContext("Hello ${name} world", 19, "dollar");
121
+ expect(result).toEqual({
122
+ isInTemplate: false,
123
+ query: "",
124
+ startPos: -1,
125
+ });
126
+ });
127
+ });
128
+
129
+ describe("edge cases", () => {
130
+ it("should handle nested braces correctly", () => {
131
+ const result = detectTemplateContext("{{outer.{{inner", 15, "mustache");
132
+ // Should find the last {{ which is at position 8
133
+ expect(result.isInTemplate).toBe(true);
134
+ expect(result.startPos).toBe(8);
135
+ expect(result.query).toBe("inner");
136
+ });
137
+
138
+ it("should handle cursor at start of template", () => {
139
+ const result = detectTemplateContext("{{", 2, "mustache");
140
+ expect(result).toEqual({
141
+ isInTemplate: true,
142
+ query: "",
143
+ startPos: 0,
144
+ });
145
+ });
146
+
147
+ it("should detect context after multiple closed templates", () => {
148
+ const result = detectTemplateContext("{{a}} {{b}} {{c", 15, "mustache");
149
+ expect(result).toEqual({
150
+ isInTemplate: true,
151
+ query: "c",
152
+ startPos: 12,
153
+ });
154
+ });
155
+ });
156
+ });