blob: 083abf63658bbb894f9c3228b06b69dd0921eaf4 [file] [log] [blame]
= Component Development
:toc: left
== Overview
The following list contains a possible approach for implementing a new UI component:
1. Create a new design or start with an existing design, see for example Figma
2. Validate the use case and analyze the components that may be used for the implementation
3. Create Composables that represent the UI component(s) and use placeholders for data population
4. Create a component interface and implementation with the UI state and UI component interactions
5. Create previews with preview component implementations to check the UI implementation
6. Create a store and store provider for fetching resources and interacting with Solr backend
7. Implement the client used by the store provider
8. Write tests and test the new component
9. If not already done, integrate the component in the existing application
10. If not already done, extract resources like texts to allow internationalization and localization
It is recommended to take a look at existing components, so that you get a better understanding
of how things are implemented, what they have in common, and how each technology is utilized.
== Component's Logic
=== Components (Decompose)
The component integration interacts with the UI composables and the state store.
The implementation of the component interface "catches" user inputs like clicks and passes them
to the store as ``Intent``s. The intents are then handled by the store implementation and
may send a request to the backend and / or update the store state. The component is consuming
and mapping the store state to the UI state. So once the store state is updated, it will
reflect the changes in the UI.
=== State Stores and Store Providers
The state stores manage the state of the application, but independent of the state that is
represented in the UI. Instances are created by store providers that hold the logic of the
store.
Store providers consist of three elements:
- an executor implementation that consumes actions and intents and creates messages and labels
- a reducer that updates the store state with the messages produced by the executor
- a function for retrieving an instance of the store
The store provider does also define the interface for the client that has to be provided in
order for the executor to make API calls and interact with the Solr backend.
== Component's Visuals
=== Composables
Composables are the UI elements that are defined and styled. They can be seen as boxes, rows and
columns that are nested and change their style and structure based on conditions, state and input.
There are many ways to get started, but the easiest way probably is to get familiar with the basics
and try things out. The Figma designs make use of almost the same elements for designing,
so the structure and configurations there may be mapped almost one-by-one in Compose code.
=== Styling
The styling in Compose is done via ``Modifier``s. Each composable should normally accept a modifier
as a parameter, so that the user can customize specific visual parameters of the composable like
width, height and alignment in the parent's composable.
Since we are using Material 3, you do not have to care much about colors, typography and shapes.
These are configured for the entire app, and you only have to make use of the right properties
that are provided by the theme.
=== Accessibility
Compose comes with many accessibility features that can be used to improve the user experience.
The simplest form of accessibility in a UI is probably the responsiveness of the UI. This is
realized with `WindowSizeClass`. Some composables may use a wrapper (usually suffixed with
`Content`) that checks the window size and loads different UI based on the dimensions of the
current window.
Another accessibility feature is the resource loading based on the system's locale or the user's
preference. This allows the UI to be displayed in the user's native language. For that, you have
to simply provide translations in the Compose resources.
Another accessibility feature often underestimated is coloring. Some people with color vision
deficiency may need a different theme, so that elements with problematic contrasts may be
better visible again.
Additional accessibility features like font scaling, semantics for screen readers may also
be considered. Jetpack Compose provides a https://developer.android.com/develop/ui/compose/accessibility[simplified overview]
and https://developer.android.com/codelabs/jetpack-compose-accessibility#0[Codelabs] for getting started.
=== Navigation and Child Components
Some components may have navigation elements and will load other components inside a frame layout.
Since components hold a hierarchical context that needs to be managed somehow, child components
(also used in navigation) are instantiated in a slightly different manner.
Decompose provides https://arkivanov.github.io/Decompose/navigation/overview/[a few examples]
and details of the process behind the navigation and child components.
== Additional Notes
=== Dependency Locking
When adding or changing dependencies, you typically run `./gradlew resolveAndLockAll --write-locks`.
Since we are building a web application from kotlin sources, we also have to update the JS lockfile
with `./gradlew kotlinUpgradeYarnLock`. This will update the lockfile found at `kotlin-js-store/yarn.lock`.
Some multiplatform libraries have platform-specific dependency resolution that will result in different
lockfiles being generated, based on the environment the lock task is executed. It is important to exclude
these platform-specific libraries from the lockfile to ensure a consistent lockfile generation across
different operating systems.
Platform-specific libraries come with a module name suffix that includes the platform name, like
in `org.jetbrains.compose.desktop:desktop-jvm-windows-x64`. To identify those, look into the
changes after updating the lockfile and add the necessary ignore-clause if such libraries
exist. These ignore-clauses should be added in `gradle/validation/dependencies.gradle` inside the
`allprojects.dependencyLocking` block.