Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mawrkus/lab-filter-bar-ui
๐งช Lab project: a filter bar UI component
https://github.com/mawrkus/lab-filter-bar-ui
filterbar react semantic-ui tvmaze-api ui xstate
Last synced: 1 day ago
JSON representation
๐งช Lab project: a filter bar UI component
- Host: GitHub
- URL: https://github.com/mawrkus/lab-filter-bar-ui
- Owner: mawrkus
- Created: 2022-02-14T02:08:29.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-09-03T12:48:29.000Z (about 1 year ago)
- Last Synced: 2024-04-14T22:22:07.704Z (7 months ago)
- Topics: filterbar, react, semantic-ui, tvmaze-api, ui, xstate
- Language: JavaScript
- Homepage: https://mawrkus.github.io/lab-filter-bar-ui/
- Size: 2.69 MB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๐งช Lab project: a Filter Bar UI component
An experiment to build a filter bar UI component with:
- [React + hooks](https://reactjs.org/)
- [Semantic UI](https://react.semantic-ui.com/)
- [TVmaze API](https://www.tvmaze.com/api)
- A custom state machine implementation inspired by [XState](https://xstate.js.org/)The code is not 100% production-ready (it's an experiment) but I believe that, as it is, it gives a pretty good idea on how to build this type of complex UI component.
[Live demo](https://mawrkus.github.io/lab-filter-bar-ui/)
## โฉ๏ธ Architecture: core concepts
- State machine with domain context (the app state)
- The app state is observable
- The Filter Bar receives the app state as props
- The Filter Bar renders "dumb" UI components (Chiclet, ...)
- Suggestions are fetched from an HTTP client โ API (+ cancellable requests)
- Clear contract on the data structure: suggestion item from the API โ filter object โ component prop
- Main states:
- `idle` (waiting for user input),
- `attribute suggestions` (loading and displaying)
- `operator suggestions` (loading and displaying)
- `value suggestions` (loading and displaying)
- `logical operator suggestions` (loading and displaying)
- Proxy states (handle logic when editing/next suggestion):
- `edit filter suggestions` (when a filter is edited)
- `next suggestions` (check for partial filter every time a filter has been created, edited or deleted)### State diagrams
Made with https://mermaid.js.org/
#### Idle state
```mermaid
stateDiagram-v2
direction LR
[*] --> idle
idle --> displayAttributeSuggestions: startInput (no filters)
idle --> displayLogicalOperatorSuggestions: startInput (last filter is complete and != logical operator)
idle --> proxyToNextSuggestions: startInput (last filter incomplete or logical operator)
idle --> proxyToEditSuggestions: editFilter (!= parens)
idle --> displayAttributeSuggestions: editFilter (empty parens)
idle --> displayLogicalOperatorSuggestions: editFilter (last filter in parens is complete and != logical operator)
idle --> proxyToNextSuggestions: editFilter (last filter in parens incomplete or logical operator)
idle --> proxyToNextSuggestions: removeFilter
idle --> idle: removeLastFilter
```#### displayAttributeSuggestions state
```mermaid
stateDiagram-v2
displayAttributeSuggestions: displayAttributeSuggestions
note left of displayAttributeSuggestions: Starts loading attributes
state if_editing <>
displayAttributeSuggestions --> if_editing: attributesLoaded
if_editing --> setAttribute: not editing
if_editing --> editAttribute: editing
displayAttributeSuggestions --> idle: discardSuggestionssetAttribute --> idle: discardSuggestions
setAttribute --> displayAttributeSuggestions: selectItem (parens)
setAttribute --> proxyToNextSuggestions: selectItem (search text)
setAttribute --> displayOperatorSuggestions: selectItem (!= search text)
setAttribute --> idle: removeLastFilter
editAttribute --> idle: discardSuggestions
editAttribute --> proxyToNextSuggestions: selectItem
```#### displayOperatorSuggestions state
```mermaid
stateDiagram-v2
displayOperatorSuggestions: displayOperatorSuggestions
note left of displayOperatorSuggestions: Starts loading operators
state if_editing <>
displayOperatorSuggestions --> if_editing: operatorsLoaded
if_editing --> setOperator: not editing
if_editing --> editPartialOperator: editing partial
if_editing --> editOperator: editing complete
displayOperatorSuggestions --> idle: discardSuggestionssetOperator --> idle: discardSuggestions
setOperator --> displayOperatorSuggestions: selectItem (parens)
setOperator --> proxyToNextSuggestions: selectItem (search text)
setOperator --> displayOperatorSuggestions: selectItem (!= search text)
setOperator --> idle: removeLastFilter
editOperator --> idle: discardSuggestions
editOperator --> proxyToNextSuggestions: selectItem
editPartialOperator --> proxyToNextSuggestions: selectItem
```#### displayValueSuggestions state
...
## ๐ Use cases
Launch and check the [end-to-end](cypress/e2e) tests: `npm run e2e:dev`
### Creation
Using the mouse or the keyboard:
1. Creation of a 3-part filter
2. Creation of a logical operator
3. Creation of search text filter
4. Creation of a 3-part filter with search text
5. Creation of 2-part filter### Edition
Using the mouse or the keyboard:
- Edition of each part of the filters
- Changing a 3-part filter to a 2-part one and vice-versa
- Same with a partial attribute
- Same with a partial attribute and operator
- Changing a filter operator from "single" to "multi" type### Deletion
Using the mouse or keyboard:
- Deletion of complete filters
- Deletion of a partial filter### Loading
- Request cancellation
- Error: selection blocked, search text creation allowed### Parens
- ...
## ๐ Quirks/Issues
### Invalid state transitions
Choosing "Season" then the "IN" operator:
- sends a `selectItem` event with type = "multiple-value"
- transitions from `setOperator` โ `displayValueSuggestions`
- sends a `startInput` event (!)
- ๐ฅ which triggers an invalid transition `displayValueSuggestions` โ `undefined`
- (...then a bit later) sends a `valueSuggestionsLoaded` event
- (...then a bit later) everything is all goodWhy? Because the `startInput` event comes from:
- unmounting ``
- mounting `` with `open` = true
- which calls the `onOpen` prop
- which sends the `startInput` event### Dropdown component warnings
- Choose "Season" then the "IN" operator
- Select 1 and 4
- Remove 1
- Close the dropdown```text
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
at Dropdown (http://localhost:3000/static/js/bundle.js:52590:29)
```After this:
- Change the "IN" operator for the "IS NULL" operator
- The filters are properly updated but the chiclet is not re-rendered### Editing IN/NOT IN values with a partial filter present
The items of the multi-dropdown are selected on close.
When editing a multi dropdown and closing it by clicking on the document:- the mouse down closes the multi dropdown,
- the partial suggestions dropdown is open and
- ๐ฅ the mouse up closes it immediately (node_modules/semantic-ui-react/src/modules/Dropdown/Dropdown.js L160)..resulting in poor UX :/
We've tried to introduce a 100ms delay in the `proxyToNextSuggestions` state to prevent the
dropdown that opens for the partial filter to be closed on mouse up but it produced wrong dropdown
placements when editing completed filter values and a partial filter is present.