# PEC ACM Website Code Style and Formatting The PEC ACM Website codebase mainly uses [Biome](https://biomejs.dev/) for code formatting in order to quickly and automatically check for formatting (and sometimes logical) errors in code. The Project includes pnpm scripts for linting and formatting, in addition to pre-commit hooks with [husky](https://typicode.github.io/husky/). These scripts include: ```bash pnpm run format # Run the formatter on the project and write any changes to the filesystem, if applicable pnpm run format:check # Run the formatter on the project but don't write any changes to the filesystem pnpm run lint # Run various checks on the project pnpm run check # Runs formatter, linter and import sorting on the project ``` The guiding principles of the code style and code formatting can be summarized as: - no nonsense - use your own judgement - break the rules **if it makes sense** e.g.: it improves readability substantially - use the formatting of the file you're in, even if it breaks the rules - no nonsense It's worth noting that modern IDEs and text editors can run Biome automatically on save. It may be worth looking into whether this functionality is supported in your editor for a more convenient development workflow. ## Table of Contents - [Path Aliases](#path-aliases) - [Formatting](#formatting) - [Naming Conventions](#naming-conventions) - [Files](#files) - [Code](#code) - [Import Sorting](#import-sorting) - [Import Categories](#import-categories) - [Complex Example](#complex-example) - [Route/Page Example](#routepage-example) - [Notes on Import Sorting](#notes-on-import-sorting) - [Common Patterns](#common-patterns) - [React Conventions](#react-conventions) - [Component Props](#component-props) - [Extending HTML Element Props](#extending-html-element-props) - [TypeScript Guidelines](#typescript-guidelines) ## Path Aliases The following path aliases are configured to improve import statements and maintain clarity across the codebase: - **$components**: Points to the `src/components` directory. - **$data**: Points to the `src/data` directory. - **$env**: Points to the `src/lib/env` directory. - **$emails**: Points to the `src/emails` directory and contains React Email templates. - **$hooks**: Points to the `src/hooks` directory. - **$lib**: Points to the `src/lib` directory and is the place for creating internal packages. - **$assets**: Points to the `src/lib/assets` directory and contains all the static assets used in the project. - **$styles**: Points to the `src/styles` directory and contains all Sass stylesheets. ## Formatting - Biome is enabled for formatting with the following settings: - **Indentation Style**: Spaces - **Indent Width**: 2 spaces - **Attribute Position**: Multiline - **Quote Style**: Double quotes - **Arrow Parentheses**: Add parentheses only as needed. ## Naming Conventions ### Files - All file names must be in **lowercase kebab case** (e.g., `src/components/sidebar/index.tsx`). - Directories should follow the same kebab case convention. ### Code - **Type Names** and **Zod Schemas** should use **Pascal Case** (e.g., `Sidebar`, `UserSchema`). - **Constant Values** should be in **UPPER_SNAKE_CASE** (e.g., `BACKEND_BASE_URL`, `MAX_RETRY_COUNT`). - **Functions** and **Variables** should be in **camelCase** (e.g., `handleClick`, `userData`). - Use meaningful names that clearly describe the purpose of the variables, functions, and types. ## Import Sorting ### Import Categories Imports must be sorted into the following categories: - **Types**: All type imports at the top. - **External Packages**: - Imports starting with a symbol come first. - Followed by named imports. - **Assets**: Static files like images, fonts, icons. - **Components**: Imported from `$components` path alias. - **Data**: Imported from `$data` path alias. - **Hooks**: Imported from `$hooks` path alias. - **Internal Packages**: - All internal imports are created inside `src/lib/` directory and can be imported using the `$lib` path alias. - **Styles**: Sass/SCSS files imported from `$styles` path alias. **Rules:** - Each category must be separated by a single line to enhance readability. - Within each category, sort imports alphabetically when possible. ### Complex Example ```tsx // Types import type { FC, ReactNode } from 'react'; import type { ApiResponse } from '$lib/types/api'; import type { User, UserRole } from '$lib/types/user'; // External Packages import { treaty } from '@elysiajs/eden'; import { useCallback, useEffect, useState } from 'react'; import { z } from 'zod'; // Assets import DEFAULT_AVATAR from '$assets/images/default-avatar.png'; // Components import Avatar from '$components/avatar'; import Button from '$components/button'; import Modal from '$components/modal'; import Spinner from '$components/spinner'; // Data import { USER_DEFAULTS } from '$data/constants'; import { ROLE_PERMISSIONS } from '$data/permissions'; // Hooks import { useAuth } from '$hooks/use-auth'; import { useDebounce } from '$hooks/use-debounce'; import { useLocalStorage } from '$hooks/use-local-storage'; // Internal Packages import { api } from '$lib/api'; import { UserSchema } from '$lib/schemas/user'; import { formatDate, truncate } from '$lib/utils'; // Styles import styles from '$styles/components/user-profile.module.scss'; ``` ### Route/Page Example ```tsx // Types import type { EventData, EventFilters } from '$lib/types/events'; // External Packages import { useSuspenseQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import { useMemo, useState } from 'react'; // Assets import HERO_IMAGE from '$assets/images/events-hero.jpg'; // Components import EventCard from '$components/event-card'; import FilterBar from '$components/filter-bar'; import Pagination from '$components/pagination'; // Data import { EVENT_CATEGORIES } from '$data/categories'; // Hooks import { useFilters } from '$hooks/use-filters'; // Internal Packages import { eventsQueryOptions } from '$lib/api/events'; import { filterEvents, sortEvents } from '$lib/utils/events'; // Styles import styles from '$styles/pages/events.module.scss'; export const Route = createFileRoute('/events')({ component: EventsPage, loader: ({ context }) => context.queryClient.ensureQueryData(eventsQueryOptions()), }); function EventsPage() { const { data: events } = useSuspenseQuery(eventsQueryOptions()); // ... component logic } ``` ### Notes on Import Sorting - **React imports**: Place React-specific imports (`useState`, `useEffect`, etc.) in the External Packages section - **TanStack imports**: Place `@tanstack/react-router`, `@tanstack/react-query`, and `@tanstack/start` in External Packages - **Route exports**: `createFileRoute` and `createAPIFileRoute` should be imported early in External Packages - **Server functions**: `createServerFn` belongs in External Packages - **Sass imports**: - Component styles: `$styles/components/component-name.module.scss` - Page styles: `$styles/pages/page-name.module.scss` - Global styles: `$styles/globals.scss` or `$styles/variables.scss` - Use CSS Modules (`.module.scss`) for component-scoped styles - **Assets**: Static files like images, fonts, icons should come early (after External Packages) since they're often used in JSX, and should be named like constants. - **Relative imports**: Avoid when possible; use path aliases instead - **Side-effect imports**: Place at the very top if absolutely necessary (e.g., `import './polyfills'`) ### Common Patterns ```tsx // Good - Clear categories with separation import type { Props } from '$lib/types'; import { createFileRoute } from '@tanstack/react-router'; import { useState } from 'react'; import ICON from '$assets/icons/star.svg'; import Button from '$components/button'; import { api } from '$lib/api'; import styles from '$styles/components/card.module.scss'; // Good - Multiple imports from same source on one line import { useCallback, useEffect, useState } from 'react'; import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; // Good - Destructured imports alphabetically import { api, auth, cache } from '$lib/api'; // Avoid - Mixed categories without separation import Button from '$components/button'; import styles from '$styles/app.module.scss'; import { useState } from 'react'; import type { Props } from './types'; import icon from '$assets/icon.svg'; ``` ## React Conventions - Prefer functional components with hooks - Use named exports for components - Avoid default exports except for route components ### Component Props - **Always** define an explicit props interface, even if empty - Props interfaces should be named `ComponentNameProps` - Place the interface directly above the component definition - Use `interface` (not `type`) for props to allow future extension #### Examples ```tsx // Good - Explicit empty interface interface HeaderProps {} export function Header(props: HeaderProps) { return
PEC ACM
; } // Good - With props interface ButtonProps { variant?: 'primary' | 'secondary'; onClick?: () => void; } export function Button({ variant = 'primary', onClick }: ButtonProps) { return ; } // Avoid - No explicit props type export function Footer({ children }) { return ; } ``` ### Extending HTML Element Props When creating wrapper components, extend the appropriate HTML attributes: ```tsx interface ButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary'; } interface InputProps extends React.InputHTMLAttributes { label?: string; } interface DivProps extends React.HTMLAttributes { customProp?: string; } ``` ## TypeScript Guidelines - Prefer `interface` for object shapes that may be extended - Prefer `type` for unions, intersections, and primitives - Avoid `any`; use `unknown` when type is truly unknown - Generic type parameters: single capital letter (`T`, `K`, `V`) or descriptive PascalCase (`TUser`, `TResponse`) ### Examples ```tsx // Good - Interface for extendable objects interface User { id: string; name: string; } interface Admin extends User { permissions: string[]; } // Good - Type for unions and primitives type Status = 'pending' | 'active' | 'inactive'; type ID = string | number; // Good - Generic with descriptive name function fetchData(url: string): Promise { // ... } // Avoid - Using 'any' function process(data: any) { // ... } // Good - Using 'unknown' instead function process(data: unknown) { if (typeof data === 'string') { // ... } } ```