Coding Conventions
The conventions that guide the development of Carbon
Import Conventions
The project uses absolute imports with these patterns:
@carbon/*- For packages~/- For app-specific imports (configured in tsconfig)
Example:
import { Button } from "@carbon/react";
import { useUser } from "~/hooks/useUser";Component Patterns
Component Structure
- Components are typically functional components using TypeScript
- Props are defined with TypeScript interfaces
- Components are exported from index.ts files for cleaner imports
Common Component Patterns
// Simple component with props
interface AvatarProps {
name: string;
size?: "sm" | "md" | "lg";
}
export function Avatar({ name, size = "md" }: AvatarProps) {
// Component logic
}
// Components are imported from the @carbon/react library in packages/react
import { Avatar, Button, VStack } from "@carbon/react";Service Layer Pattern
Services handle business logic and database operations. The first argument of the service is always the client object, which is a user-permissioned Supabase client:
// Example from sales.service.ts
import type { Database } from "@carbon/database";
import type { SupabaseClient } from "@supabase/supabase-js";
export async function closeSalesOrder(
client: SupabaseClient<Database>,
orderId: string
) {
return client
.from("salesOrder")
.update({
status: "Closed",
})
.eq("id", orderId);
}Form Handling
The project uses a custom form library with validators:
import { ValidatedForm, Input, Submit } from "@carbon/form";
import { z } from "zod";
export const customerValidator = z.object({
name: z.string().min(1),
email: z.string().email(),
});
export const Form = ({ initialValues }: FormProps) => {
return (
<ValidatedForm validator={customerValidator} method="post" action={path.to.customer(id)}>
<Input name="name" label="Name" />
<Input name="email" label="Email" />
<Submit>Save</Submit>
</ValidatedForm>
);
};Route Patterns
Loader Pattern
export async function loader({ request }: LoaderFunctionArgs) {
const { client, companyId, userId } = await requirePermissions(request, {
view: "moduleName",
});
// Data fetching logic
return json({ data });
}Action Pattern
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
// Handle form submission
return json({ success: true });
}State Management
- Uses nanostores for global state
- React Query for server state
- Remix's built-in data loading for route-specific data
Authentication Pattern
import { requirePermissions } from "@carbon/auth";
// In server-side code
const { client, companyId, userId } = await requirePermissions(request, {
view: "sales",
});
const result = methodFromServices(client);UI Patterns
- Consistent use of the Carbon design system
- Responsive design with Tailwind utilities
- Accessibility considerations (ARIA attributes)
- Dark mode support via theme system
Database Conventions
- Tables are named in the singular camelCase format (e.g.
customer,salesOrder,item) - Views are named in the plural camelCase format (e.g.
customers,salesOrders,items) - Functions are named in the snake_case format (e.g.
get_customers,create_sales_order,update_item)