Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
33c23f3
feat(ui): implement reusable SearchBar component
nancymuyeh Sep 17, 2025
dadff60
feat(ui): implement reusable SearchBar component
nancymuyeh Sep 17, 2025
02fdf91
feat(ui): implement reusable SearchBar component
nancymuyeh Sep 18, 2025
07f3c4a
feat(ui): implement reusable SearchBar component
nancymuyeh Sep 18, 2025
581d387
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh Sep 18, 2025
62a576d
fix(ui): mark SearchBar component prop as read-only
nancymuyeh Sep 18, 2025
0f3de59
Merge branch '31-design-and-implement-shared-reusable-search-bar-comp…
nancymuyeh Sep 18, 2025
d886154
fix(ui): increase test coverage
nancymuyeh Sep 18, 2025
b198f93
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh Sep 18, 2025
190f0b3
fix(ui): increase test coverage
nancymuyeh Sep 18, 2025
a24a3d9
fix(ui): refactor useSearchBar hook with extracted helper functions f…
nancymuyeh Sep 18, 2025
a74f693
fix(ui): refactor useSearchBar hook with extracted helper functions f…
nancymuyeh Sep 18, 2025
390b514
Update packages/ui/src/components/SearchBar/SearchBar.view.tsx
nancymuyeh Sep 18, 2025
7f2e481
fix(ui): refactor useSearchBar component for SonarQube compliance
nancymuyeh Sep 19, 2025
5f5396c
fix(ui): refactor useSearchBar component for SonarQube compliance
nancymuyeh Sep 19, 2025
216d993
fix(ui): refactor SearchBar component for SonarQube compliance
nancymuyeh Sep 19, 2025
e04b8bd
fix(ui): refactor SearchBar component for SonarQube compliance
nancymuyeh Sep 19, 2025
3328a72
chore(ui): merge main
nancymuyeh Sep 19, 2025
89b66c6
fix(ui): refactor SearchBar component for SonarQube compliance
nancymuyeh Sep 19, 2025
1161ec1
fix(ui): refactor SearchBar component with more simplifies functionality
nancymuyeh Sep 19, 2025
cd8acd7
fix(ui): update searchbar variants and styling
nancymuyeh Sep 19, 2025
1e6e858
fix(ui): remove unused dependencies
nancymuyeh Sep 19, 2025
103e6c8
fix(ui): remove unused dependencies
nancymuyeh Sep 22, 2025
0cd4017
fix(ui): update searchbar componenet to use global styling from style…
nancymuyeh Sep 22, 2025
8ba9f4b
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh Sep 22, 2025
e885bf6
fix(ui): fix minor lint issue
nancymuyeh Sep 22, 2025
9d2d22f
fix(ui): merge main
nancymuyeh Sep 22, 2025
632273e
Merge remote-tracking branch 'origin' into 31-design-and-implement-sh…
nancymuyeh Sep 22, 2025
eafd972
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh Sep 22, 2025
0fffcc2
Merge branch '31-design-and-implement-shared-reusable-search-bar-comp…
nancymuyeh Sep 23, 2025
9999bf2
fix(ui): fix minor lint issue
nancymuyeh Sep 23, 2025
f87027b
fix(ui): fix minor lint issue
nancymuyeh Sep 23, 2025
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
83 changes: 83 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Fineract-apps Copilot Instructions

This document provides guidance for AI coding agents to effectively contribute to the `fineract-apps` repository.

## Project Overview

This is a monorepo for modern frontend applications built with Vite, React, TypeScript, and Tailwind CSS. The project is structured as a pnpm workspace.

- `frontend/`: Contains the individual React applications (`account-manager-app`, `branch-manager-app`, `cashier-app`).
- `packages/`: Contains shared code, including:
- `ui`: A shared component library.
- `config`: Shared configurations for tools like TypeScript and Vite.

## Key Technologies

- **Build Tool**: Vite
- **Framework**: React with TypeScript
- **Styling**: Tailwind CSS
- **Package Manager**: pnpm with workspaces
- **Code Quality**: Biome (for linting and formatting)
- **Git Hooks**: Husky and commitlint

## Development Workflow

### Getting Started

1. Install dependencies from the root of the project:
```bash
pnpm install
```

2. Run a specific application's development server:
```bash
pnpm --filter <app-name> dev
```
For example, to run `account-manager-app`:
```bash
pnpm --filter account-manager-app dev
```

### Building

To build all applications and packages:

```bash
pnpm build
```

To build a specific application:

```bash
pnpm --filter <app-name> build
```

### Testing

Tests are written with Jest and React Testing Library. Test files are located alongside the components they test (e.g., `Component.test.tsx`).

Run all tests:

```bash
pnpm test
```

### Code Quality

- **Linting and Formatting**: This project uses Biome.
- Check for issues: `pnpm lint`
- Format code: `pnpm format`
- **Commit Messages**: Commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification. This is enforced by a `commit-msg` hook.

## Architectural Patterns

- **Shared UI Components**: Reusable UI components are located in `packages/ui/src/components`. When creating a new component that might be used across multiple applications, it should be added here.
- **Shared Configuration**: Base configurations for TypeScript (`tsconfig.base.json`) and Vite (`vite.config.base.js`) are in `packages/config`. Application-specific configurations extend these base configs.
- **Monorepo Structure**: The use of pnpm workspaces allows for easy management of dependencies and inter-package linking. When adding a dependency to a specific app, use the `--filter` flag with `pnpm add`.

## Important Files

- `pnpm-workspace.yaml`: Defines the workspaces in the monorepo.
- `biome.json`: Configuration for the Biome toolchain.
- `packages/ui/src/index.ts`: The entry point for the shared UI component library.
- `frontend/*/vite.config.ts`: Vite configuration for each application, which extends the base configuration.
223 changes: 223 additions & 0 deletions docs/shared-components/searchbar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Search Bar Component

A clean and focused search component that provides a consistent search experience across all applications in the Fineract ecosystem. It supports the two most essential use cases: default search and search with button.

## Features

- **Live Search**: Real-time search with debouncing
- **Suggestions**: Both client-side and server-side suggestions support
- **Keyboard Navigation**: Full keyboard support for suggestion navigation
- **Accessibility**: ARIA compliant using Downshift
- **Loading States**: Visual feedback during async operations
- **Clear/Reset**: Optional clear button functionality
- **Two Core Variants**: Default and with button styles
- **Customizable**: Easy to extend with custom styling

## Installation

The SearchBar component is part of the `@fineract-apps/ui` package and is automatically available in all frontend applications.

## Basic Usage

```tsx
import { SearchBar } from "@fineract-apps/ui";

// Default search
<SearchBar
placeholder="Search accounts"
onSearch={(value) => console.log('Searching for:', value)}
/>

// With search button
<SearchBar
variant="withButton"
placeholder="Search with button"
onSearch={(value) => console.log('Searching for:', value)}
/>

// With suggestions
<SearchBar
placeholder="Search accounts"
suggestions={[
{ id: '1', label: 'Account #123' },
{ id: '2', label: 'Account #456' }
]}
onSuggestionSelect={(suggestion) => {
console.log('Selected:', suggestion);
}}
/>

// With async suggestions
<SearchBar
placeholder="Search accounts"
suggestionProvider={async (query, signal) => {
const res = await fetch(`/api/search?q=${query}`, { signal });
const data = await res.json();
return data.map(item => ({
id: item.id,
label: item.displayName
}));
}}
onSuggestionSelect={(suggestion) => {
navigate(`/accounts/${suggestion.id}`);
}}
/>
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| \`variant\` | \`"default" \| "withButton"\` | \`"default"\` | The visual style variant |
| \`size\` | \`"sm" \| "md" \| "lg"\` | \`"md"\` | The size of the search bar |
| \`placeholder\` | \`string\` | \`"Search..."\` | Placeholder text |
| \`className\` | \`string\` | - | Additional CSS classes |
| \`onSearch\` | \`(value: string) => void\` | - | Called when search is triggered |
| \`showClear\` | \`boolean\` | \`true\` | Whether to show the clear button |
| \`showSearchButton\` | \`boolean\` | \`false\` | Whether to show search button |
| \`suggestions\` | \`Suggestion[]\` | - | Array of suggestions for client-side filtering |
| \`suggestionProvider\` | \`(query: string, signal?: AbortSignal) => Promise<Suggestion[]>\` | - | Async function to fetch suggestions |
| \`onSuggestionSelect\` | \`(suggestion: Suggestion) => void\` | - | Called when a suggestion is selected |
| \`isLoading\` | \`boolean\` | \`false\` | Shows loading indicator |
| \`minChars\` | \`number\` | \`2\` | Minimum characters before showing suggestions |
| \`debounceMs\` | \`number\` | \`250\` | Debounce delay for suggestions in ms |
| \`maxSuggestions\` | \`number\` | \`8\` | Maximum number of suggestions to show |

## Variants

### Default
Standard search bar with icon and optional clear button.
```tsx
<SearchBar placeholder="Search accounts" />
```

### With Button
Includes a search button, useful for explicit search actions.
```tsx
<SearchBar
variant="withButton"
placeholder="Search with button"
/>
```

### Different Sizes
Control the size of the search bar:
```tsx
<SearchBar size="sm" placeholder="Small search" />
<SearchBar size="md" placeholder="Medium search" />
<SearchBar size="lg" placeholder="Large search" />
```

## Accessibility

The SearchBar component follows WAI-ARIA guidelines and includes:

- Proper ARIA roles and attributes (`combobox`, `listbox`, `option`)
- Keyboard navigation:
- `↑/↓`: Navigate through suggestions
- `Enter`: Select suggestion or trigger search
- `Esc`: Close suggestions
- Clear button has proper aria-label
- Loading state announcements
- Focus management

## Examples

### Basic Search
```tsx
<SearchBar
placeholder="Search accounts"
onSearch={(value) => {
console.log('Searching:', value);
}}
/>
```

### With Async Suggestions
```tsx
const [loading, setLoading] = useState(false);

<SearchBar
placeholder="Search accounts"
suggestionProvider={async (query, signal) => {
setLoading(true);
try {
const res = await fetch(`/api/search?q=${query}`, { signal });
const data = await res.json();
return data.map(item => ({
id: item.id,
label: item.name
}));
} finally {
setLoading(false);
}
}}
isLoading={loading}
onSuggestionSelect={(suggestion) => {
navigate(`/accounts/${suggestion.id}`);
}}
/>
```

### Customization
```tsx
// Custom styling
<SearchBar
placeholder="Custom search"
className="max-w-md mx-auto"
size="lg"
/>

// Different configurations
<SearchBar
variant="withButton"
showClear={false}
debounceMs={500}
minChars={3}
/>
```

## Technical Details

The SearchBar uses:
- Downshift for accessibility and keyboard interactions
- class-variance-authority for variant management
- AbortController for cancelling outdated requests
- Debouncing for performance

## Best Practices

1. **Error Handling**: Always handle errors in your suggestion provider:
```tsx
const handleSuggestions = async (query: string, signal?: AbortSignal) => {
try {
const data = await fetchSuggestions(query, signal);
return data.map(toSuggestion);
} catch (err) {
if (err.name === 'AbortError') return [];
console.error('Search error:', err);
return []; // Or show error state
}
};
```

2. **Performance**: Use debouncing and cancellation:
```tsx
<SearchBar
debounceMs={300} // Adjust based on your needs
minChars={2} // Prevent unnecessary searches
maxSuggestions={8} // Limit number of results
/>
```

3. **Responsive Design**: Use appropriate sizes and styling:
```tsx
<SearchBar
size="md"
className="hidden md:block" // Hide on mobile
/>
<SearchBar
size="sm"
className="block md:hidden" // Show on mobile
/>
```
75 changes: 66 additions & 9 deletions frontend/account-manager-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,78 @@
import { Button, SearchBar } from "@fineract-apps/ui";
import { useState } from "react";
import "@fineract-apps/ui/styles.css";
import { Button } from "@fineract-apps/ui";

const mockSuggestions = [
{ id: "1", label: "John Doe - #123456" },
{ id: "2", label: "Jane Smith - #789012" },
{ id: "3", label: "Bob Wilson - #345678" },
];

function App() {
const [loading, setLoading] = useState(false);

const handleSearch = async (value: string) => {
console.log("Searching for:", value);
};

const mockSuggestionProvider = async (q: string) => {
setLoading(true);
try {
// Simulate API delay
await new Promise((resolve) => setTimeout(resolve, 500));
return mockSuggestions.filter((s) =>
s.label.toLowerCase().includes(q.toLowerCase()),
);
} finally {
setLoading(false);
}
};

return (
<div style={{ padding: "20px" }}>
<h1>Account Manager App</h1>
<p>
This button is a shared component from the '@fineract-apps/ui' package.
</p>
<Button>Click me!</Button>
<Button>Click me!</Button>

<Button>Click me!</Button>
<h2 className="mt-8 mb-4 text-lg font-semibold">Default SearchBar</h2>
<div style={{ maxWidth: 420 }}>
<SearchBar
placeholder="Search accounts"
onSearch={handleSearch}
suggestionProvider={mockSuggestionProvider}
isLoading={loading}
/>
</div>

<Button>Click me!</Button>
<h2 className="mt-8 mb-4 text-lg font-semibold">Simple Usage</h2>
<div style={{ maxWidth: 320 }}>
<SearchBar
size="sm"
placeholder="Quick search"
onSearch={handleSearch}
/>
</div>

<h2 className="mt-8 mb-4 text-lg font-semibold">With Search Button</h2>
<div style={{ maxWidth: 420 }}>
<SearchBar
variant="withButton"
placeholder="Search with button"
onSearch={handleSearch}
/>
</div>

<Button className="bg-red-500">Click me!</Button>
<h2 className="mt-8 mb-4 text-lg font-semibold">Static Suggestions</h2>
<div style={{ maxWidth: 420 }}>
<SearchBar
placeholder="Search from list"
suggestions={mockSuggestions}
onSearch={handleSearch}
onSuggestionSelect={(suggestion) =>
console.log("Selected:", suggestion)
}
/>
</div>

<Button>Click me!</Button>
</div>
);
}
Expand Down
Loading
Loading