Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ The React application leverages a modern technology stack to ensure optimal perf
- **HTTP Client**: Axios
- **Styling**: TailwindCSS
- **Component Library**: shadcn/ui
- **Font Awesome**: icons
- **Icons**: Font Awesome and Lucide
- **Fonts**: Fontsource
- **Utility Library**: Lodash
- **Date Library**: date-fns
- **Unit Testing**: Vitest
Expand All @@ -53,7 +54,7 @@ src
useGetCurrentUser.ts # API hook for fetching current user
useGetCurrentUser.test.ts # Unit test for useGetCurrentUser
/components
/ui # shadcn/ui components
/shadcn # shadcn/ui components
button.tsx # Reusable button component from shadcn/ui
input.tsx # Reusable input component from shadcn/ui
label.tsx # Reusable label component from shadcn/ui
Expand Down Expand Up @@ -142,8 +143,8 @@ package.json # Project dependencies and scripts

- Use **functional components** with hooks.
- Use **TypeScript** for type safety.
- Return **JSX.Element** or **false** from components.
- Use arrow functions for components.
- Return JSX or `null` from components.
- Use the `data-testid` attribute to assist with testing.
- Use default exports for components.
- Use a **testId** prop for components that need to be tested, defaulting to the component name in kebab-case.
Expand All @@ -157,6 +158,7 @@ package.json # Project dependencies and scripts
- Use **Tailwind CSS** for styling.
- Apply base styles in `src/index.css`
- Use CSS variables for theming (index.css).
- Use class-variance-authority (CVA) for reusable component styles and variants, see: `src/common/utils/css.ts`.

### Configuration

Expand Down Expand Up @@ -195,8 +197,9 @@ package.json # Project dependencies and scripts

After installing shadcn/ui:

- Reusable UI components like `<Button />`, `<Input />`, `<Label />` live in `src/common/components/ui/`
- You can override and customize each component’s styles with Tailwind and variants
- Reusable UI components like `<Button />`, `<Input />`, `<Label />` live in `src/common/components/shadcn/`
- DO override and customize each component’s styles with Tailwind and variants.
- DO NOT modify shadcn/ui underlying component logic or structure, as this will make it difficult to maintain and update in the future. Instead create wrapper components if you need to add additional functionality or logic.
- Recommended: use the CLI to scaffold new components:

```bash
Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,21 @@ The page will reload when source files are saved.

### `npm test`

Executes the unit tests once. See the Vitest documentation about [running tests](https://vitest.dev/guide/cli.html) for more information.

### `npm run test:coverage`

Executes the unit tests once, producing a code coverage report.

### `npm run test:watch`

Launches the test runner in the interactive watch mode.
See the section about [running tests](https://vitest.dev/guide/cli.html) for more information.

### `npm run test:ci`

Executes the test runner in `CI` mode and produces a coverage report. With `CI` mode enabled, the test runner executes all tests one time and prints a summary report to the console. A code coverage report is printed to the console immediately following the test summary.
Executes the test runner in `CI` mode and produces a coverage report. With `CI` mode enabled, the test runner executes all tests one time silently and prints a summary report to the console. A code coverage report is printed to the console immediately following the test summary.

A detailed test coverage report is created in the `./coverage` directory.
A detailed test coverage report is created in the `./coverage` directory. Additional report formats, for example a JSON summary report, are produced which may be injested by external reporting tools.

> **NOTE:** This is the command which should be utilized by CI/CD platforms.

Expand All @@ -191,10 +198,22 @@ It correctly bundles React in production mode and optimizes the build for the be

See the official guide for more information about [building for production](https://vitejs.dev/guide/build.html) and [deploying a static site](https://vitejs.dev/guide/static-deploy.html).

### `npm run format`

Runs the Prettier static code analysis and fixes problems identified to comply with Prettier formatting rules. See `.prettierrc` and `.prettierignore`.

### `npm run format:check`

Runs the Prettier static code analysis and prints the results to the console.

### `npm run lint`

Runs the eslint static code analysis and prints the results to the console.

### `npm run lint:fix`

Runs the eslint static code analysis and updates source code to fix problems.

## `npm run storybook`

Starts the [Storybook][storybook] UI. Open [http://localhost:6006](http://localhost:6006) to view it in the browser.
Expand Down
54 changes: 44 additions & 10 deletions docs/SHADCN_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,25 +245,59 @@ Dependencies: clsx, class-variance-authority

### Wrapping shadcn Components

Create wrapper components to standardize usage across your application:
**Only wrap shadcn components when you need to adjust their behavior.** For style, variant, or CVA configuration changes, modify the component directly in `src/common/components/shadcn/` instead. This keeps base components clean and centralizes visual variants in one place.

#### Example: Alert Components

The project provides a great example of this pattern with the Alert components:

**When NOT to Wrap — Modify Variants/Styles Directly:**

The base [shadcn Alert component](src/common/components/shadcn/alert.tsx) defines all visual variants (default, destructive, success, warning) and style configurations directly using CVA:

```typescript
// src/common/components/Button/Button.tsx
import { Button as ShadcnButton, ButtonProps } from 'common/components/shadcn/button';
const alertVariants = cva('group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 ...', {
variants: {
variant: {
default: 'bg-card text-card-foreground',
destructive: 'bg-card text-destructive *:[svg]:text-current',
success: 'bg-card text-success *:[svg]:text-current',
warning: 'bg-card text-warning *:[svg]:text-current',
},
},
defaultVariants: {
variant: 'default',
},
});
```

export interface CustomButtonProps extends ButtonProps {
isLoading?: boolean;
}
Style and variant adjustments stay here, not in wrapper components.

export const Button = ({ isLoading, children, ...props }: CustomButtonProps) => {
**When to Wrap — Add Behavior/Functionality:**

The [ErrorAlert wrapper](src/common/components/Alert/ErrorAlert.tsx) extends the Alert for a specific use case by adding optional title handling and structured error presentation:

```typescript
const ErrorAlert = ({ className, description, testId = 'alert-error', title, ...props }: ErrorAlertProps) => {
return (
<ShadcnButton disabled={isLoading} {...props}>
{isLoading ? 'Loading...' : children}
</ShadcnButton>
<Alert variant="destructive" className={cn(className)} data-testId={testId} {...props}>
<AlertCircleIcon />
{title && <AlertTitle data-testId={`${testId}-title`}>{title}</AlertTitle>}
<AlertDescription data-testId={`${testId}-description`}>{description}</AlertDescription>
</Alert>
);
};
```

This wrapper adds behavior-specific logic while reusing the base Alert's styles and CVA configuration.

#### General Pattern

Follow this approach for all shadcn components:

- **Modify the shadcn component** (`src/common/components/shadcn/*.tsx`) for all visual customizations, variants, and CVA configuration
- **Create a wrapper** (`src/common/components/ComponentName/*.tsx`) only when adding behavior, functionality, or context-specific logic

### Testing shadcn Components

When testing components that use shadcn:
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
"prepare": "husky",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"test": "vitest",
"test:coverage": "vitest --coverage --coverage.all=false",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:ci": "vitest run --coverage --silent",
"test:ui": "vitest --ui --coverage --silent"
"test:ui": "vitest --ui --coverage --silent",
"test:watch": "vitest"
},
"dependencies": {
"@fontsource-variable/noto-sans": "5.2.10",
Expand Down
83 changes: 0 additions & 83 deletions src/common/components/Alert/Alert.tsx

This file was deleted.

25 changes: 10 additions & 15 deletions src/common/components/Alert/ErrorAlert.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { AlertCircleIcon } from 'lucide-react';

import { cn } from 'common/utils/css';
import { FAIconProps } from '../Icon/FAIcon';
import Alert, { AlertProps } from './Alert';
import { Alert, AlertDescription, AlertTitle } from '../shadcn/alert';
import { BaseComponentProps } from 'common/utils/types';

/**
* Properties for the `ErrorAlert` component.
*/
export interface ErrorAlertProps extends Omit<AlertProps, 'variant'>, Partial<Pick<FAIconProps, 'icon'>> {
export interface ErrorAlertProps extends BaseComponentProps {
title?: string;
description: string;
}
Expand All @@ -14,19 +16,12 @@ export interface ErrorAlertProps extends Omit<AlertProps, 'variant'>, Partial<Pi
* The `ErrorAlert` component renders a bespoke `Alert` layout for error
* messages.
*/
const ErrorAlert = ({
className,
description,
icon = 'circleExclamation',
testId = 'alert-error',
title,
...props
}: ErrorAlertProps) => {
const ErrorAlert = ({ className, description, testId = 'alert-error', title, ...props }: ErrorAlertProps) => {
return (
<Alert variant="danger" className={cn(className)} testId={testId} {...props}>
<Alert.Icon icon={icon} testId={`${testId}-icon`} />
{title && <Alert.Title testId={`${testId}-title`}>{title}</Alert.Title>}
<Alert.Description testId={`${testId}-description`}>{description}</Alert.Description>
<Alert variant="destructive" className={cn(className)} data-testid={testId} {...props}>
<AlertCircleIcon />
{title && <AlertTitle data-testid={`${testId}-title`}>{title}</AlertTitle>}
<AlertDescription data-testid={`${testId}-description`}>{description}</AlertDescription>
</Alert>
);
};
Expand Down
10 changes: 0 additions & 10 deletions src/common/components/Alert/__stories__/ErrorAlert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ const meta = {
component: ErrorAlert,
tags: ['autodocs'],
argTypes: {
children: { description: 'The content.' },
className: { description: 'Additional CSS classes.' },
description: { description: 'The detailed description.' },
icon: { description: 'The icon name.', type: 'string' },
testId: { description: 'The test identifier.', type: 'string' },
title: { description: 'The title.' },
},
Expand All @@ -33,11 +31,3 @@ export const DescriptionOnly: Story = {
description: 'Some problem has occurred. Please check your work and try again.',
},
};

export const WithAlternateIcon: Story = {
args: {
icon: 'phone',
title: 'This is bad!',
description: 'You probably need to call customer support.',
},
};
Loading