-
Couldn't load subscription status.
- Fork 1
31 design and implement shared reusable search bar component #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nancymuyeh
wants to merge
32
commits into
main
Choose a base branch
from
31-design-and-implement-shared-reusable-search-bar-component
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 25 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
33c23f3
feat(ui): implement reusable SearchBar component
nancymuyeh dadff60
feat(ui): implement reusable SearchBar component
nancymuyeh 02fdf91
feat(ui): implement reusable SearchBar component
nancymuyeh 07f3c4a
feat(ui): implement reusable SearchBar component
nancymuyeh 581d387
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh 62a576d
fix(ui): mark SearchBar component prop as read-only
nancymuyeh 0f3de59
Merge branch '31-design-and-implement-shared-reusable-search-bar-comp…
nancymuyeh d886154
fix(ui): increase test coverage
nancymuyeh b198f93
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh 190f0b3
fix(ui): increase test coverage
nancymuyeh a24a3d9
fix(ui): refactor useSearchBar hook with extracted helper functions f…
nancymuyeh a74f693
fix(ui): refactor useSearchBar hook with extracted helper functions f…
nancymuyeh 390b514
Update packages/ui/src/components/SearchBar/SearchBar.view.tsx
nancymuyeh 7f2e481
fix(ui): refactor useSearchBar component for SonarQube compliance
nancymuyeh 5f5396c
fix(ui): refactor useSearchBar component for SonarQube compliance
nancymuyeh 216d993
fix(ui): refactor SearchBar component for SonarQube compliance
nancymuyeh e04b8bd
fix(ui): refactor SearchBar component for SonarQube compliance
nancymuyeh 3328a72
chore(ui): merge main
nancymuyeh 89b66c6
fix(ui): refactor SearchBar component for SonarQube compliance
nancymuyeh 1161ec1
fix(ui): refactor SearchBar component with more simplifies functionality
nancymuyeh cd8acd7
fix(ui): update searchbar variants and styling
nancymuyeh 1e6e858
fix(ui): remove unused dependencies
nancymuyeh 103e6c8
fix(ui): remove unused dependencies
nancymuyeh 0cd4017
fix(ui): update searchbar componenet to use global styling from style…
nancymuyeh 8ba9f4b
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh e885bf6
fix(ui): fix minor lint issue
nancymuyeh 9d2d22f
fix(ui): merge main
nancymuyeh 632273e
Merge remote-tracking branch 'origin' into 31-design-and-implement-sh…
nancymuyeh eafd972
Merge branch 'main' into 31-design-and-implement-shared-reusable-sear…
nancymuyeh 0fffcc2
Merge branch '31-design-and-implement-shared-reusable-search-bar-comp…
nancymuyeh 9999bf2
fix(ui): fix minor lint issue
nancymuyeh f87027b
fix(ui): fix minor lint issue
nancymuyeh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,349 @@ | ||
| # SearchBar Component | ||
|
|
||
| A reusable search input component with multiple variants for different use cases. | ||
|
|
||
| ## Overview | ||
|
|
||
| The SearchBar component provides a search input with search icon, optional clear functionality, and loading states. It comes in three variants to handle different UI scenarios. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```typescript | ||
| import { SearchBar } from "@fineract-apps/ui"; | ||
| import "@fineract-apps/ui/styles.css"; | ||
| ``` | ||
|
|
||
| ## Variants | ||
|
|
||
| ### Default | ||
| A standard search input with search icon and clear button. Updates the value as you type. | ||
|
|
||
| ```typescript | ||
| function BasicSearch() { | ||
| const [searchValue, setSearchValue] = useState(""); | ||
|
|
||
| return ( | ||
| <SearchBar | ||
| value={searchValue} | ||
| onValueChange={setSearchValue} | ||
| placeholder="Search transactions..." | ||
| /> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### With Button | ||
| Adds a "Search" button for explicit search actions. Good for triggered searches rather than real-time filtering. | ||
|
|
||
| ```typescript | ||
| function SearchWithButton() { | ||
| const [query, setQuery] = useState(""); | ||
|
|
||
| const handleSearch = (searchTerm: string) => { | ||
| console.log("Searching for:", searchTerm); | ||
| // Your search logic here | ||
| }; | ||
|
|
||
| return ( | ||
| <SearchBar | ||
| variant="withButton" | ||
| value={query} | ||
| onValueChange={setQuery} | ||
| onSearch={handleSearch} | ||
| placeholder="Search customers..." | ||
| /> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### Expandable | ||
| Starts as a search icon, expands into a full input when clicked. Useful for mobile layouts or when space is limited. | ||
|
|
||
| ```typescript | ||
| function ExpandableSearch() { | ||
| const [searchTerm, setSearchTerm] = useState(""); | ||
|
|
||
| return ( | ||
| <SearchBar | ||
| variant="expandable" | ||
| value={searchTerm} | ||
| onValueChange={setSearchTerm} | ||
| placeholder="Quick search..." | ||
| /> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Props | ||
|
|
||
| The SearchBar is designed to work seamlessly with TanStack Query for backend integration. Use `onValueChange` for real-time filtering of local data, and `onSearch` for triggered API calls when working with server-side search endpoints. | ||
|
|
||
| | Prop | Type | Default | Description | | ||
| |------|------|---------|-------------| | ||
| | `value` | `string` | `""` | Current search input value - control this with React state | | ||
| | `onValueChange` | `(value: string) => void` | - | Called on every keystroke - use for real-time filtering or updating search state | | ||
| | `onSearch` | `(value: string) => void` | - | Called when user presses Enter or clicks search button - use to trigger TanStack Query refetch | | ||
| | `placeholder` | `string` | `"Search..."` | Descriptive placeholder text to guide users (e.g., "Search customers by name...") | | ||
| | `variant` | `"default" \| "withButton" \| "expandable"` | `"default"` | Choose based on UX: `default` for filtering, `withButton` for explicit searches, `expandable` for mobile | | ||
| | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the search input | | ||
| | `disabled` | `boolean` | `false` | Whether the input is disabled | | ||
| | `isLoading` | `boolean` | `false` | Shows loading spinner when true | | ||
| | `showClear` | `boolean` | `true` | Whether to show clear button when there's text | | ||
| | `className` | `string` | - | Additional CSS classes | | ||
|
|
||
| ## Sizes | ||
|
|
||
| | Size | Height | Use Case | | ||
| |------|--------|----------| | ||
| | `sm` | 32px | Compact interfaces, mobile | | ||
| | `md` | 40px | Default size for most use cases | | ||
| | `lg` | 44px | Prominent search features | | ||
|
|
||
| ## Common Usage Patterns | ||
|
|
||
| ### Loading State | ||
|
|
||
| Show a spinner while performing async search operations. Use the `isLoading` prop to display loading feedback to users. | ||
|
|
||
| ```typescript | ||
| function SearchWithLoading() { | ||
| const [query, setQuery] = useState(""); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
|
|
||
| const handleSearch = async (searchTerm: string) => { | ||
| setIsLoading(true); | ||
| // Your async search logic | ||
| await fetchResults(searchTerm); | ||
| setIsLoading(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <SearchBar | ||
| variant="withButton" | ||
| value={query} | ||
| onValueChange={setQuery} | ||
| onSearch={handleSearch} | ||
| isLoading={isLoading} | ||
| placeholder="Search..." | ||
| /> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### Real-time Filtering | ||
|
|
||
| Filter a list of items as the user types. The search updates immediately without needing to press Enter or click a button. | ||
|
|
||
| ```typescript | ||
| function FilterableList() { | ||
| const [searchTerm, setSearchTerm] = useState(""); | ||
| const [items] = useState(["Apple", "Banana", "Cherry"]); | ||
|
|
||
| const filteredItems = items.filter(item => | ||
| item.toLowerCase().includes(searchTerm.toLowerCase()) | ||
| ); | ||
|
|
||
| return ( | ||
| <div> | ||
| <SearchBar | ||
| value={searchTerm} | ||
| onValueChange={setSearchTerm} | ||
| placeholder="Filter items..." | ||
| /> | ||
|
|
||
| <ul> | ||
| {filteredItems.map((item, index) => ( | ||
| <li key={index}>{item}</li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Keyboard Navigation | ||
|
|
||
| - **Enter**: Triggers search action | ||
| - **Escape**: Clears input and collapses expandable variant | ||
| - **Tab**: Focus navigation between elements | ||
|
|
||
| ## Styling | ||
|
|
||
| Add custom styles using the `className` prop: | ||
|
|
||
| ```typescript | ||
| <SearchBar | ||
| value={searchValue} | ||
| onValueChange={setSearchValue} | ||
| className="w-full max-w-md" | ||
| size="lg" | ||
| /> | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## Integration with Button Component | ||
|
|
||
| ### Button Usage Details | ||
|
|
||
| The SearchBar internally uses the Button component for all interactive elements: | ||
|
|
||
| #### Clear Button (X) | ||
| ```typescript | ||
| // Rendered when showClear={true} and there's input value | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| className="p-1 h-auto w-auto hover:bg-accent" | ||
| aria-label="Clear input" | ||
| > | ||
| <X className="h-3 w-3" /> | ||
| </Button> | ||
| ``` | ||
|
|
||
| #### Search Button (withButton variant) | ||
| ```typescript | ||
| // Rendered when variant="withButton" | ||
| <Button | ||
| size="sm" | ||
| className="px-3 py-1 text-sm" | ||
| onClick={handleSearch} | ||
| disabled={disabled} | ||
| > | ||
| Search | ||
| </Button> | ||
| ``` | ||
|
|
||
| #### Expandable Trigger Button | ||
| ```typescript | ||
| // Rendered when variant="expandable" and not expanded | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| className="cursor-pointer justify-center" | ||
| aria-label="Open search" | ||
| > | ||
| <Search className="h-4 w-4 text-muted-foreground" /> | ||
| </Button> | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| The SearchBar component includes comprehensive test coverage (97%+). Here are example test patterns: | ||
|
|
||
| ```typescript | ||
| import { render, screen, fireEvent } from '@testing-library/react'; | ||
| import { SearchBar } from './SearchBar'; | ||
|
|
||
| test('handles user input correctly', () => { | ||
| const mockOnValueChange = jest.fn(); | ||
|
|
||
| render( | ||
| <SearchBar | ||
| value="" | ||
| onValueChange={mockOnValueChange} | ||
| /> | ||
| ); | ||
|
|
||
| const input = screen.getByRole('textbox'); | ||
| fireEvent.change(input, { target: { value: 'test query' } }); | ||
|
|
||
| expect(mockOnValueChange).toHaveBeenCalledWith('test query'); | ||
| }); | ||
|
|
||
| test('button interactions work correctly', () => { | ||
| const mockOnSearch = jest.fn(); | ||
|
|
||
| render( | ||
| <SearchBar | ||
| variant="withButton" | ||
| value="test" | ||
| onValueChange={() => {}} | ||
| onSearch={mockOnSearch} | ||
| /> | ||
| ); | ||
|
|
||
| const searchButton = screen.getByRole('button', { name: /search/i }); | ||
| fireEvent.click(searchButton); | ||
|
|
||
| expect(mockOnSearch).toHaveBeenCalledWith('test'); | ||
| }); | ||
| ``` | ||
|
|
||
| ## Best Practices | ||
|
|
||
| ### Component Usage | ||
| 1. **Use appropriate variants**: Choose `expandable` for mobile/compact layouts, `withButton` for explicit search actions, `default` for real-time filtering | ||
| 2. **Size selection**: Use `sm` for compact interfaces, `md` for standard layouts, `lg` for prominent search features | ||
| 3. **Provide meaningful placeholders**: Help users understand what they can search for (e.g., "Search customers by name or ID...") | ||
|
|
||
| ### State Management | ||
| 4. **Handle empty states**: Show appropriate messages when no results are found | ||
| 5. **Loading states**: Always use `isLoading` prop during async operations to provide user feedback | ||
| 6. **Controlled inputs**: Always use controlled pattern with `value` and `onValueChange` for predictable behavior | ||
|
|
||
| ### Accessibility & UX | ||
| 7. **Keyboard navigation**: Test Enter key for search, Escape for clearing/closing, Tab for focus management | ||
| 8. **ARIA labels**: Provide descriptive labels for screen readers | ||
| 9. **Error handling**: Implement proper validation and error messaging | ||
| 10. **Performance**: Consider debouncing for expensive search operations (implement in your handler, not in the component) | ||
|
|
||
| ### Styling & Theme | ||
| 11. **Consistent theming**: Rely on CSS variables rather than custom classes when possible | ||
| 12. **Button consistency**: Let the integrated Button component handle all interactive styling | ||
| 13. **Responsive design**: Use different variants for different screen sizes | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Common Issues | ||
|
|
||
| **Input not updating:** | ||
| - Ensure you're using controlled input pattern with `value` and `onValueChange` | ||
| - Check that `onValueChange` callback is properly implemented | ||
| - Verify state updates are not being blocked by parent components | ||
|
|
||
| **Search not triggering:** | ||
| - Check that `onSearch` callback is properly implemented for `withButton` variant | ||
| - Verify Enter key handling works (should trigger automatically) | ||
| - Ensure the input has focus when pressing Enter | ||
|
|
||
| **Buttons not working:** | ||
| - Verify Button component is properly imported in your app | ||
| - Check that CSS variables are defined in your theme | ||
| - Ensure `@fineract-apps/ui/styles.css` is imported | ||
|
|
||
| **Styling issues:** | ||
| - Import UI library styles: `import "@fineract-apps/ui/styles.css"` | ||
| - Check that CSS variables are properly defined in your theme | ||
| - Use `className` prop for additional custom styling | ||
| - Verify Tailwind CSS is configured correctly | ||
|
|
||
| **Focus/accessibility problems:** | ||
| - Test keyboard navigation (Tab, Enter, Escape) | ||
| - Verify ARIA labels are working with screen readers | ||
| - Check focus-visible states are appearing correctly | ||
|
|
||
| **TypeScript errors:** | ||
| - Ensure proper prop types are used | ||
| - Check that `onValueChange` signature matches `(value: string) => void` | ||
| - Verify `onSearch` callback signature matches `(value: string) => void` | ||
|
|
||
| ### Performance Issues | ||
|
|
||
| **Slow search responses:** | ||
| - Implement debouncing in your search handler (not in the component) | ||
| - Consider using `isLoading` state during async operations | ||
| - Avoid heavy operations in `onValueChange` callbacks | ||
|
|
||
| ### Getting Help | ||
|
|
||
| For additional support: | ||
| - Check the component tests for comprehensive usage examples | ||
| - Review the TypeScript types for complete API documentation | ||
| - Consult the architecture guide for integration patterns | ||
| - See the Button component docs for styling details | ||
| - Check the CSS variables in `styles.css` for theming options | ||
|
|
||
| --- | ||
|
|
||
| *This component is part of the fineract-apps shared UI library. For technical issues or feature requests, please refer to the project's contribution guidelines.* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.