diff --git a/.changeset/sources-refactor-ui-variants.md b/.changeset/sources-refactor-ui-variants.md
new file mode 100644
index 000000000..90562d583
--- /dev/null
+++ b/.changeset/sources-refactor-ui-variants.md
@@ -0,0 +1,11 @@
+---
+"@hyperdx/app": minor
+---
+
+Refactor Sources components and add custom Mantine UI variants
+
+- Move SourceForm to Sources/ subfolder with reusable SourcesList component
+- Add primary, secondary, and danger button/action icon variants
+- Improve Storybook with font switching and component stories
+- Update ErrorBoundary styling with danger variant
+
diff --git a/.gitignore b/.gitignore
index 31004dafa..9bc6ca347 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,6 +61,9 @@ e2e/cypress/results
**/playwright/.cache/
**/.auth/
+# storybook
+**/storybook-static/
+
# scripts
scripts/*.csv
**/venv
diff --git a/agent_docs/code_style.md b/agent_docs/code_style.md
index 43bc9cac0..fd0a1d957 100644
--- a/agent_docs/code_style.md
+++ b/agent_docs/code_style.md
@@ -23,6 +23,22 @@
- Define TypeScript interfaces for props
- Use proper keys for lists, memoization for expensive computations
+## Mantine UI Components
+
+The project uses Mantine UI with **custom variants** defined in `packages/app/src/theme/mantineTheme.ts`:
+
+### Custom Button Variants
+- `variant="primary"` - Light green button for primary actions
+- `variant="secondary"` - Default styled button for secondary actions
+- `variant="danger"` - Light red button for destructive actions
+
+### Custom ActionIcon Variants
+- `variant="primary"` - Light green action icon
+- `variant="secondary"` - Default styled action icon
+- `variant="danger"` - Light red action icon for destructive actions
+
+These are valid variants - do not replace them with standard Mantine variants like `variant="light" color="red"`.
+
## Refactoring
- Edit files directly - don't create `component-v2.tsx` copies
diff --git a/package.json b/package.json
index c6be2971e..387016655 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"app:dev": "concurrently -k -n 'API,APP,ALERTS-TASK,COMMON-UTILS' -c 'green.bold,blue.bold,yellow.bold,magenta' 'nx run @hyperdx/api:dev' 'nx run @hyperdx/app:dev' 'nx run @hyperdx/api:dev-task check-alerts' 'nx run @hyperdx/common-utils:dev'",
"app:dev:local": "concurrently -k -n 'APP,COMMON-UTILS' -c 'blue.bold,magenta' 'nx run @hyperdx/app:dev:local' 'nx run @hyperdx/common-utils:dev'",
"app:lint": "nx run @hyperdx/app:ci:lint",
+ "app:storybook": "nx run @hyperdx/app:storybook",
"dev": "yarn build:common-utils && dotenvx run --convention=nextjs -- docker compose -f docker-compose.dev.yml up -d && yarn app:dev && docker compose -f docker-compose.dev.yml down",
"dev:local": "IS_LOCAL_APP_MODE='DANGEROUSLY_is_local_app_modeđź’€' yarn dev",
"dev:down": "docker compose -f docker-compose.dev.yml down",
diff --git a/packages/app/.storybook/main.ts b/packages/app/.storybook/main.ts
index a1eed1374..eafb73359 100644
--- a/packages/app/.storybook/main.ts
+++ b/packages/app/.storybook/main.ts
@@ -21,6 +21,15 @@ const config: StorybookConfig = {
options: {},
},
staticDirs: ['./public'],
+ webpackFinal: async config => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ 'next/router': require.resolve('next/router'),
+ };
+ }
+ return config;
+ },
};
export default config;
diff --git a/packages/app/.storybook/preview.tsx b/packages/app/.storybook/preview.tsx
index e698f59da..6ac5a5403 100644
--- a/packages/app/.storybook/preview.tsx
+++ b/packages/app/.storybook/preview.tsx
@@ -1,10 +1,11 @@
import React from 'react';
import { NextAdapter } from 'next-query-params';
import { initialize, mswLoader } from 'msw-storybook-addon';
-import { QueryClient, QueryClientProvider } from 'react-query';
import { QueryParamProvider } from 'use-query-params';
import type { Preview } from '@storybook/nextjs';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ibmPlexMono, inter, roboto, robotoMono } from '../src/fonts';
import { meHandler } from '../src/mocks/handlers';
import { ThemeWrapper } from '../src/ThemeWrapper';
@@ -31,29 +32,75 @@ export const globalTypes = {
defaultValue: 'light',
toolbar: {
icon: 'mirror',
+ title: 'Theme',
items: [
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
],
},
},
+ font: {
+ name: 'Font',
+ description: 'App font family',
+ defaultValue: 'inter',
+ toolbar: {
+ icon: 'typography',
+ title: 'Font',
+ items: [
+ { value: 'inter', title: 'Inter' },
+ { value: 'roboto', title: 'Roboto' },
+ { value: 'ibm-plex-mono', title: 'IBM Plex Mono' },
+ { value: 'roboto-mono', title: 'Roboto Mono' },
+ ],
+ },
+ },
};
initialize();
-const queryClient = new QueryClient();
+const fontMap = {
+ inter: inter,
+ roboto: roboto,
+ 'ibm-plex-mono': ibmPlexMono,
+ 'roboto-mono': robotoMono,
+};
+
+// Create a new QueryClient for each story to avoid cache pollution between stories
+const createQueryClient = () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ staleTime: 0,
+ },
+ },
+ });
const preview: Preview = {
decorators: [
- (Story, context) => (
-