Skip to content

Commit a113d2a

Browse files
committed
Pull request #59: EOA-5335: Add Filter bar component to docs CR
Merge in DEV/colibri-docs from feature/EOA-5335_Create_filter_bar_documentation_CR to release/current * commit '587842cdae29515635cf513a741386512256d36f': EOA-5335: Addressing PR comments and bug fixes EOA-5335: updated Filter Bar doc and stories
2 parents dcef0c2 + 587842c commit a113d2a

File tree

3 files changed

+647
-1
lines changed

3 files changed

+647
-1
lines changed

.storybook/preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ const preview: Preview = {
177177
controls: { expanded: true, hideNoControlsWarning: true },
178178
options: {
179179
storySort: {
180-
order: ['Welcome', 'Atoms', 'Molecules', 'Organisms', 'Tokens'],
180+
order: ['Welcome', 'Atoms', 'Molecules', 'Organisms', 'Patterns', 'Tokens'],
181181
},
182182
},
183183
docs: {
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
import React from 'react';
2+
import { Canvas, Meta, Title, Subtitle, Unstyled } from '@storybook/blocks';
3+
import { Callout } from '../../_storybook/components';
4+
import * as stories from './col-filter-bar.stories';
5+
6+
<Meta of={stories} />
7+
8+
<div className="col-doc__wrapper">
9+
<div className="col-doc__container">
10+
<Unstyled>
11+
<Title>Filter Bar</Title>
12+
<div className="col-intro-text">
13+
A Filter Bar is a reusable composition pattern built from Colibri components (not a single component). It provides search and filtering controls within a view, allowing users to select and apply filters to refine the displayed results.
14+
</div>
15+
16+
<Subtitle>Table of contents</Subtitle>
17+
- [Overview](#overview)
18+
- [Usage](#usage)
19+
- [Example](#example)
20+
- [Variants](#variants)
21+
- [Accessibility](#accessibility)
22+
23+
## Overview
24+
<div>
25+
The Filter Bar provides filtering and search controls within a view, allowing users to select and apply filters to narrow the displayed results.
26+
It's commonly used above tables, lists, dashboards, and search results.
27+
28+
Place a Filter Bar above data-heavy content such as tables, lists, dashboards, and search results. Configure quick filters directly in the bar and move advanced options into a secondary surface (e.g., a drawer) when necessary. Keep the composition consistent across pages to build user familiarity.
29+
30+
<Callout variant="tip" icon="🧩">
31+
This is a composition pattern using primitives such as `col-toolbar`, `col-search-bar`, `col-select`, `col-button`, `col-badge`, and (optionally) `col-drawer` for advanced filters.
32+
Treat it as a recipe you can copy and adapt rather than a single drop-in component.
33+
</Callout>
34+
</div>
35+
36+
## Usage
37+
1. Install the library and styles:
38+
```bash
39+
npm install @telesign/colibri
40+
```
41+
2. Register components (all or the ones you use):
42+
```ts
43+
import { registerAllComponents, registerColibriComponents } from '@telesign/colibri';
44+
import '@telesign/colibri/styles/styles.css';
45+
46+
// Option A: everything
47+
registerAllComponents();
48+
49+
// Option B: only what's needed
50+
registerColibriComponents([ColToolbar, ColSearchBar, ColSelect, ColButton, ColBadge, ColDrawer, ColListMenu, ColListMenuItem]);
51+
```
52+
3. Compose the Filter Bar using the building blocks below (see example):
53+
- Search input: `col-search-bar`
54+
- Filter controls: `col-select`, date range pickers, toggles, etc.
55+
- Actions: primary button(s), filter button with counter `col-badge`
56+
- Optional advanced filters panel: `col-drawer` with form fields
57+
58+
## Examples
59+
60+
<Callout variant="info" icon="💡">
61+
To help understand how to use this pattern, we provide helpful sample code, simply click `Show code` to copy/paste it and try it out.
62+
The examples below use inline scripts for simplicity. In a real project, you would typically separate the JavaScript logic into its own file/module.
63+
</Callout>
64+
65+
### Basic Example
66+
67+
A simple Filter Bar with a search input and a few filter buttons:
68+
69+
<Canvas of={stories.BasicFilterBar} />
70+
71+
Using `<col-toolbar>` to arrange the elements horizontally, the needed input components, like `<col-search-bar>` and `<col-select>`, you can quickly build a functional filter bar.
72+
73+
In order to capture the submit event and associate the items to the form, you can use the `form` attribute on the inputs and buttons:
74+
75+
```html
76+
<!-- Create the form as a sibling component of the toolbar -->
77+
<form id="basic-form"></form>
78+
79+
<!-- And then on each "input" add the form attribute passing the form's id -->
80+
<col-seach-bar form="basic-form" ...></col-search-bar>
81+
<col-select form="basic-form" ...></col-select>
82+
<col-button form="basic-form" type="submit">Search</col-button>
83+
```
84+
85+
This way, we ensure the items are linked to the form without loosing the styles the toolbar provides.
86+
87+
Then you can listen to the form's `submit` event to capture the values and perform the search/filtering logic.
88+
89+
```javascript
90+
const basicForm = document.getElementById('basic-form');
91+
92+
basicForm.addEventListener('submit', event => {
93+
event.preventDefault();
94+
const formData = new FormData(basicForm);
95+
const filters = Object.fromEntries(formData.entries());
96+
97+
console.log('[Basic Search] Search with filters:', filters);
98+
// Perform search/filtering logic here
99+
});
100+
```
101+
102+
### Advanced Search Example - Filter Button with Drawer
103+
104+
Sometimes you may need more advanced filtering options that don't fit in the main bar. In this case, the Design System recommends using a "Filter Button":
105+
106+
<Canvas of={stories.FilterButton} />
107+
108+
<Callout variant="info" icon="ℹ️">
109+
If you set values in any of the advanced filters in the ColDrawer component, you can see the ColBadge counter update, try it out!
110+
</Callout>
111+
112+
The Filter Button includes a badge that shows the number of active filters. When clicked, it opens a Drawer with additional filtering options.
113+
114+
To create the filter button simply use a `col-button` with a `col-badge` inside, and add the logic to open the drawer when clicked:
115+
116+
```html
117+
<col-button id="filter-button" aria-label="Open filters" onclick="openDrawer()">
118+
<col-icon name="filter" size="16"></col-icon>
119+
<col-badge id="filter-counter" aria-label="Active filters count">0</col-badge>
120+
</col-button>
121+
```
122+
123+
Then, implement the logic to open/close the drawer and update the badge count based on the selected filters:
124+
125+
```javascript
126+
const drawer = document.querySelector('#filter-drawer');
127+
const openDrawer = () => (drawer.active = true);
128+
129+
// the closeDrawer can be called from the buttons inside the drawer
130+
const closeDrawer = () => (drawer.active = false);
131+
```
132+
133+
An example of the JavaScript code needed to update the badge count when filters are applied:
134+
135+
```javascript
136+
137+
// Grab drawer elements to monitor changes
138+
const exampleDrawerElements = document
139+
.querySelector('#example-drawer-content')
140+
.querySelectorAll('col-text-field, col-select, col-number-field');
141+
142+
// Centralize badge update logic
143+
const updateExampleBadge = () => {
144+
let total = 0;
145+
exampleDrawerElements.forEach(el => {
146+
if (el && typeof el.value !== 'undefined' && String(el.value || '').trim()) total += 1;
147+
});
148+
const filterCounter = document.querySelector('#example-filter-counter');
149+
if (filterCounter) filterCounter.textContent = String(total);
150+
};
151+
152+
// Listen to changes on drawer elements to update badge
153+
exampleDrawerElements.forEach(el => {
154+
el?.addEventListener('change', updateExampleBadge);
155+
el?.addEventListener('input', updateExampleBadge);
156+
});
157+
158+
// Handle Drawer open/close logic
159+
const exampleDrawer = document.querySelector('#example-filter-drawer');
160+
const openExampleDrawer = () => (exampleDrawer.active = true);
161+
const closeExampleDrawer = () => (exampleDrawer.active = false);
162+
const closeAndClearExampleDrawer = () => {
163+
exampleDrawerElements.forEach(el => {
164+
if (el && typeof el.value !== 'undefined') el.value = '';
165+
});
166+
updateExampleBadge();
167+
closeExampleDrawer();
168+
};
169+
exampleDrawer.addEventListener('overlay-click-outside', closeAndClearExampleDrawer);
170+
exampleDrawer.addEventListener('on-close', closeAndClearExampleDrawer);
171+
172+
// Initialize
173+
updateExampleBadge();
174+
```
175+
176+
### Advanced Example - Full Filter Bar
177+
178+
Putting it all together we get an Advanced Filter Bar like this:
179+
180+
<Canvas of={stories.AdvancedFilterBar} />
181+
182+
## Helpful Tips
183+
184+
You can customize the width of the search bar and select inputs using the `custom-width` attribute to better fit your layout:
185+
186+
```html
187+
<col-search-bar custom-width="150px" ...></col-search-bar>
188+
<col-select custom-width="200px" ...></col-select>
189+
```
190+
191+
There are times when the Filter Bar is placed in a container with limited horizontal space. In these cases, you can use the `wrap` attribute on the `col-toolbar` to allow the items to wrap onto multiple lines:
192+
193+
```html
194+
<col-toolbar wrap ...>
195+
...
196+
</col-toolbar>
197+
```
198+
199+
An example of the JavaScript needed to handle the form submission and capture the filter values:
200+
201+
```javascript
202+
// Handle Form logic
203+
const form = document.getElementById('filter-form');
204+
const toolbarElements = document
205+
.querySelector('#toolbar')
206+
.querySelectorAll('col-search-bar, col-select');
207+
const drawerElements = document
208+
.querySelector('#drawer-content')
209+
.querySelectorAll('col-text-field, col-select, col-number-field');
210+
211+
const updateBadge = () => {
212+
let total = 0;
213+
toolbarElements.forEach(el => {
214+
if (el && typeof el.value !== 'undefined' && String(el.value || '').trim()) total += 1;
215+
});
216+
drawerElements.forEach(el => {
217+
if (el && typeof el.value !== 'undefined' && String(el.value || '').trim()) total += 1;
218+
});
219+
const filterCounter = document.querySelector('#filter-counter');
220+
if (filterCounter) filterCounter.textContent = String(total);
221+
};
222+
223+
// Add event listeners to update badge on input changes
224+
[...toolbarElements, ...drawerElements].forEach(el => {
225+
el?.addEventListener('change', updateBadge);
226+
el?.addEventListener('input', updateBadge);
227+
});
228+
229+
// Functions
230+
const onSubmit = event => {
231+
event.preventDefault();
232+
const formData = new FormData(form);
233+
const filters = Object.fromEntries(formData.entries());
234+
console.log('[FilterBar] Search with filters:', filters);
235+
};
236+
237+
const clearForm = () => {
238+
toolbarElements.forEach(el => {
239+
if (el && typeof el.value !== 'undefined') el.value = '';
240+
});
241+
drawerElements.forEach(el => {
242+
if (el && typeof el.value !== 'undefined') el.value = '';
243+
});
244+
updateBadge();
245+
};
246+
247+
const onReset = event => {
248+
event.preventDefault();
249+
form.reset();
250+
clearForm();
251+
};
252+
253+
form.addEventListener('submit', onSubmit);
254+
form.addEventListener('reset', onReset);
255+
256+
// Handle Drawer open/close logic
257+
const drawer = document.querySelector('#filter-drawer');
258+
const openDrawer = () => (drawer.active = true);
259+
const closeDrawer = () => (drawer.active = false);
260+
const closeAndClearDrawer = () => {
261+
drawerElements.forEach(el => {
262+
if (el && typeof el.value !== 'undefined') el.value = '';
263+
});
264+
updateBadge();
265+
closeDrawer();
266+
};
267+
drawer.addEventListener('overlay-click-outside', closeAndClearDrawer);
268+
drawer.addEventListener('on-close', closeAndClearDrawer);
269+
270+
// Initialize
271+
updateBadge();
272+
```
273+
274+
</Unstyled>
275+
276+
## Variants
277+
The Filter Bar can be adapted to multiple contexts and information densities. Common variations include:
278+
- **Product Filtering:** Filter accounts, campaigns, users, and other entities.
279+
- **Content Filtering in Lists or Tables:** Filter data by date, status, type, and other attributes.
280+
- **Filters by Category:** Group filters into logical categories or sections.
281+
- **Multi-Selection Filters:** Allow selecting multiple options within a single filter.
282+
- **Search Filter:** Filter results based on user-entered terms.
283+
- **Dynamic Filters:** Reveal additional filters based on a primary selection.
284+
- **Range-Based Filters:** Set numeric or date ranges (e.g., date range selectors).
285+
- **Sort Filter:** Sort results by different criteria.
286+
- **Custom Filters:** Create domain-specific filters based on user needs.
287+
288+
## Accessibility
289+
- Ensure all controls have accessible names/labels (e.g., `sub-label`, `label`)
290+
- Maintain logical tab order; make focus states clearly visible
291+
- Use ARIA where appropriate for custom patterns; avoid redundant roles
292+
- Announce state changes (e.g., update counter text content) without stealing focus
293+
- Provide keyboard access to open/close drawers and operate menus
294+
295+
</div>
296+
</div>

0 commit comments

Comments
 (0)