Skip to content

Commit dce1b66

Browse files
authored
shadcn alert (#159)
* Refactor Alert component to use shadcn styles and update related tests and stories * Update test scripts in package.json for improved execution and coverage reporting * Update copilot instructions to clarify icon usage and component structure * Add Fontsource to technology stack in copilot instructions * Update SHADCN guide to clarify component wrapping guidelines and provide examples * Update font variable in theme to use 'Noto Sans Variable' * Fix data-testid attribute casing in ErrorAlert component
1 parent 6094438 commit dce1b66

13 files changed

Lines changed: 328 additions & 290 deletions

File tree

.github/copilot-instructions.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ The React application leverages a modern technology stack to ensure optimal perf
3131
- **HTTP Client**: Axios
3232
- **Styling**: TailwindCSS
3333
- **Component Library**: shadcn/ui
34-
- **Font Awesome**: icons
34+
- **Icons**: Font Awesome and Lucide
35+
- **Fonts**: Fontsource
3536
- **Utility Library**: Lodash
3637
- **Date Library**: date-fns
3738
- **Unit Testing**: Vitest
@@ -53,7 +54,7 @@ src
5354
useGetCurrentUser.ts # API hook for fetching current user
5455
useGetCurrentUser.test.ts # Unit test for useGetCurrentUser
5556
/components
56-
/ui # shadcn/ui components
57+
/shadcn # shadcn/ui components
5758
button.tsx # Reusable button component from shadcn/ui
5859
input.tsx # Reusable input component from shadcn/ui
5960
label.tsx # Reusable label component from shadcn/ui
@@ -142,8 +143,8 @@ package.json # Project dependencies and scripts
142143

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

161163
### Configuration
162164

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

196198
After installing shadcn/ui:
197199

198-
- Reusable UI components like `<Button />`, `<Input />`, `<Label />` live in `src/common/components/ui/`
199-
- You can override and customize each component’s styles with Tailwind and variants
200+
- Reusable UI components like `<Button />`, `<Input />`, `<Label />` live in `src/common/components/shadcn/`
201+
- DO override and customize each component’s styles with Tailwind and variants.
202+
- 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.
200203
- Recommended: use the CLI to scaffold new components:
201204

202205
```bash

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,21 @@ The page will reload when source files are saved.
169169

170170
### `npm test`
171171

172+
Executes the unit tests once. See the Vitest documentation about [running tests](https://vitest.dev/guide/cli.html) for more information.
173+
174+
### `npm run test:coverage`
175+
176+
Executes the unit tests once, producing a code coverage report.
177+
178+
### `npm run test:watch`
179+
172180
Launches the test runner in the interactive watch mode.
173-
See the section about [running tests](https://vitest.dev/guide/cli.html) for more information.
174181

175182
### `npm run test:ci`
176183

177-
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.
184+
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.
178185

179-
A detailed test coverage report is created in the `./coverage` directory.
186+
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.
180187

181188
> **NOTE:** This is the command which should be utilized by CI/CD platforms.
182189
@@ -191,10 +198,22 @@ It correctly bundles React in production mode and optimizes the build for the be
191198

192199
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).
193200

201+
### `npm run format`
202+
203+
Runs the Prettier static code analysis and fixes problems identified to comply with Prettier formatting rules. See `.prettierrc` and `.prettierignore`.
204+
205+
### `npm run format:check`
206+
207+
Runs the Prettier static code analysis and prints the results to the console.
208+
194209
### `npm run lint`
195210

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

213+
### `npm run lint:fix`
214+
215+
Runs the eslint static code analysis and updates source code to fix problems.
216+
198217
## `npm run storybook`
199218

200219
Starts the [Storybook][storybook] UI. Open [http://localhost:6006](http://localhost:6006) to view it in the browser.

docs/SHADCN_GUIDE.md

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,25 +245,59 @@ Dependencies: clsx, class-variance-authority
245245

246246
### Wrapping shadcn Components
247247

248-
Create wrapper components to standardize usage across your application:
248+
**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.
249+
250+
#### Example: Alert Components
251+
252+
The project provides a great example of this pattern with the Alert components:
253+
254+
**When NOT to Wrap — Modify Variants/Styles Directly:**
255+
256+
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:
249257

250258
```typescript
251-
// src/common/components/Button/Button.tsx
252-
import { Button as ShadcnButton, ButtonProps } from 'common/components/shadcn/button';
259+
const alertVariants = cva('group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 ...', {
260+
variants: {
261+
variant: {
262+
default: 'bg-card text-card-foreground',
263+
destructive: 'bg-card text-destructive *:[svg]:text-current',
264+
success: 'bg-card text-success *:[svg]:text-current',
265+
warning: 'bg-card text-warning *:[svg]:text-current',
266+
},
267+
},
268+
defaultVariants: {
269+
variant: 'default',
270+
},
271+
});
272+
```
253273

254-
export interface CustomButtonProps extends ButtonProps {
255-
isLoading?: boolean;
256-
}
274+
Style and variant adjustments stay here, not in wrapper components.
257275

258-
export const Button = ({ isLoading, children, ...props }: CustomButtonProps) => {
276+
**When to Wrap — Add Behavior/Functionality:**
277+
278+
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:
279+
280+
```typescript
281+
const ErrorAlert = ({ className, description, testId = 'alert-error', title, ...props }: ErrorAlertProps) => {
259282
return (
260-
<ShadcnButton disabled={isLoading} {...props}>
261-
{isLoading ? 'Loading...' : children}
262-
</ShadcnButton>
283+
<Alert variant="destructive" className={cn(className)} data-testId={testId} {...props}>
284+
<AlertCircleIcon />
285+
{title && <AlertTitle data-testId={`${testId}-title`}>{title}</AlertTitle>}
286+
<AlertDescription data-testId={`${testId}-description`}>{description}</AlertDescription>
287+
</Alert>
263288
);
264289
};
265290
```
266291

292+
This wrapper adds behavior-specific logic while reusing the base Alert's styles and CVA configuration.
293+
294+
#### General Pattern
295+
296+
Follow this approach for all shadcn components:
297+
298+
- **Modify the shadcn component** (`src/common/components/shadcn/*.tsx`) for all visual customizations, variants, and CVA configuration
299+
- **Create a wrapper** (`src/common/components/ComponentName/*.tsx`) only when adding behavior, functionality, or context-specific logic
300+
267301
### Testing shadcn Components
268302

269303
When testing components that use shadcn:

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
"prepare": "husky",
2525
"preview": "vite preview",
2626
"storybook": "storybook dev -p 6006",
27-
"test": "vitest",
28-
"test:coverage": "vitest --coverage --coverage.all=false",
27+
"test": "vitest run",
28+
"test:coverage": "vitest run --coverage",
2929
"test:ci": "vitest run --coverage --silent",
30-
"test:ui": "vitest --ui --coverage --silent"
30+
"test:ui": "vitest --ui --coverage --silent",
31+
"test:watch": "vitest"
3132
},
3233
"dependencies": {
3334
"@fontsource-variable/noto-sans": "5.2.10",

src/common/components/Alert/Alert.tsx

Lines changed: 0 additions & 83 deletions
This file was deleted.

src/common/components/Alert/ErrorAlert.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { AlertCircleIcon } from 'lucide-react';
2+
13
import { cn } from 'common/utils/css';
2-
import { FAIconProps } from '../Icon/FAIcon';
3-
import Alert, { AlertProps } from './Alert';
4+
import { Alert, AlertDescription, AlertTitle } from '../shadcn/alert';
5+
import { BaseComponentProps } from 'common/utils/types';
46

57
/**
68
* Properties for the `ErrorAlert` component.
79
*/
8-
export interface ErrorAlertProps extends Omit<AlertProps, 'variant'>, Partial<Pick<FAIconProps, 'icon'>> {
10+
export interface ErrorAlertProps extends BaseComponentProps {
911
title?: string;
1012
description: string;
1113
}
@@ -14,19 +16,12 @@ export interface ErrorAlertProps extends Omit<AlertProps, 'variant'>, Partial<Pi
1416
* The `ErrorAlert` component renders a bespoke `Alert` layout for error
1517
* messages.
1618
*/
17-
const ErrorAlert = ({
18-
className,
19-
description,
20-
icon = 'circleExclamation',
21-
testId = 'alert-error',
22-
title,
23-
...props
24-
}: ErrorAlertProps) => {
19+
const ErrorAlert = ({ className, description, testId = 'alert-error', title, ...props }: ErrorAlertProps) => {
2520
return (
26-
<Alert variant="danger" className={cn(className)} testId={testId} {...props}>
27-
<Alert.Icon icon={icon} testId={`${testId}-icon`} />
28-
{title && <Alert.Title testId={`${testId}-title`}>{title}</Alert.Title>}
29-
<Alert.Description testId={`${testId}-description`}>{description}</Alert.Description>
21+
<Alert variant="destructive" className={cn(className)} data-testid={testId} {...props}>
22+
<AlertCircleIcon />
23+
{title && <AlertTitle data-testid={`${testId}-title`}>{title}</AlertTitle>}
24+
<AlertDescription data-testid={`${testId}-description`}>{description}</AlertDescription>
3025
</Alert>
3126
);
3227
};

src/common/components/Alert/__stories__/ErrorAlert.stories.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ const meta = {
77
component: ErrorAlert,
88
tags: ['autodocs'],
99
argTypes: {
10-
children: { description: 'The content.' },
1110
className: { description: 'Additional CSS classes.' },
1211
description: { description: 'The detailed description.' },
13-
icon: { description: 'The icon name.', type: 'string' },
1412
testId: { description: 'The test identifier.', type: 'string' },
1513
title: { description: 'The title.' },
1614
},
@@ -33,11 +31,3 @@ export const DescriptionOnly: Story = {
3331
description: 'Some problem has occurred. Please check your work and try again.',
3432
},
3533
};
36-
37-
export const WithAlternateIcon: Story = {
38-
args: {
39-
icon: 'phone',
40-
title: 'This is bad!',
41-
description: 'You probably need to call customer support.',
42-
},
43-
};

0 commit comments

Comments
 (0)