@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.
- package/CHANGELOG.md +153 -0
- package/bunfig.toml +2 -0
- package/package.json +40 -0
- package/src/components/Accordion.tsx +55 -0
- package/src/components/Alert.tsx +90 -0
- package/src/components/AmbientBackground.tsx +105 -0
- package/src/components/AnimatedCounter.tsx +54 -0
- package/src/components/BackLink.tsx +56 -0
- package/src/components/Badge.tsx +38 -0
- package/src/components/Button.tsx +55 -0
- package/src/components/Card.tsx +56 -0
- package/src/components/Checkbox.tsx +46 -0
- package/src/components/ColorPicker.tsx +69 -0
- package/src/components/CommandPalette.tsx +74 -0
- package/src/components/ConfirmationModal.tsx +134 -0
- package/src/components/DateRangeFilter.tsx +128 -0
- package/src/components/DateTimePicker.tsx +65 -0
- package/src/components/Dialog.tsx +134 -0
- package/src/components/DropdownMenu.tsx +96 -0
- package/src/components/DynamicForm/DynamicForm.tsx +126 -0
- package/src/components/DynamicForm/DynamicOptionsField.tsx +220 -0
- package/src/components/DynamicForm/FormField.tsx +690 -0
- package/src/components/DynamicForm/JsonField.tsx +98 -0
- package/src/components/DynamicForm/index.ts +11 -0
- package/src/components/DynamicForm/types.ts +95 -0
- package/src/components/DynamicForm/utils.ts +39 -0
- package/src/components/DynamicIcon.tsx +45 -0
- package/src/components/EditableText.tsx +141 -0
- package/src/components/EmptyState.tsx +32 -0
- package/src/components/HealthBadge.tsx +57 -0
- package/src/components/InfoBanner.tsx +97 -0
- package/src/components/Input.tsx +20 -0
- package/src/components/Label.tsx +17 -0
- package/src/components/LoadingSpinner.tsx +29 -0
- package/src/components/Markdown.tsx +206 -0
- package/src/components/NavItem.tsx +112 -0
- package/src/components/Page.tsx +58 -0
- package/src/components/PageLayout.tsx +83 -0
- package/src/components/PaginatedList.tsx +135 -0
- package/src/components/Pagination.tsx +195 -0
- package/src/components/PermissionDenied.tsx +31 -0
- package/src/components/PermissionGate.tsx +97 -0
- package/src/components/PluginConfigForm.tsx +91 -0
- package/src/components/SectionHeader.tsx +30 -0
- package/src/components/Select.tsx +157 -0
- package/src/components/StatusCard.tsx +78 -0
- package/src/components/StatusUpdateTimeline.tsx +222 -0
- package/src/components/StrategyConfigCard.tsx +333 -0
- package/src/components/SubscribeButton.tsx +96 -0
- package/src/components/Table.tsx +119 -0
- package/src/components/Tabs.tsx +141 -0
- package/src/components/TemplateEditor.test.ts +156 -0
- package/src/components/TemplateEditor.tsx +435 -0
- package/src/components/TerminalFeed.tsx +152 -0
- package/src/components/Textarea.tsx +22 -0
- package/src/components/ThemeProvider.tsx +76 -0
- package/src/components/Toast.tsx +118 -0
- package/src/components/ToastProvider.tsx +126 -0
- package/src/components/Toggle.tsx +47 -0
- package/src/components/Tooltip.tsx +20 -0
- package/src/components/UserMenu.tsx +79 -0
- package/src/hooks/usePagination.e2e.ts +275 -0
- package/src/hooks/usePagination.ts +231 -0
- package/src/index.ts +53 -0
- package/src/themes.css +204 -0
- package/src/utils/strip-markdown.ts +44 -0
- package/src/utils.ts +8 -0
- 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