A library of reusable Angular components and utilities that provides high-quality UI elements for your applications.
https://jean-merelis.github.io/angular-components/
npm install @merelis/angular --save
Currently, the library provides the following components:
An advanced select component with filtering and typeahead capabilities. Supports single or multiple selection, full customization, reactive forms integration, and conditional rendering.
A progress bar component that can be used independently or integrated with other components.
After installing the package, you need to import the necessary styles in your application's styles.scss
file:
@use '@angular/cdk/overlay-prebuilt.css';
@use '@merelis/angular/select/styles';
This will import both the CDK overlay styles (required for the dropdown functionality) and the component-specific styles.
Since these are standalone components, you can import them directly in your components:
import { Component } from '@angular/core';
import { MerSelect } from '@merelis/angular/select';
import { MerProgressBar } from '@merelis/angular/progress-bar';
@Component({
selector: 'app-example',
standalone: true,
imports: [
MerSelect,
MerProgressBar
],
template: `
<mer-select [dataSource]="items" [(value)]="selectedItem"></mer-select>
<mer-progress-bar [value]="0.5"></mer-progress-bar>
`
})
export class ExampleComponent {
// ...
}
The MerSelect
offers a robust alternative to the native HTML select, with additional features like filtering and typeahead.
<mer-select
[dataSource]="optionsList"
[(value)]="selectedValue"
[placeholder]="'Select an option'">
</mer-select>
Name | Type | Default | Description |
---|---|---|---|
dataSource | Array<T> | SelectDataSource<T> | undefined | List of available options for selection or data source for the component |
value | T | T[] | null | undefined | Currently selected value |
loading | boolean | false | Displays loading indicator using MerProgressBar |
disabled | boolean | false | Disables the component |
readOnly | boolean | false | Sets the component as read-only |
disableSearch | boolean | false | Disables text search functionality |
disableOpeningWhenFocusedByKeyboard | boolean | false | Prevents the panel from opening automatically when focused via keyboard |
multiple | boolean | false | Allows multiple selection |
canClear | boolean | true | Allows clearing the selection |
alwaysIncludesSelected | boolean | false | Always includes the selected item in the dropdown, even if it doesn't match the filter. Note: Only effective when using an array as dataSource, not when using a custom SelectDataSource. |
autoActiveFirstOption | boolean | true | Automatically activates the first option when the panel is opened |
debounceTime | number | 100 | Debounce time for text input (in ms) |
panelOffsetY | number | 0 | Vertical offset of the options panel |
compareWith | Comparable<T> | undefined | Function to compare values |
displayWith | DisplayWith<T> | undefined | Function to display values as text |
filterPredicate | FilterPredicate<T> | undefined | Function to filter options based on typed text. Note: Only effective when using an array as dataSource, not when using a custom SelectDataSource. |
disableOptionPredicate | OptionPredicate<T> | () => false | Function to determine which options should be disabled |
disabledOptions | T[] | [] | List of options that should be disabled |
connectedTo | MerSelectPanelOrigin | undefined | Element to which the panel should connect |
panelClass | string | string[] | undefined | CSS class(es) applied to the options panel |
panelWidth | string | number | undefined | Width of the options panel |
position | 'auto' | 'above' | 'below' | 'auto' | Position of the panel relative to the input |
placeholder | string | undefined | Text to display when no item is selected |
Name | Description |
---|---|
opened | Emitted when the options panel is opened |
closed | Emitted when the options panel is closed |
focus | Emitted when the component receives focus |
blur | Emitted when the component loses focus |
inputChanges | Emitted when the text input value changes |
import { Component } from '@angular/core';
interface User {
id: number;
name: string;
}
@Component({
selector: 'app-example',
template: `
<mer-select
[dataSource]="users"
[(value)]="selectedUser"
[displayWith]="displayUserName"
[compareWith]="compareUsers"
[placeholder]="'Select a user'"
[loading]="isLoading"
(opened)="onPanelOpened()"
(closed)="onPanelClosed()"
(inputChanges)="onInputChanged($event)">
</mer-select>
`
})
export class ExampleComponent {
users: User[] = [
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Mary Johnson' },
{ id: 3, name: 'Peter Williams' }
];
selectedUser: User | null = null;
isLoading = false;
displayUserName(user: User): string {
return user.name;
}
compareUsers(user1: User, user2: User): boolean {
return user1?.id === user2?.id;
}
onPanelOpened(): void {
console.log('Options panel opened');
}
onPanelClosed(): void {
console.log('Options panel closed');
}
onInputChanged(text: string): void {
console.log('Search text:', text);
}
}
The MerSelect
supports two operation modes for data filtering:
When you provide an array as dataSource
, the component performs automatic filtering based on the typed text. In this case, the following inputs control the filtering behavior:
Name | Type | Description |
---|---|---|
filterPredicate | FilterPredicate<T> | Custom function to filter options based on typed text. Only applied when the dataSource is an array, not a custom SelectDataSource. |
alwaysIncludesSelected | boolean | When true, always includes the selected item(s) in the dropdown, even if they don't match the filter. Only applied when the dataSource is an array, not a custom SelectDataSource. |
When you implement and provide a custom SelectDataSource
, the filtering behavior is determined by the implementation of the dataSource's applyFilter
method. In this case:
- The component invokes the
applyFilter
method when the user types - The
filterPredicate
andalwaysIncludesSelected
inputs are ignored - The filtering logic is entirely controlled by the dataSource
export class CustomDataSource<T> implements SelectDataSource<T> {
// ...
async applyFilter(criteria: FilterCriteria<T>): void | Promise<void> {
// Here you implement your own filtering logic
// The criteria parameter contains:
// - searchText: the text typed by the user
// - selected: the currently selected item(s)
// You can decide to include selected items even if they don't match the filter
// (equivalent to the alwaysIncludesSelected behavior)
// You can also implement your own filtering logic
// (equivalent to the filterPredicate behavior)
}
}
- Use a simple array when you have a small set of static data that doesn't require server-side filtering.
- Implement a SelectDataSource when you need complete control over filtering, especially for:
- Fetching data from the server based on typed text (typeahead)
- Handling large datasets
- Implementing complex filtering logic
- Showing loading indicators during asynchronous operations
The MerSelect
supports typeahead functionality, allowing you to search for options as you type. The library provides a generic TypeaheadDataSource
implementation that handles common typeahead requirements including search request cancellation, loading states, and result management.
A simple function type that can be used to perform typeahead searches:
export type TypeaheadSearchFn<T> = (query: string) => Observable<T[]>;
Alternatively, you can implement the TypeaheadSearchService
interface to define how search operations will be performed:
export interface TypeaheadSearchService<T> {
/**
* Search method that takes a query string and returns an Observable of results
* @param query The search query string
* @returns Observable of search results
*/
search(query: string): Observable<T[]>;
}
The TypeaheadDataSource
accepts a configuration options object:
export interface TypeaheadDataSourceOptions<T> {
/**
* Whether to always include selected items in the results. Default false.
*/
alwaysIncludeSelected?: boolean;
/**
* Whether to suppress loading events. Default false.
*/
suppressLoadingEvents?: boolean;
/**
* Custom function to compare items for equality (defaults to comparing by reference)
* @param a First item to compare
* @param b Second item to compare
*/
compareWith?: (a: T, b: T) => boolean;
}
The TypeaheadDataSource
provides a robust solution for typeahead functionality with automatic cancellation of previous requests, which is essential for a smooth user experience.
You can implement typeahead functionality in two ways:
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { TypeaheadDataSource, TypeaheadDataSourceOptions } from '@merelis/angular/select';
// Define your data model
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-search',
standalone: true,
imports: [MerSelect],
template: `
<mer-select
[(value)]="selectedUser"
[dataSource]="userDataSource"
[displayWith]="displayUserName"
[placeholder]="'Search for users...'">
</mer-select>
`
})
export class UserSearchComponent implements OnDestroy {
selectedUser: User | null = null;
userDataSource: TypeaheadDataSource<User>;
constructor(private http: HttpClient) {
// Define a search function that returns an Observable
const searchFn = (query: string): Observable<User[]> => {
return this.http.get<User[]>(`/api/users?q=${query}`);
};
// Define options for the data source
const options: TypeaheadDataSourceOptions<User> = {
compareWith: (a, b) => a.id === b.id
};
// Create the data source with the search function and options
this.userDataSource = new TypeaheadDataSource<User>(searchFn, options);
}
// Display function for the select component
displayUserName(user: User): string {
return user?.name || '';
}
}
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { TypeaheadSearchService, TypeaheadDataSource, TypeaheadDataSourceOptions } from '@merelis/angular/select';
// Define your data model
interface User {
id: number;
name: string;
email: string;
}
// Implement TypeaheadSearchService for your data type
@Injectable({ providedIn: 'root' })
export class UserSearchService implements TypeaheadSearchService<User> {
constructor(private http: HttpClient) {}
search(query: string): Observable<User[]> {
// Real implementation would use HttpClient
return this.http.get<User[]>(`/api/users?q=${query}`);
}
}
@Component({
selector: 'app-user-search',
standalone: true,
imports: [MerSelect],
template: `
<mer-select
[(value)]="selectedUser"
[dataSource]="userDataSource"
[displayWith]="displayUserName"
[placeholder]="'Search for users...'">
</mer-select>
`
})
export class UserSearchComponent implements OnDestroy {
selectedUser: User | null = null;
userDataSource: TypeaheadDataSource<User>;
constructor(private userSearchService: UserSearchService) {
// Create the data source with the service and options
this.userDataSource = new TypeaheadDataSource<User>(
userSearchService,
{
compareWith: (a, b) => a.id === b.id
}
);
}
// Rest of the component...
}
The TypeaheadDataSource
constructor accepts the following parameters:
Parameter | Type | Required | Description |
---|---|---|---|
searchService | TypeaheadSearchFn | TypeaheadSearchService | Yes | A function or service that implements the search functionality |
options | TypeaheadDataSourceOptions | No | Configuration options object |
Option | Type | Default | Description |
---|---|---|---|
alwaysIncludeSelected | boolean | false | Whether to always include selected items in the results even if they don't match the search criteria |
suppressLoadingEvents | boolean | false | Whether to suppress loading event emissions |
compareWith | (a: T, b: T) => boolean | (a, b) => a === b | Custom function to determine equality between items |
-
Efficient Request Handling: When the user types in the search input, previous in-flight requests are automatically cancelled using RxJS
switchMap
, ensuring only the most recent search query is processed. -
Loading State Management: The data source emits loading states that the
MerSelect
can display as a progress indicator. This can be suppressed using thesuppressLoadingEvents
option. -
Selected Items Preservation: When
alwaysIncludeSelected
is true, selected items will always appear in the dropdown results even if they don't match the current search query. -
Error Handling: If the search service encounters an error, the data source will handle it gracefully, preventing the component from breaking and falling back to an empty result set.
- Flexibility: Supports two ways to implement search - through a simple function or a full service
- Performance: Efficiently handles rapid typing by cancelling outdated requests
- User Experience: Shows loading indicators at appropriate times
- Resilience: Provides graceful error handling
- Adaptability: Works with any data type and search implementation
- Integration: Seamlessly works with MerSelect's search capabilities
The TypeaheadDataSource
implementation follows best practices for reactive programming with RxJS and works with both simple and complex typeahead scenarios.
The MerSelect
allows customization of the trigger (clickable area) and options through templates.
<mer-select [dataSource]="users" [(value)]="selectedUser">
<ng-template merSelectTriggerDef>
<div class="custom-trigger">
<img *ngIf="selectedUser?.avatar" [src]="selectedUser.avatar" class="avatar">
<span>{{ selectedUser?.name }}</span>
</div>
</ng-template>
</mer-select>
<mer-select [dataSource]="users" [(value)]="selectedUser">
<ng-template merSelectOptionDef let-option>
<div class="custom-option">
<img *ngIf="option.avatar" [src]="option.avatar" class="avatar">
<div class="user-info">
<div class="name">{{ option.name }}</div>
<div class="email">{{ option.email }}</div>
</div>
</div>
</ng-template>
</mer-select>
The library provides testing harnesses for the MerSelect
and its options, making it easier to test components that use these elements. These harnesses are built on top of Angular's Component Test Harnesses (CDK Testing) and provide a clean, implementation-detail-free way to interact with components in tests.
The testing harnesses are included in the package and can be imported from:
import { MerSelectHarness } from '@merelis/angular/select/testing';
import { MerSelectOptionHarness } from '@merelis/angular/select/testing';
To use the harnesses in your tests, you'll need to set up the Angular test environment with the harness environment:
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MerSelectHarness } from '@merelis/angular/select/testing';
describe('YourComponent', () => {
let fixture: ComponentFixture<YourComponent>;
let component: YourComponent;
let loader: HarnessLoader;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [YourComponent],
// Include other necessary imports here
}).compileComponents();
fixture = TestBed.createComponent(YourComponent);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
});
// Tests go here
});
The MerSelectHarness
provides methods to interact with and query the state of a MerSelect
:
Method | Description |
---|---|
static with(filters: MerSelectHarnessFilters) |
Gets a HarnessPredicate that can be used to find a select with specific attributes |
click() |
Clicks on the select trigger to open/close the panel |
clickOnClearIcon() |
Clicks on the clear icon to clear the selection |
focus() |
Focuses the select input |
blur() |
Removes focus from the select input |
isFocused() |
Gets whether the select is focused |
getValue() |
Gets the text value displayed in the select trigger |
isDisabled() |
Gets whether the select is disabled |
getSearchText() |
Gets the current text in the search input |
setTextSearch(value: string) |
Sets the text in the search input |
isOpen() |
Gets whether the options panel is open |
getOptions(filters?: Omit<SelectOptionHarnessFilters, 'ancestor'>) |
Gets the options inside the panel |
clickOptions(filters: SelectOptionHarnessFilters) |
Clicks the option(s) matching the given filters |
The MerSelectOptionHarness
provides methods to interact with and query the state of a select option:
Method | Description |
---|---|
static with(filters: SelectOptionHarnessFilters) |
Gets a HarnessPredicate that can be used to find an option with specific attributes |
click() |
Clicks the option |
getText() |
Gets the text of the option |
isDisabled() |
Gets whether the option is disabled |
isSelected() |
Gets whether the option is selected |
isActive() |
Gets whether the option is active |
isMultiple() |
Gets whether the option is in multiple selection mode |
Here's an example of testing a component that uses MerSelect
:
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HarnessLoader } from '@angular/cdk/testing';
import { MerSelectHarness, MerSelectOptionHarness } from '@merelis/angular/select/testing';
import { MerSelect } from '@merelis/angular/select';
@Component({
template: `
<mer-select
[dataSource]="fruits"
[(value)]="selectedFruit"
[placeholder]="'Select a fruit'">
</mer-select>
`,
standalone: true,
imports: [MerSelect]
})
class TestComponent {
fruits = ['Apple', 'Banana', 'Orange', 'Strawberry'];
selectedFruit: string | null = null;
}
describe('TestComponent', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
let loader: HarnessLoader;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TestComponent]
}).compileComponents();
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
fixture.detectChanges();
});
it('should open the select and select an option', async () => {
// Get the select harness
const select = await loader.getHarness(MerSelectHarness);
// Check initial state
expect(await select.getValue()).toBe('');
expect(await select.isOpen()).toBe(false);
// Open the select
await select.click();
expect(await select.isOpen()).toBe(true);
// Get all options
const options = await select.getOptions();
expect(options.length).toBe(4);
// Click the "Banana" option
await select.clickOptions({ text: 'Banana' });
// Check that the panel is closed after selection
expect(await select.isOpen()).toBe(false);
// Check that the value is updated
expect(await select.getValue()).toBe('Banana');
expect(component.selectedFruit).toBe('Banana');
});
it('should filter options based on search text', async () => {
const select = await loader.getHarness(MerSelectHarness);
// Open the select
await select.click();
// Enter search text
await select.setTextSearch('ber');
// Get filtered options
const options = await select.getOptions();
expect(options.length).toBe(1);
expect(await options[0].getText()).toBe('Strawberry');
// Select the filtered option
await options[0].click();
expect(await select.getValue()).toBe('Strawberry');
});
});
When using objects as options, you can leverage the harness methods to test more complex scenarios:
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HarnessLoader } from '@angular/cdk/testing';
import { MerSelectHarness } from '@merelis/angular/select/testing';
import { MerSelect } from '@merelis/angular/select';
interface User {
id: number;
name: string;
email: string;
}
@Component({
template: `
<mer-select
[dataSource]="users"
[(value)]="selectedUser"
[displayWith]="displayUser"
[compareWith]="compareUsers"
[placeholder]="'Select a user'">
</mer-select>
`,
standalone: true,
imports: [MerSelect]
})
class UserSelectComponent {
users: User[] = [
{ id: 1, name: 'John Doe', email: '[email protected]' },
{ id: 2, name: 'Jane Smith', email: '[email protected]' },
{ id: 3, name: 'Bob Johnson', email: '[email protected]' }
];
selectedUser: User | null = null;
displayUser(user: User): string {
return user?.name || '';
}
compareUsers(user1: User, user2: User): boolean {
return user1?.id === user2?.id;
}
}
describe('UserSelectComponent', () => {
let fixture: ComponentFixture<UserSelectComponent>;
let component: UserSelectComponent;
let loader: HarnessLoader;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserSelectComponent]
}).compileComponents();
fixture = TestBed.createComponent(UserSelectComponent);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
fixture.detectChanges();
});
it('should select a user by name and update the component model', async () => {
const select = await loader.getHarness(MerSelectHarness);
// Open the select
await select.click();
// Click the option with Jane's name
await select.clickOptions({ text: 'Jane Smith' });
// Check that the select shows the correct text
expect(await select.getValue()).toBe('Jane Smith');
// Check that the component model is updated with the correct object
expect(component.selectedUser).toEqual(component.users[1]);
expect(component.selectedUser?.id).toBe(2);
});
});
You can also test the multiple selection mode of the MerSelect
:
@Component({
template: `
<mer-select
[dataSource]="colors"
[(value)]="selectedColors"
[multiple]="true"
[placeholder]="'Select colors'">
</mer-select>
`,
standalone: true,
imports: [MerSelect]
})
class ColorSelectComponent {
colors = ['Red', 'Green', 'Blue', 'Yellow', 'Purple'];
selectedColors: string[] = [];
}
describe('ColorSelectComponent', () => {
// Test setup...
it('should support multiple selection', async () => {
const select = await loader.getHarness(MerSelectHarness);
// Open the select
await select.click();
// Select multiple options
await select.clickOptions({ text: 'Red' });
await select.clickOptions({ text: 'Blue' });
await select.clickOptions({ text: 'Yellow' });
// Check component model
expect(component.selectedColors).toEqual(['Red', 'Blue', 'Yellow']);
// Verify that the selected options are marked as selected
const options = await select.getOptions();
for (const option of options) {
const text = await option.getText();
const isSelected = await option.isSelected();
if (['Red', 'Blue', 'Yellow'].includes(text)) {
expect(isSelected).toBe(true);
} else {
expect(isSelected).toBe(false);
}
}
});
});
The MerSelect can be integrated with Angular Material's mat-form-field component through the @merelis/angular-material package. This integration allows you to use the select component within Material's form field, benefiting from features like floating labels, hints, and error messages.
npm install @merelis/angular-material --save
import { Component } from '@angular/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MerSelect } from '@merelis/angular/select';
import { MerSelectFormFieldControl } from "@merelis/angular-material/select";
@Component({
selector: 'app-material-example',
standalone: true,
imports: [
MatFormFieldModule,
MatInputModule,
MerSelect,
MerSelectFormFieldControl
],
providers: [
provideMerMaterialIntegration() // Enable integration with Angular Material
],
template: `
<mat-form-field appearance="outline">
<mat-label>Select a user</mat-label>
<mer-select merSelectFormField
[dataSource]="users"
[(value)]="selectedUser"
[displayWith]="displayUserName"
[compareWith]="compareUsers">
</mer-select>
<mat-hint>Select a user from the list</mat-hint>
<mat-error>Please select a valid user</mat-error>
</mat-form-field>
`
})
export class MaterialExampleComponent {
users = [
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Mary Johnson' },
{ id: 3, name: 'Peter Williams' }
];
selectedUser = null;
displayUserName(user: any): string {
return user?.name || '';
}
compareUsers(user1: any, user2: any): boolean {
return user1?.id === user2?.id;
}
}
The MerSelect
internally uses the MerProgressBar
to display a loading indicator when the loading
property is set to true
.
<mer-select
[dataSource]="dataItems"
[(value)]="selectedItem"
[loading]="isLoadingData">
</mer-select>
The components can be customized using CSS variables. Below are the available variables for each component.
.mer-select {
// Base select appearance
--mer-select-font: system-ui, Roboto, sans-serif;
--mer-select-font-size: 1em;
--mer-select-font-weight: normal;
--mer-select-line-height: 1em;
--mer-select-letter-spacing: normal;
--mer-select-min-height: 32px;
--mer-select-side-padding: 8px;
--mer-select-input-height: 100%;
--mer-select-input-width: 100%;
--mer-select-trigger-wrapper-gap: 4px;
// multiple select
--mer-select-multiple-trigger-wrapper-gap: 4px;
--mer-select-multiple-side-padding: 2px;
--mer-select-multiple-input-min-width: 33%;
--mer-select-multiple-input-height: 24px;
--mer-select-multiple-input-padding: 0 4px;
--mer-select-multiple-values-gap: 4px;
--mer-select-multiple-values-padding: 0;
--mer-select-chip-text-color: inherit;
--mer-select-chip-background-color: #e6e6e6;
--mer-select-chip-border-radius: 8px;
--mer-select-chip-border: none;
--mer-select-chip-padding-top: 2px;
--mer-select-chip-padding-right: 2px;
--mer-select-chip-padding-bottom: 2px;
--mer-select-chip-padding-left: 8px;
--mer-select-chip-font-size: 0.875rem;
--mer-select-chip-text-color-hover: var(--mer-select-chip-text-color, inherit);
--mer-select-chip-background-color-hover: var(--mer-select-chip-background-color,#e6e6e6);
--mer-select-chip-border-hover: var(--mer-select-chip-border, none);
--mer-select-chip-readonly-padding-right: 8px;
--mer-select-chip-remove-cursor: pointer;
--mer-select-chip-remove-margin-left: 4px;
--mer-select-chip-remove-font-size: 1rem;
--mer-select-chip-remove-line-height: 1rem;
--mer-select-chip-remove-font-weight: normal;
--mer-select-chip-remove-text-color: #000;
--mer-select-chip-remove-bg-color: #d1d1d1;
--mer-select-chip-remove-border-radius: 9999px;
--mer-select-chip-remove-padding: 0;
--mer-select-chip-remove-width: 12px;
--mer-select-chip-remove-height: 12px;
--mer-select-chip-remove-opacity: .5;
--mer-select-chip-remove-border: none;
--mer-select-chip-remove-text-color-hover: white;
--mer-select-chip-remove-bg-color-hover: #505050;
--mer-select-chip-remove-opacity-hover: 1;
--mer-select-chip-remove-border-hover: none;
// Colors and states
--mer-select-background-color: white;
--mer-select-color: black;
--mer-select-border: 1px solid #8c8a8a;
// Focus state
--mer-select-background-color--focused: white;
--mer-select-color--focused: black;
--mer-select-border--focused: 1px solid #8c8a8a;
--mer-select-outline--focused: solid #4e95e8 2px;
--mer-select-outline-offset--focused: -1px;
// Disabled state
--mer-select-background-color--disabled: #ececec;
--mer-select-color--disabled: #707070;
--mer-select-border--disabled: 1px solid #8c8a8a;
// Invalid state
--mer-select-background-color--invalid: white;
--mer-select-color--invalid: black;
--mer-select-border--invalid: 1px solid #c10909;
--mer-select-outline--invalid: solid #c10909 2px;
--mer-select-outline-offset--invalid: -1px;
// Icons
--mer-select-chevron-icon-color: #b3b3b3;
--mer-select-chevron-icon-color--hover: #353535;
// Loading indicator
--mer-select-loading-height: 2px;
--mer-select-loading-background-color: #d7e8fb;
--mer-select-loading-color: #0772CD;
}
.mer-select-panel {
--mer-select-panel-background-color: #ffffff;
--mer-select-panel-border-radius: 8px;
--mer-select-panel-box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
}
.mer-option {
// Base option appearance
--mer-option-font: system-ui, Roboto, sans-serif;
--mer-option-font-size: 1em;
--mer-option-font-weight: normal;
--mer-option-line-height: 1em;
--mer-option-letter-spacing: normal;
--mer-option-min-height: 48px;
--mer-option-side-padding: 8px;
--mer-option-material-side-padding: 16px;
--mer-option-group-indent: 20px;
// Colors and states
--mer-option-color: #121212;
--mer-option-hover-background-color: #f6f6f6;
--mer-option-active-background-color: #ececec;
--mer-option-selected-color: #0d67ca;
--mer-option-selected-background-color: #eef6ff;
--mer-option-selected-hover-color: #0d67ca;
--mer-option-selected-hover-background-color: #e1eef8;
--mer-option-selected-active-color: #0d67ca;
--mer-option-selected-active-background-color: #dcecfb;
--mer-option-selected-active-hover-color: #0d67ca;
--mer-option-selected-active-hover-background-color: #dceafa;
}
.mer-progress-bar {
--mer-progress-bar-height: 4px;
--mer-progress-bar-background-color: rgba(5, 114, 206, 0.2);
--mer-progress-bar-color: rgb(5, 114, 206);
}
Contributions are welcome! Feel free to open issues or submit pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.