https://github.com/jean-merelis/angular-components
https://github.com/jean-merelis/angular-components
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/jean-merelis/angular-components
- Owner: jean-merelis
- Created: 2025-03-30T12:41:09.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2025-04-26T01:49:55.000Z (about 2 months ago)
- Last Synced: 2025-05-01T11:05:57.125Z (about 2 months ago)
- Language: TypeScript
- Homepage: https://jean-merelis.github.io/angular-components
- Size: 1.52 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- fucking-awesome-angular - @jean-merelis/angular-components - A library of reusable Angular components and utilities that provides high-quality UI elements for your applications. (Table of contents / Third Party Components)
- awesome-angular - @jean-merelis/angular-components - A library of reusable Angular components and utilities that provides high-quality UI elements for your applications. (Table of contents / Third Party Components)
README
# Merelis Angular Components
A library of reusable Angular components and utilities that provides high-quality UI elements for your applications.
[](https://opensource.org/licenses/MIT)
## Showcase
https://jean-merelis.github.io/angular-components/## Installation
```bash
npm install @merelis/angular --save
```## Available Components
Currently, the library provides the following components:
### MerSelect
An advanced select component with filtering and typeahead capabilities. Supports single or multiple selection, full customization, reactive forms integration, and conditional rendering.
### MerProgressBar
A progress bar component that can be used independently or integrated with other components.
## Installation and Usage
### Initial Setup
After installing the package, you need to import the necessary styles in your application's `styles.scss` file:
```scss
@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:
#### Direct import in a component
```typescript
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: `
`
})
export class ExampleComponent {
// ...
}
```### MerSelect
The `MerSelect` offers a robust alternative to the native HTML select, with additional features like filtering and typeahead.
#### Basic HTML
```html
```
#### Input Properties
| Name | Type | Default | Description |
|------|------|---------|-------------|
| dataSource | Array\ | SelectDataSource\ | 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\ | undefined | Function to compare values |
| displayWith | DisplayWith\ | undefined | Function to display values as text |
| filterPredicate | FilterPredicate\ | 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\ | () => 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 |#### Output Events
| 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 |#### Complete Example
```typescript
import { Component } from '@angular/core';interface User {
id: number;
name: string;
}@Component({
selector: 'app-example',
template: `
`
})
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);
}
}
```## Filtering Behavior and DataSource
The `MerSelect` supports two operation modes for data filtering:
### 1. Automatic Filtering (Array as dataSource)
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\ | 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.** |### 2. Custom Filtering (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` and `alwaysIncludesSelected` inputs are ignored
- The filtering logic is entirely controlled by the dataSource```typescript
export class CustomDataSource implements SelectDataSource {
// ...
async applyFilter(criteria: FilterCriteria): void | Promise {
// 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)
}
}
```### Choosing Between Array and SelectDataSource
- **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## Typeahead Functionality with TypeaheadDataSource
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.
### TypeaheadSearchService Interface
First, implement the `TypeaheadSearchService` interface to define how search operations will be performed:
```typescript
export interface TypeaheadSearchService {
/**
* 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;
}
```### TypeaheadDataSourceOptions Interface
The `TypeaheadDataSource` now accepts a configuration options object:
```typescript
export interface TypeaheadDataSourceOptions {
/**
* 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;
}
```### Using the TypeaheadDataSource
The `TypeaheadDataSource` provides a robust solution for typeahead functionality with automatic cancellation of previous requests, which is essential for a smooth user experience.
#### Implementation
```typescript
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 {
constructor(private http: HttpClient) {}
search(query: string): Observable {
// Real implementation would use HttpClient
return this.http.get(`/api/users?q=${query}`);
// Example of a mock implementation for testing:
/*
const users = [
{ id: 1, name: 'John Smith', email: '[email protected]' },
{ id: 2, name: 'Mary Johnson', email: '[email protected]' },
{ id: 3, name: 'Peter Williams', email: '[email protected]' }
];
const results = query
? users.filter(user => user.name.toLowerCase().includes(query.toLowerCase()))
: users;
return of(results).pipe(delay(300)); // Simulate network delay
*/
}
}
```#### Usage in a Component
```typescript
import { Component, OnDestroy } from '@angular/core';
import { MerSelect } from '@merelis/angular/select';
import { TypeaheadDataSource, TypeaheadDataSourceOptions } from '@merelis/angular/select';
import { UserSearchService, User } from './user-search.service';@Component({
selector: 'app-user-search',
standalone: true,
imports: [MerSelect],
template: `
Selected: {{selectedUser.name}}
`
})
export class UserSearchComponent implements OnDestroy {
selectedUser: User | null = null;
userDataSource: TypeaheadDataSource;
constructor(private userSearchService: UserSearchService) {
// Define options for the data source
const options: TypeaheadDataSourceOptions = {
alwaysIncludeSelected: true,
compareWith: (a, b) => a.id === b.id,
suppressLoadingEvents: false
};
// Create the data source with the service and options
this.userDataSource = new TypeaheadDataSource(
userSearchService,
options
);
}
ngOnDestroy(): void {
// Cleanup resources
this.userDataSource.disconnect();
}
// Display function for the select component
displayUserName(user: User): string {
return user?.name || '';
}
}
```### TypeaheadDataSource API
The `TypeaheadDataSource` constructor now accepts the following parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| searchService | TypeaheadSearchService | Yes | The service implementing the search functionality |
| options | TypeaheadDataSourceOptions | No | Configuration options object |#### TypeaheadDataSourceOptions Properties
| 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 |### How It Works
1. **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.
2. **Loading State Management**: The data source emits loading states that the `MerSelect` can display as a progress indicator. This can be suppressed using the `suppressLoadingEvents` option.
3. **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.
4. **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.
### Benefits of Using TypeaheadDataSource
1. **Performance**: Efficiently handles rapid typing by cancelling outdated requests
2. **User Experience**: Shows loading indicators at appropriate times
3. **Resilience**: Provides graceful error handling
4. **Flexibility**: Works with any data type and search implementation
5. **Integration**: Seamlessly works with MerSelect's search capabilitiesThe `TypeaheadDataSource` implementation follows best practices for reactive programming with RxJS and works with both simple and complex typeahead scenarios.
---
### MerProgressBar
The `MerProgressBar` component displays a visual progress bar, useful for indicating the progress of operations.
#### Basic HTML
```html
```
#### Input Properties
| Name | Type | Default | Description |
|------|------|---------|-------------|
| value | number | 0 | Current progress value (between 0 and 1) |
| indeterminate | boolean | false | Determines if the progress bar should show an indeterminate progress indicator |#### Usage Example
```typescript
import { Component } from '@angular/core';@Component({
selector: 'app-progress-example',
template: `
Start Download
`
})
export class ProgressExampleComponent {
downloadProgress = 0;
downloadInProgress = false;startDownload() {
this.downloadInProgress = true;
// Simulating a download
const interval = setInterval(() => {
this.downloadProgress += 0.1;
if (this.downloadProgress >= 1) {
clearInterval(interval);
this.downloadInProgress = false;
this.downloadProgress = 1;
}
}, 500);
}
}
```---
## Custom Templates
The `MerSelect` allows customization of the trigger (clickable area) and options through templates.
### Custom Trigger Template
```html
![]()
{{ selectedUser?.name }}
```
### Custom Option Template
```html
![]()
```
---
## Testing with Component Harnesses
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.
### Installation
The testing harnesses are included in the package and can be imported from:
```typescript
import { MerSelectHarness } from '@merelis/angular/select/testing';
import { MerSelectOptionHarness } from '@merelis/angular/select/testing';
```### Setting Up Component Test Harnesses
To use the harnesses in your tests, you'll need to set up the Angular test environment with the harness environment:
```typescript
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;
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
});
```### MerSelectHarness API
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)` | Gets the options inside the panel |
| `clickOptions(filters: SelectOptionHarnessFilters)` | Clicks the option(s) matching the given filters |### MerSelectOptionHarness API
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 |### Example Test
Here's an example of testing a component that uses `MerSelect`:
```typescript
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: `
`,
standalone: true,
imports: [MerSelect]
})
class TestComponent {
fruits = ['Apple', 'Banana', 'Orange', 'Strawberry'];
selectedFruit: string | null = null;
}describe('TestComponent', () => {
let fixture: ComponentFixture;
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');
});
});
```### Testing with Complex Data Structures
When using objects as options, you can leverage the harness methods to test more complex scenarios:
```typescript
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: `
`,
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;
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);
});
});
```### Testing Multiple Selection
You can also test the multiple selection mode of the `MerSelect`:
```typescript
@Component({
template: `
`,
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);
}
}
});
});
```---
## Integration with Angular Material
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.### Installation
```bash
npm install @merelis/angular-material --save
```### Usage with mat-form-field
```typescript
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: `
Select a user
Select a user from the list
Please select a valid user
`
})
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;
}
}
```---
## Component Integration
The `MerSelect` internally uses the `MerProgressBar` to display a loading indicator when the `loading` property is set to `true`.
```html
```
---
### CSS Customization
The components can be customized using CSS variables. Below are the available variables for each component.
#### MerSelect
```scss
.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-background-color: #e6e6e6;
--mer-select-chip-border-radius: 8px;
--mer-select-chip-padding: 2px 2px 2px 8px;
--mer-select-chip-font-size: 0.875rem;--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-text-color-hover: white;
--mer-select-chip-remove-bg-color-hover: #505050;
--mer-select-chip-remove-opacity-hover: 1;
// 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;
}
```#### MerSelect Panel
```scss
.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;
}
```#### MerOption
```scss
.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;
}```
#### MerProgressBar
```scss
.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);
}
```---
## Contributing
Contributions are welcome! Feel free to open issues or submit pull requests.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request## License
This project is licensed under the MIT License - see the LICENSE file for details.