Skip to content

Commit b577aa9

Browse files
authored
Merge pull request #870 from trycompai/claudio/ui-tweaks-3
[dev] [claudfuen] claudio/ui-tweaks-3
2 parents 7359159 + 44ea88c commit b577aa9

25 files changed

Lines changed: 493 additions & 425 deletions

File tree

.cursor/rules/design-system.mdc

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
description:
3+
globs: *.tsx
4+
alwaysApply: false
5+
---
6+
7+
Rule Name: design-system
8+
Description:
9+
Design System & Component Guidelines
10+
11+
## Design Philosophy
12+
13+
- **B2B, Modern, Flat, Minimal, Elegant**: All UI should follow a clean, professional aesthetic suitable for business applications
14+
- **Sleek & Minimal**: Avoid visual clutter, use whitespace effectively, keep interfaces clean
15+
- **Dark Mode First**: Always ensure components work seamlessly in both light and dark modes
16+
17+
## Component Usage
18+
19+
- **Adhere to Base Components**: Minimize custom overrides and stick to shadcn/ui base components whenever possible
20+
- **Semantic Color Classes**: Use semantic classes like `text-muted-foreground`, `bg-muted/50` instead of hardcoded colors
21+
- **Dark Mode Support**: Always use dark mode variants like `bg-green-50 dark:bg-green-950/20`, `text-green-600 dark:text-green-400`
22+
23+
## Typography & Sizing
24+
25+
- **Moderate Text Sizes**: Avoid overly large text - prefer `text-base`, `text-sm`, `text-xs` over `text-xl+`
26+
- **Consistent Hierarchy**: Use `font-medium`, `font-semibold` sparingly, prefer `font-normal` with size differentiation
27+
- **Tabular Numbers**: Use `tabular-nums` class for numeric data to ensure proper alignment
28+
29+
## Layout & Spacing
30+
31+
- **Consistent Spacing**: Use standard Tailwind spacing scale (`space-y-4`, `gap-6`, etc.)
32+
- **Card-Based Layouts**: Prefer Card components for content organization
33+
- **Minimal Padding**: Use conservative padding - `p-3`, `p-4` rather than larger values
34+
- **Clean Separators**: Use subtle borders (`border-t`, `border-muted`) instead of heavy dividers
35+
36+
## Color & Visual Elements
37+
38+
- **Status Colors**:
39+
- Green for completed/success states
40+
- Blue for in-progress/info states
41+
- Yellow for warnings
42+
- Red for errors/destructive actions
43+
- **Subtle Indicators**: Use small colored dots (`w-2 h-2 rounded-full`) instead of large icons for status
44+
- **Minimal Shadows**: Prefer `hover:shadow-sm` over heavy shadow effects
45+
- **Progress Bars**: Keep thin (`h-1`, `h-2`) for minimal visual weight
46+
47+
## Interactive Elements
48+
49+
- **Subtle Hover States**: Use gentle transitions (`transition-shadow`, `hover:shadow-sm`)
50+
- **Consistent Button Sizing**: Prefer `size="sm"` for most buttons, `size="icon"` for icon-only
51+
- **Badge Usage**: Keep badges minimal with essential info only (percentages, short status)
52+
53+
## Data Display
54+
55+
- **Shared Design Language**: Ensure related components (cards, overviews, details) use consistent patterns
56+
- **Minimal Stats**: Present data cleanly without excessive decoration
57+
- **Contextual Icons**: Use small, relevant icons (`h-3 w-3`, `h-4 w-4`) sparingly for context
58+
59+
## Anti-Patterns to Avoid
60+
61+
- Large text sizes (`text-2xl+` except for main headings)
62+
- Heavy shadows or borders
63+
- Excessive use of colored backgrounds
64+
- Redundant badges or status indicators
65+
- Complex custom styling overrides
66+
- Non-semantic color usage (hardcoded hex values)
67+
- Cluttered layouts with too many visual elements

apps/app/src/app/(app)/[orgId]/controls/[controlId]/components/PoliciesTable.tsx

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
import { DataTable } from "@/components/data-table/data-table";
44
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header";
5-
import { DataTableSortList } from "@/components/data-table/data-table-sort-list";
65
import { StatusIndicator } from "@/components/status-indicator";
76
import { useDataTable } from "@/hooks/use-data-table";
87
import { Policy } from "@comp/db/types";
9-
import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card";
108
import { Input } from "@comp/ui/input";
9+
import { Icons } from "@comp/ui/icons";
1110
import { ColumnDef } from "@tanstack/react-table";
1211
import { useMemo, useState } from "react";
1312

@@ -106,35 +105,23 @@ export function PoliciesTable({
106105
});
107106

108107
return (
109-
<Card>
110-
<CardHeader>
111-
<CardTitle>
112-
{"Linked Policies"} ({filteredPolicies.length})
113-
</CardTitle>
114-
</CardHeader>
115-
<CardContent>
116-
<div className="flex items-center mb-4">
117-
<Input
118-
placeholder={"Search..."}
119-
value={searchTerm}
120-
onChange={(e) => setSearchTerm(e.target.value)}
121-
className="max-w-sm"
122-
/>
123-
{/* <div className="ml-auto">
124-
<DataTableSortList
125-
table={table.table}
126-
align="end"
127-
tableId="policiesTable"
128-
/>
129-
</div> */}
130-
</div>
131-
<DataTable
132-
table={table.table}
133-
rowClickBasePath={`/${orgId}/policies/`}
134-
getRowId={(row) => row.id}
135-
tableId={"policiesTable"}
108+
<div className="space-y-4">
109+
<div className="flex items-center">
110+
<Input
111+
placeholder="Search policies..."
112+
value={searchTerm}
113+
onChange={(e) => setSearchTerm(e.target.value)}
114+
className="max-w-sm"
115+
leftIcon={<Icons.Search size={16} />}
136116
/>
137-
</CardContent>
138-
</Card>
117+
</div>
118+
119+
<DataTable
120+
table={table.table}
121+
rowClickBasePath={`/${orgId}/policies/`}
122+
getRowId={(row) => row.id}
123+
tableId={"policiesTable"}
124+
/>
125+
</div>
139126
);
140127
}

apps/app/src/app/(app)/[orgId]/controls/[controlId]/components/RequirementsTable.tsx

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import { DataTable } from "@/components/data-table/data-table";
44
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header";
5-
import { DataTableSortList } from "@/components/data-table/data-table-sort-list";
65
import { useDataTable } from "@/hooks/use-data-table";
76
import type {
87
FrameworkEditorFramework,
98
FrameworkEditorRequirement,
109
FrameworkInstance,
1110
RequirementMap,
1211
} from "@comp/db/types";
13-
import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card";
1412
import { Input } from "@comp/ui/input";
13+
import { Icons } from "@comp/ui/icons";
1514
import { ColumnDef } from "@tanstack/react-table";
1615
import { useMemo, useState } from "react";
1716

@@ -21,8 +20,8 @@ interface RequirementsTableProps {
2120
orgId: string;
2221
}
2322

24-
export function RequirementsTable({
25-
requirements,
23+
export function RequirementsTable({
24+
requirements,
2625
orgId,
2726
}: RequirementsTableProps) {
2827
const [searchTerm, setSearchTerm] = useState("");
@@ -116,41 +115,28 @@ interface RequirementsTableProps {
116115
});
117116

118117
return (
119-
<Card>
120-
<CardHeader>
121-
<CardTitle>
122-
{"Linked Requirements"} (
123-
{filteredRequirements.length})
124-
</CardTitle>
125-
</CardHeader>
126-
<CardContent>
127-
<div className="flex items-center mb-4">
128-
<Input
129-
placeholder={"Search..."}
130-
value={searchTerm}
131-
onChange={(e) => setSearchTerm(e.target.value)}
132-
className="max-w-sm"
133-
/>
134-
{/* <div className="ml-auto">
135-
<DataTableSortList
136-
table={table.table}
137-
align="end"
138-
tableId="r"
139-
/>
140-
</div> */}
141-
</div>
142-
<DataTable
143-
table={table.table}
144-
rowClickBasePath={`/${orgId}/frameworks`}
145-
getRowId={(row) => {
146-
// This constructs the path to the specific requirement page
147-
// row.requirementId is the FrameworkEditorRequirement.id (e.g. frk_rq_...)
148-
// row.frameworkInstanceId is the ID of the FrameworkInstance
149-
return `${row.frameworkInstanceId}/requirements/${row.requirementId}`;
150-
}}
151-
tableId={"r"}
118+
<div className="space-y-4">
119+
<div className="flex items-center">
120+
<Input
121+
placeholder="Search requirements..."
122+
value={searchTerm}
123+
onChange={(e) => setSearchTerm(e.target.value)}
124+
className="max-w-sm"
125+
leftIcon={<Icons.Search size={16} />}
152126
/>
153-
</CardContent>
154-
</Card>
127+
</div>
128+
129+
<DataTable
130+
table={table.table}
131+
rowClickBasePath={`/${orgId}/frameworks`}
132+
getRowId={(row) => {
133+
// This constructs the path to the specific requirement page
134+
// row.requirementId is the FrameworkEditorRequirement.id (e.g. frk_rq_...)
135+
// row.frameworkInstanceId is the ID of the FrameworkInstance
136+
return `${row.frameworkInstanceId}/requirements/${row.requirementId}`;
137+
}}
138+
tableId={"r"}
139+
/>
140+
</div>
155141
);
156142
}

apps/app/src/app/(app)/[orgId]/controls/[controlId]/components/SingleControl.tsx

Lines changed: 88 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import type {
1010
RequirementMap,
1111
Task,
1212
} from "@comp/db/types";
13-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@comp/ui/card";
1413
import { Button } from "@comp/ui/button";
14+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@comp/ui/tabs";
1515
import {
1616
DropdownMenu,
1717
DropdownMenuContent,
1818
DropdownMenuItem,
1919
DropdownMenuTrigger,
2020
} from "@comp/ui/dropdown-menu";
21-
import { MoreVertical, PencilIcon, Trash2 } from "lucide-react";
21+
import { MoreVertical, Trash2 } from "lucide-react";
2222
import { useState } from "react";
2323
import { ControlDeleteDialog } from "./ControlDeleteDialog";
2424
import { useParams } from "next/navigation";
@@ -79,67 +79,99 @@ export function SingleControl({
7979

8080
return (
8181
<div className="space-y-6">
82-
<Card>
83-
<CardHeader>
84-
<CardTitle>
85-
<div className="flex items-center justify-between gap-2">
86-
<div className="flex items-center gap-2">
87-
{control.name}
88-
</div>
89-
<div className="flex items-center gap-2">
90-
<StatusIndicator status={progressStatus} />
91-
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
92-
<DropdownMenuTrigger asChild>
93-
<Button
94-
size="icon"
95-
variant="ghost"
96-
className="p-2 m-0 size-auto"
97-
>
98-
<MoreVertical className="h-4 w-4" />
99-
</Button>
100-
</DropdownMenuTrigger>
101-
<DropdownMenuContent align="end">
102-
<DropdownMenuItem
103-
onClick={() => {
104-
setDropdownOpen(false);
105-
setDeleteDialogOpen(true);
106-
}}
107-
className="text-destructive focus:text-destructive"
108-
>
109-
<Trash2 className="h-4 w-4 mr-2" />
110-
Delete
111-
</DropdownMenuItem>
112-
</DropdownMenuContent>
113-
</DropdownMenu>
114-
</div>
82+
{/* Control Header */}
83+
<div className="space-y-3">
84+
<div className="flex items-start justify-between gap-4">
85+
<div className="min-w-0 flex-1">
86+
<div className="flex items-center gap-2 mb-2">
87+
<h1 className="text-xl font-semibold truncate">{control.name}</h1>
88+
<StatusIndicator status={progressStatus} />
11589
</div>
116-
</CardTitle>
117-
<CardDescription>
118-
{control.description}
119-
</CardDescription>
120-
</CardHeader>
121-
</Card>
122-
<RequirementsTable
123-
requirements={control.requirementsMapped}
124-
orgId={orgIdFromParams}
125-
/>
126-
<PoliciesTable
127-
policies={relatedPolicies}
128-
orgId={orgIdFromParams}
129-
controlId={controlIdFromParams}
130-
/>
131-
<TasksTable
132-
tasks={relatedTasks}
133-
orgId={orgIdFromParams}
134-
controlId={controlIdFromParams}
135-
/>
90+
{control.description && (
91+
<p className="text-sm text-muted-foreground leading-relaxed">
92+
{control.description}
93+
</p>
94+
)}
95+
</div>
96+
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
97+
<DropdownMenuTrigger asChild>
98+
<Button
99+
size="sm"
100+
variant="ghost"
101+
className="shrink-0"
102+
>
103+
<MoreVertical className="h-4 w-4" />
104+
</Button>
105+
</DropdownMenuTrigger>
106+
<DropdownMenuContent align="end">
107+
<DropdownMenuItem
108+
onClick={() => {
109+
setDropdownOpen(false);
110+
setDeleteDialogOpen(true);
111+
}}
112+
className="text-destructive focus:text-destructive"
113+
>
114+
<Trash2 className="h-4 w-4 mr-2" />
115+
Delete
116+
</DropdownMenuItem>
117+
</DropdownMenuContent>
118+
</DropdownMenu>
119+
</div>
120+
</div>
121+
122+
{/* Tabbed Content */}
123+
<Tabs defaultValue="requirements" className="space-y-4">
124+
<TabsList className="grid w-full grid-cols-3">
125+
<TabsTrigger value="requirements" className="flex items-center gap-2">
126+
<span>Requirements</span>
127+
<span className="text-xs bg-muted/50 px-1.5 py-0.5 rounded-xs tabular-nums">
128+
{control.requirementsMapped.length}
129+
</span>
130+
</TabsTrigger>
131+
<TabsTrigger value="policies" className="flex items-center gap-2">
132+
<span>Policies</span>
133+
<span className="text-xs bg-muted/50 px-1.5 py-0.5 rounded-xs tabular-nums">
134+
{relatedPolicies.length}
135+
</span>
136+
</TabsTrigger>
137+
<TabsTrigger value="tasks" className="flex items-center gap-2">
138+
<span>Tasks</span>
139+
<span className="text-xs bg-muted/50 px-1.5 py-0.5 rounded-xs tabular-nums">
140+
{relatedTasks.length}
141+
</span>
142+
</TabsTrigger>
143+
</TabsList>
144+
145+
<TabsContent value="requirements" className="space-y-0">
146+
<RequirementsTable
147+
requirements={control.requirementsMapped}
148+
orgId={orgIdFromParams}
149+
/>
150+
</TabsContent>
151+
152+
<TabsContent value="policies" className="space-y-0">
153+
<PoliciesTable
154+
policies={relatedPolicies}
155+
orgId={orgIdFromParams}
156+
controlId={controlIdFromParams}
157+
/>
158+
</TabsContent>
159+
160+
<TabsContent value="tasks" className="space-y-0">
161+
<TasksTable
162+
tasks={relatedTasks}
163+
orgId={orgIdFromParams}
164+
controlId={controlIdFromParams}
165+
/>
166+
</TabsContent>
167+
</Tabs>
136168

137169
{/* Delete Dialog */}
138170
<ControlDeleteDialog
139171
isOpen={deleteDialogOpen}
140172
onClose={() => setDeleteDialogOpen(false)}
141173
control={control}
142174
/>
143-
</div >
175+
</div>
144176
);
145177
}

0 commit comments

Comments
 (0)