@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,231 @@
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
2
+
3
+ /**
4
+ * Pagination parameters passed to the fetch function
5
+ */
6
+ export interface PaginationParams {
7
+ limit: number;
8
+ offset: number;
9
+ }
10
+
11
+ /**
12
+ * Options for usePagination hook
13
+ */
14
+ export interface UsePaginationOptions<TResponse, TItem, TExtraParams = object> {
15
+ /**
16
+ * Fetch function that receives pagination + extra params and returns response
17
+ */
18
+ fetchFn: (params: PaginationParams & TExtraParams) => Promise<TResponse>;
19
+
20
+ /**
21
+ * Extract items array from the response
22
+ */
23
+ getItems: (response: TResponse) => TItem[];
24
+
25
+ /**
26
+ * Extract total count from the response
27
+ */
28
+ getTotal: (response: TResponse) => number;
29
+
30
+ /**
31
+ * Extra parameters to pass to fetchFn (merged with pagination params)
32
+ * Changes to this object will trigger a refetch
33
+ */
34
+ extraParams?: TExtraParams;
35
+
36
+ /**
37
+ * Initial page number (1-indexed)
38
+ * @default 1
39
+ */
40
+ defaultPage?: number;
41
+
42
+ /**
43
+ * Items per page
44
+ * @default 10
45
+ */
46
+ defaultLimit?: number;
47
+
48
+ /**
49
+ * Whether to fetch on mount
50
+ * @default true
51
+ */
52
+ fetchOnMount?: boolean;
53
+ }
54
+
55
+ /**
56
+ * Pagination state returned by usePagination hook
57
+ */
58
+ export interface PaginationState {
59
+ /** Current page (1-indexed) */
60
+ page: number;
61
+ /** Items per page */
62
+ limit: number;
63
+ /** Total number of items */
64
+ total: number;
65
+ /** Total number of pages */
66
+ totalPages: number;
67
+ /** Whether there is a next page */
68
+ hasNext: boolean;
69
+ /** Whether there is a previous page */
70
+ hasPrev: boolean;
71
+ /** Go to a specific page (1-indexed) */
72
+ setPage: (page: number) => void;
73
+ /** Change items per page (resets to page 1) */
74
+ setLimit: (limit: number) => void;
75
+ /** Go to next page */
76
+ nextPage: () => void;
77
+ /** Go to previous page */
78
+ prevPage: () => void;
79
+ /** Refresh current page */
80
+ refetch: () => void;
81
+ }
82
+
83
+ /**
84
+ * Return value of usePagination hook
85
+ */
86
+ export interface UsePaginationResult<TItem> {
87
+ /** Current page items */
88
+ items: TItem[];
89
+ /** Loading state */
90
+ loading: boolean;
91
+ /** Error if fetch failed */
92
+ error: Error | undefined;
93
+ /** Pagination controls and state */
94
+ pagination: PaginationState;
95
+ }
96
+
97
+ /**
98
+ * Hook for managing paginated data fetching with automatic state management.
99
+ *
100
+ * @example
101
+ * ```tsx
102
+ * const { items, loading, pagination } = usePagination({
103
+ * fetchFn: (p) => client.getNotifications(p),
104
+ * getItems: (r) => r.notifications,
105
+ * getTotal: (r) => r.total,
106
+ * extraParams: { unreadOnly: true },
107
+ * });
108
+ *
109
+ * return (
110
+ * <>
111
+ * {items.map(item => <Card key={item.id} {...item} />)}
112
+ * <Pagination {...pagination} />
113
+ * </>
114
+ * );
115
+ * ```
116
+ */
117
+ export function usePagination<TResponse, TItem, TExtraParams = object>({
118
+ fetchFn,
119
+ getItems,
120
+ getTotal,
121
+ extraParams,
122
+ defaultPage = 1,
123
+ defaultLimit = 10,
124
+ fetchOnMount = true,
125
+ }: UsePaginationOptions<
126
+ TResponse,
127
+ TItem,
128
+ TExtraParams
129
+ >): UsePaginationResult<TItem> {
130
+ const [items, setItems] = useState<TItem[]>([]);
131
+ const [loading, setLoading] = useState(fetchOnMount);
132
+ const [error, setError] = useState<Error>();
133
+ const [page, setPageState] = useState(defaultPage);
134
+ const [limit, setLimitState] = useState(defaultLimit);
135
+ const [total, setTotal] = useState(0);
136
+
137
+ // Use refs for callback functions to avoid re-creating fetchData when they change
138
+ // This is better DX - callers don't need to memoize their functions
139
+ const fetchFnRef = useRef(fetchFn);
140
+ const getItemsRef = useRef(getItems);
141
+ const getTotalRef = useRef(getTotal);
142
+ const extraParamsRef = useRef(extraParams);
143
+
144
+ // Update refs when functions change
145
+ fetchFnRef.current = fetchFn;
146
+ getItemsRef.current = getItems;
147
+ getTotalRef.current = getTotal;
148
+ extraParamsRef.current = extraParams;
149
+
150
+ // Memoize extraParams to detect changes
151
+ const extraParamsKey = useMemo(
152
+ () => JSON.stringify(extraParams ?? {}),
153
+ [extraParams]
154
+ );
155
+
156
+ const fetchData = useCallback(async () => {
157
+ setLoading(true);
158
+ setError(undefined);
159
+
160
+ try {
161
+ const offset = (page - 1) * limit;
162
+ const params = {
163
+ limit,
164
+ offset,
165
+ ...extraParamsRef.current,
166
+ } as PaginationParams & TExtraParams;
167
+
168
+ const response = await fetchFnRef.current(params);
169
+ setItems(getItemsRef.current(response));
170
+ setTotal(getTotalRef.current(response));
171
+ } catch (error_) {
172
+ setError(error_ instanceof Error ? error_ : new Error(String(error_)));
173
+ setItems([]);
174
+ } finally {
175
+ setLoading(false);
176
+ }
177
+ }, [page, limit, extraParamsKey]);
178
+
179
+ // Fetch on mount and when dependencies change
180
+ useEffect(() => {
181
+ if (fetchOnMount || page !== defaultPage || limit !== defaultLimit) {
182
+ void fetchData();
183
+ }
184
+ }, [fetchData, fetchOnMount, page, limit, defaultPage, defaultLimit]);
185
+
186
+ // Reset to page 1 when extraParams change
187
+ useEffect(() => {
188
+ setPageState(1);
189
+ }, [extraParamsKey]);
190
+
191
+ const totalPages = Math.max(1, Math.ceil(total / limit));
192
+ const hasNext = page < totalPages;
193
+ const hasPrev = page > 1;
194
+
195
+ const setPage = useCallback((newPage: number) => {
196
+ setPageState(Math.max(1, newPage));
197
+ }, []);
198
+
199
+ const setLimit = useCallback((newLimit: number) => {
200
+ setLimitState(newLimit);
201
+ setPageState(1); // Reset to first page when limit changes
202
+ }, []);
203
+
204
+ const nextPage = useCallback(() => {
205
+ if (hasNext) {
206
+ setPageState((p) => p + 1);
207
+ }
208
+ }, [hasNext]);
209
+
210
+ const prevPage = useCallback(() => {
211
+ if (hasPrev) {
212
+ setPageState((p) => p - 1);
213
+ }
214
+ }, [hasPrev]);
215
+
216
+ const pagination: PaginationState = {
217
+ page,
218
+ limit,
219
+ total,
220
+ totalPages,
221
+ hasNext,
222
+ hasPrev,
223
+ setPage,
224
+ setLimit,
225
+ nextPage,
226
+ prevPage,
227
+ refetch: fetchData,
228
+ };
229
+
230
+ return { items, loading, error, pagination };
231
+ }
package/src/index.ts ADDED
@@ -0,0 +1,53 @@
1
+ export * from "./components/Button";
2
+ export * from "./components/Input";
3
+ export * from "./components/Card";
4
+ export * from "./components/Label";
5
+ export * from "./components/NavItem";
6
+ export * from "./components/PermissionDenied";
7
+ export * from "./components/PermissionGate";
8
+ export * from "./components/SectionHeader";
9
+ export * from "./components/StatusCard";
10
+ export * from "./components/EmptyState";
11
+ export * from "./components/LoadingSpinner";
12
+ export * from "./components/DropdownMenu";
13
+ export * from "./components/UserMenu";
14
+ export * from "./components/EditableText";
15
+ export * from "./components/ConfirmationModal";
16
+ export * from "./components/HealthBadge";
17
+ export * from "./components/Table";
18
+ export * from "./utils";
19
+ export * from "./components/Select";
20
+ export * from "./components/Page";
21
+ export * from "./components/Textarea";
22
+ export * from "./components/Tooltip";
23
+ export * from "./components/Toggle";
24
+ export * from "./components/Checkbox";
25
+ export * from "./components/Alert";
26
+ export * from "./components/InfoBanner";
27
+ export * from "./components/DynamicForm";
28
+ export * from "./components/PageLayout";
29
+ export * from "./components/PluginConfigForm";
30
+ export * from "./components/Toast";
31
+ export * from "./components/ToastProvider";
32
+ export * from "./components/Dialog";
33
+ export * from "./components/Badge";
34
+ export * from "./components/Accordion";
35
+ export * from "./components/Tabs";
36
+ export * from "./components/ThemeProvider";
37
+ export * from "./components/SubscribeButton";
38
+ export * from "./components/Pagination";
39
+ export * from "./components/PaginatedList";
40
+ export * from "./hooks/usePagination";
41
+ export * from "./components/DateTimePicker";
42
+ export * from "./components/DateRangeFilter";
43
+ export * from "./components/BackLink";
44
+ export * from "./components/StatusUpdateTimeline";
45
+ export * from "./components/DynamicIcon";
46
+ export * from "./components/StrategyConfigCard";
47
+ export * from "./components/Markdown";
48
+ export * from "./components/ColorPicker";
49
+ export * from "./components/TemplateEditor";
50
+ export * from "./components/AnimatedCounter";
51
+ export * from "./components/CommandPalette";
52
+ export * from "./components/TerminalFeed";
53
+ export * from "./components/AmbientBackground";
package/src/themes.css ADDED
@@ -0,0 +1,204 @@
1
+ /* Base theme colors using CSS custom properties */
2
+ @layer base {
3
+ :root {
4
+ /* Primary - Purple/Indigo brand color */
5
+ --primary: 262 83% 58%;
6
+ --primary-foreground: 0 0% 100%;
7
+
8
+ /* Secondary - Complementary color */
9
+ --secondary: 240 5% 96%;
10
+ --secondary-foreground: 240 6% 10%;
11
+
12
+ /* Accent - Highlight color */
13
+ --accent: 240 5% 96%;
14
+ --accent-foreground: 240 6% 10%;
15
+
16
+ /* Destructive - Error/danger color */
17
+ --destructive: 0 84% 60%;
18
+ --destructive-foreground: 0 0% 100%;
19
+ /* white - for text on solid destructive backgrounds */
20
+
21
+ /* Muted - Subtle backgrounds */
22
+ --muted: 240 5% 96%;
23
+ --muted-foreground: 240 4% 46%;
24
+
25
+ /* Background and foreground */
26
+ --background: 0 0% 100%;
27
+ --foreground: 240 10% 4%;
28
+
29
+ /* Card - Elevated surfaces */
30
+ --card: 0 0% 98%;
31
+ --card-foreground: 240 10% 4%;
32
+
33
+ /* Popover - Floating elements */
34
+ --popover: 0 0% 100%;
35
+ --popover-foreground: 240 10% 4%;
36
+
37
+ /* Border and input */
38
+ --border: 240 6% 90%;
39
+ --input: 240 6% 90%;
40
+ --ring: 262 83% 58%;
41
+
42
+ /* Chart colors */
43
+ --chart-1: 262 83% 58%;
44
+ --chart-2: 173 58% 39%;
45
+ --chart-3: 197 37% 24%;
46
+ --chart-4: 43 74% 66%;
47
+ --chart-5: 27 87% 67%;
48
+
49
+ /* Semantic colors */
50
+ --success: 142 71% 45%;
51
+ /* green-600 */
52
+ --success-foreground: 0 0% 100%;
53
+ /* white - for text on solid success backgrounds */
54
+
55
+ --warning: 38 92% 50%;
56
+ /* yellow-500 */
57
+ --warning-foreground: 0 0% 0%;
58
+ /* black - for text on solid warning backgrounds (yellow needs dark text) */
59
+
60
+ --info: 217 91% 60%;
61
+ /* blue-500 */
62
+ --info-foreground: 0 0% 100%;
63
+ /* white - for text on solid info backgrounds */
64
+
65
+ /* Radius for rounded corners */
66
+ --radius: 0.5rem;
67
+ }
68
+
69
+ .dark {
70
+ /* Primary - Brighter in dark mode */
71
+ --primary: 263 70% 65%;
72
+ --primary-foreground: 0 0% 100%;
73
+
74
+ /* Secondary */
75
+ --secondary: 240 4% 16%;
76
+ --secondary-foreground: 0 0% 100%;
77
+
78
+ /* Accent */
79
+ --accent: 240 4% 16%;
80
+ --accent-foreground: 0 0% 100%;
81
+
82
+ /* Destructive */
83
+ --destructive: 0 63% 50%;
84
+ --destructive-foreground: 0 0% 100%;
85
+
86
+ /* Muted */
87
+ --muted: 240 4% 16%;
88
+ --muted-foreground: 240 5% 65%;
89
+
90
+ /* Background and foreground */
91
+ --background: 240 10% 4%;
92
+ --foreground: 0 0% 98%;
93
+
94
+ /* Card */
95
+ --card: 240 6% 10%;
96
+ --card-foreground: 0 0% 98%;
97
+
98
+ /* Popover */
99
+ --popover: 240 10% 4%;
100
+ --popover-foreground: 0 0% 98%;
101
+
102
+ /* Border and input */
103
+ --border: 240 4% 16%;
104
+ --input: 240 4% 16%;
105
+ --ring: 263 70% 65%;
106
+
107
+ /* Chart colors - adjusted for dark mode */
108
+ --chart-1: 263 70% 65%;
109
+ --chart-2: 173 58% 49%;
110
+ --chart-3: 197 37% 34%;
111
+ --chart-4: 43 74% 76%;
112
+ --chart-5: 27 87% 77%;
113
+
114
+ /* Semantic colors - dark mode */
115
+ --success: 142 76% 36%;
116
+ /* green-700 - slightly darker */
117
+ --success-foreground: 138 76% 97%;
118
+ /* green-50 */
119
+
120
+ --warning: 48 96% 53%;
121
+ /* yellow-400 - brighter */
122
+ --warning-foreground: 48 96% 89%;
123
+ /* yellow-200 - light/bright for dark backgrounds */
124
+
125
+ --info: 213 94% 68%;
126
+ /* blue-400 - brighter */
127
+ --info-foreground: 214 100% 97%;
128
+ /* blue-50 */
129
+ }
130
+ }
131
+
132
+ /* Apply base styles */
133
+ @layer base {
134
+ * {
135
+ @apply border-border;
136
+ }
137
+
138
+ body {
139
+ @apply bg-background text-foreground;
140
+ font-feature-settings: "rlig" 1, "calt" 1;
141
+ }
142
+ }
143
+
144
+ /* Custom animations */
145
+ @keyframes bell-ring {
146
+ 0% {
147
+ transform: rotate(0deg);
148
+ }
149
+
150
+ 10% {
151
+ transform: rotate(15deg);
152
+ }
153
+
154
+ 20% {
155
+ transform: rotate(-15deg);
156
+ }
157
+
158
+ 30% {
159
+ transform: rotate(10deg);
160
+ }
161
+
162
+ 40% {
163
+ transform: rotate(-10deg);
164
+ }
165
+
166
+ 50% {
167
+ transform: rotate(5deg);
168
+ }
169
+
170
+ 60% {
171
+ transform: rotate(-5deg);
172
+ }
173
+
174
+ 70% {
175
+ transform: rotate(0deg);
176
+ }
177
+
178
+ 100% {
179
+ transform: rotate(0deg);
180
+ }
181
+ }
182
+
183
+ .animate-bell-ring {
184
+ animation: bell-ring 0.5s ease-in-out;
185
+ transform-origin: top center;
186
+ }
187
+
188
+ /* Subtle tile pulse animation - gentle breathing effect */
189
+ @keyframes tile-pulse {
190
+
191
+ 0%,
192
+ 100% {
193
+ opacity: 1;
194
+ }
195
+
196
+ 50% {
197
+ opacity: 0.4;
198
+ }
199
+ }
200
+
201
+ .tile-glow {
202
+ animation: tile-pulse 12s ease-in-out infinite;
203
+ animation-delay: var(--glow-delay, 0s);
204
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Strip markdown formatting to plain text.
3
+ *
4
+ * A lightweight client-side utility for displaying markdown content as plain text.
5
+ * This is a simplified version that handles common markdown syntax.
6
+ */
7
+ export function stripMarkdown(markdown: string): string {
8
+ let result = markdown;
9
+
10
+ // Remove bold: **text** or __text__
11
+ result = result.replaceAll(/\*\*(.+?)\*\*/g, "$1");
12
+ result = result.replaceAll(/__(.+?)__/g, "$1");
13
+
14
+ // Remove italic: *text* or _text_ (but not if it's part of bold)
15
+ result = result.replaceAll(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
16
+ result = result.replaceAll(/(?<!_)_([^_]+)_(?!_)/g, "$1");
17
+
18
+ // Remove strikethrough: ~~text~~
19
+ result = result.replaceAll(/~~(.+?)~~/g, "$1");
20
+
21
+ // Remove links: [text](url) -> text
22
+ result = result.replaceAll(/\[([^\]]+)\]\([^)]+\)/g, "$1");
23
+
24
+ // Remove inline code: `code` -> code
25
+ result = result.replaceAll(/`([^`]+)`/g, "$1");
26
+
27
+ // Remove code blocks (simple version)
28
+ result = result.replaceAll(/```[\s\S]*?```/g, "");
29
+
30
+ // Remove headers: # text -> text
31
+ result = result.replaceAll(/^#+\s*/gm, "");
32
+
33
+ // Remove blockquotes: > text -> text
34
+ result = result.replaceAll(/^>\s*/gm, "");
35
+
36
+ // Remove horizontal rules
37
+ result = result.replaceAll(/^---+$/gm, "");
38
+ result = result.replaceAll(/^\*\*\*+$/gm, "");
39
+
40
+ // Collapse multiple whitespace
41
+ result = result.replaceAll(/\n\s*\n/g, "\n").trim();
42
+
43
+ return result;
44
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export { stripMarkdown } from "./utils/strip-markdown";
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/frontend.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }