Skip to content

Commit 4b4c9a2

Browse files
feat: Flows list view (#25)
1 parent 205f634 commit 4b4c9a2

29 files changed

+1419
-8
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ See `ServerActivationFormContainer.tsx` and `LoginFormContainer.tsx` for current
164164
- Keep Tailwind utility classes; Prettier (with the Tailwind plugin) auto-sorts them
165165
- Prefer focused components over overly generic abstractions
166166

167+
See [DESIGN.md](./DESIGN.md) for design-related guidelines.
168+
167169
### Coding Conventions
168170

169171
- Define React components with `function` declarations instead of arrow functions

DESIGN.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
## Icons
2+
3+
- MUST use `@untitledui/icons` for ALL icons — never use lucide-react, heroicons, or any other icon package
4+
- MUST import icons as React components: `import { SearchLg, Check } from "@untitledui/icons"`
5+
- NEVER use inline `<svg>` for standard icons — always use the Untitled UI component
6+
- Size icons via className: `<SearchLg className="w-4 h-4" />`
7+
- Only exception for inline SVG: custom illustrations, animated graphics, or the product logo
8+
9+
## Colors
10+
11+
- ALL color values MUST use oklch() format — never hex, rgb, or hsl
12+
- This applies to CSS variables, inline values, and any new tokens
13+
- Base design tokens are defined in `src/styles/globals.css` using oklch()
14+
- When adding a new color, convert it to oklch first
15+
- Use existing theme tokens (e.g., `text-foreground`, `bg-card`) before introducing new values
16+
17+
## Component Rules
18+
19+
- MUST use shadcn/ui components as the base — never rebuild primitives
20+
- MUST add `aria-label` to icon-only buttons
21+
- NEVER mix component primitive systems in the same surface
22+
- NEVER rebuild keyboard or focus behavior by hand
23+
24+
## Accessibility
25+
26+
- All interactive elements must be keyboard accessible
27+
- WCAG AA minimum: 4.5:1 contrast for body text, 3:1 for large text and UI components
28+
- Never use color alone to convey information — pair with icons or text
29+
- Every image needs `alt` text; decorative images get `alt=""`
30+
- Use semantic HTML elements (`nav`, `main`, `section`, `button`) — not divs with click handlers
31+
- MUST use `AlertDialog` for destructive/irreversible actions
32+
33+
## Responsiveness
34+
35+
- Mobile-first: base styles for mobile, `min-width` queries to layer complexity
36+
- NEVER use `h-screen` — use `h-dvh` instead
37+
- MUST respect `safe-area-inset` for fixed/sticky elements
38+
- Use `clamp()` for fluid spacing and font sizes where appropriate
39+
- Touch targets must be at least 44x44px on touch devices
40+
41+
## Design Quality
42+
43+
- NEVER use gradients unless explicitly requested
44+
- NEVER use glow effects as primary affordances
45+
- NEVER use purple or multicolor gradients
46+
- Use Tailwind default shadow scale — no arbitrary shadow values
47+
- Limit accent color to one per view
48+
- Use existing theme/Tailwind color tokens before introducing new hex values
49+
- `tabular-nums` for all numeric data displays
50+
- `text-balance` for headings, `text-pretty` for body text
51+
- Use `truncate` or `line-clamp` for dense UI
52+
53+
## Animation
54+
55+
- NEVER add animation unless explicitly requested
56+
- Animate only compositor props (`transform`, `opacity`)
57+
- NEVER animate layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`)
58+
- Never exceed 200ms for interaction feedback
59+
- MUST respect `prefers-reduced-motion`
60+
61+
## Resizable Panels
62+
63+
- Use CSS-based drag handlers (mousedown → document mousemove/mouseup) — NOT `react-resizable-panels`
64+
- Each resizable panel needs: min width, max width, collapse threshold, default width constants
65+
- Store cleanup functions in a ref and call them on unmount to prevent listener leaks
66+
- Set `document.body.style.cursor = "col-resize"` and `document.body.style.userSelect = "none"` during drag
67+
- Drag handle: `w-px bg-border hover:bg-primary/50 cursor-col-resize` with `after:` pseudo-element for larger hit area
68+
- Auto-collapse: when dragged below threshold, set panel open state to false and render width 0
69+
- Conditionally render panel content only when open to avoid layout of hidden elements
70+
71+
## Tooltips
72+
73+
- `TooltipProvider` wraps the entire app in `src/App.tsx` — do not add additional providers
74+
- MUST wrap every icon-only button in a `Tooltip` with a descriptive `TooltipContent`
75+
- Use `side="bottom"` for toolbar buttons, `side="top"` for bottom-positioned actions
76+
77+
## shadcn Component Usage
78+
79+
- NEVER override shadcn component styles with inline className when a variant or size prop exists
80+
- Use standard `variant` and `size` props: `<Button variant="outline" size="sm">` — not `className="text-[12px] gap-1.5"`
81+
- `TabsTrigger` and `ToggleGroupItem` should use default styles — override only `data-[state=on]` or `data-[state=active]` when needed
82+
- When multiple toggle/filter elements share a toolbar, use consistent active state styling across all of them

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
"@tanstack/react-query": "^5.90.21",
2121
"@tanstack/react-router": "^1.166.7",
2222
"@tanstack/react-router-devtools": "^1.166.7",
23+
"@tanstack/react-table": "^8.21.3",
2324
"@untitledui/icons": "^0.0.21",
2425
"class-variance-authority": "^0.7.1",
2526
"clsx": "^2.1.1",
27+
"date-fns": "^4.1.0",
2628
"es-toolkit": "^1.45.1",
2729
"next-themes": "^0.4.6",
2830
"openapi-fetch": "^0.17.0",

pnpm-lock.yaml

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { components } from "@/shared/api/openapi";
2+
3+
export type ExecutionStatus = components["schemas"]["ExecutionStatus"];
4+
5+
export const executionStatusValues: ExecutionStatus[] = [
6+
"initializing",
7+
"provisioning",
8+
"running",
9+
"failed",
10+
"completed",
11+
"cached",
12+
"skipped",
13+
"retrying",
14+
"retried",
15+
"stopped",
16+
"stopping",
17+
] as const;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { queryOptions } from "@tanstack/react-query";
2+
import { fetchFlows } from "../domain/fetch-flows";
3+
4+
export const flowsQueryKeys = {
5+
all: ["flows"] as const,
6+
};
7+
8+
export const flowsQueries = {
9+
all: () =>
10+
queryOptions({
11+
queryKey: flowsQueryKeys.all,
12+
queryFn: fetchFlows,
13+
}),
14+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { useSuspenseQuery } from "@tanstack/react-query";
2+
import { flowsQueries } from "./flows-queries";
3+
4+
export function useFlows() {
5+
const query = useSuspenseQuery(flowsQueries.all());
6+
7+
return { ...query, flowsData: query.data };
8+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { apiClient } from "@/shared/api/domain/api-client";
2+
import { expectData } from "@/shared/api/utils/unwrap-api-result";
3+
import { type Flow, flowFromApiToDomain } from "./flow";
4+
5+
// TODO: Remove these constants and use the API pagination instead
6+
const DEFAULT_PAGE = 1;
7+
const MAX_PAGE_SIZE = 1000;
8+
9+
export async function fetchFlows(): Promise<Flow[]> {
10+
const response = await apiClient.GET("/api/v1/pipelines", {
11+
params: {
12+
query: {
13+
page: DEFAULT_PAGE,
14+
size: MAX_PAGE_SIZE,
15+
},
16+
},
17+
});
18+
const flowsPage = expectData(response);
19+
20+
return flowsPage.items.map(flowFromApiToDomain);
21+
}

0 commit comments

Comments
 (0)