Carbon LogoCarbon

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)