diff --git a/frontend/README.md b/frontend/README.md
index 89623fba..a68ec74b 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -52,6 +52,13 @@ This enables supporting multiple searches in the same page
* searchalicious-checkbox is a simple checkbox
* it can be used to replace the default checkbox
* it allows to keep the state of the checkbox when you change the property
+* searchalicious-radio is a simple radio button
+ * it can be used to replace the default radio button
+ * it allows to keep the state of the radio button when you change the property
+ * You can unchecked the radio button by clicking on it
+* searchalicious-toggle is a simple toggle button
+ * it can be used to replace the checkbox
+ * it allows to keep the state of the toggle button when you change the property
* searchalicious-secondary-button is a button with defined style
* it can be used to replace the default button
* searchalicious-button-transparent is a transparent button with defined style
diff --git a/frontend/public/icons/checkbox-check.svg b/frontend/public/icons/checkbox-check.svg
new file mode 100644
index 00000000..43769237
--- /dev/null
+++ b/frontend/public/icons/checkbox-check.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/frontend/src/mixins/checked-input.ts b/frontend/src/mixins/checked-input.ts
new file mode 100644
index 00000000..d0f9be8e
--- /dev/null
+++ b/frontend/src/mixins/checked-input.ts
@@ -0,0 +1,79 @@
+import {Constructor} from './utils';
+import {LitElement, PropertyValues} from 'lit';
+import {property} from 'lit/decorators.js';
+import {BasicEvents} from '../utils/enums';
+
+export interface CheckedInputMixinInterface {
+ checked: boolean;
+ name: string;
+ label: string;
+ getInputElement(): HTMLInputElement | null;
+ _dispatchChangeEvent(checked: boolean, name: string): void;
+ refreshInput(): void;
+ _handleChange(e: {target: HTMLInputElement}): void;
+}
+
+export const CheckedInputMixin = >(
+ superClass: T
+) => {
+ class CheckedInputMixinClass extends superClass {
+ /**
+ * Represents the checked state of the input.
+ * @type {boolean}
+ */
+ @property({type: Boolean})
+ checked = false;
+
+ /**
+ * Represents the name of the input.
+ * @type {string}
+ */
+ @property({type: String})
+ name = '';
+
+ /**
+ * Represents the label of the input.
+ * @type {string}
+ */
+ @property({type: String})
+ label = '';
+
+ getInputElement() {
+ return this.shadowRoot?.querySelector('input');
+ }
+
+ _dispatchChangeEvent(checked: boolean, name: string) {
+ const inputEvent = new CustomEvent(BasicEvents.CHANGE, {
+ detail: {checked, name},
+ bubbles: true,
+ composed: true,
+ });
+ this.dispatchEvent(inputEvent);
+ }
+ refreshInput() {
+ const inputElement = this.getInputElement();
+ if (inputElement) {
+ inputElement.checked = this.checked;
+ }
+
+ /**
+ * Called when the element’s DOM has been updated and rendered.
+ * @param {PropertyValues} _changedProperties - The changed properties.
+ */
+ }
+ protected override updated(_changedProperties: PropertyValues) {
+ this.refreshInput();
+ super.updated(_changedProperties);
+ }
+
+ /**
+ * Handles the change event on the radio.
+ * @param {Event} e - The change event.
+ */
+ _handleChange(e: {target: HTMLInputElement}) {
+ this.checked = e.target.checked;
+ this._dispatchChangeEvent(this.checked, this.name);
+ }
+ }
+ return CheckedInputMixinClass as Constructor & T;
+};
diff --git a/frontend/src/search-a-licious.ts b/frontend/src/search-a-licious.ts
index f03ec31c..fc2f4e53 100644
--- a/frontend/src/search-a-licious.ts
+++ b/frontend/src/search-a-licious.ts
@@ -1,4 +1,6 @@
export {SearchaliciousCheckbox} from './search-checkbox';
+export {SearchaliciousRadio} from './search-radio';
+export {SearchaliciousToggle} from './search-toggle';
export {SearchaliciousBar} from './search-bar';
export {SearchaliciousButton} from './search-button';
export {SearchaliciousPages} from './search-pages';
diff --git a/frontend/src/search-checkbox.ts b/frontend/src/search-checkbox.ts
index db6bf4ad..12e1f828 100644
--- a/frontend/src/search-checkbox.ts
+++ b/frontend/src/search-checkbox.ts
@@ -1,5 +1,6 @@
-import {LitElement, html, PropertyValues} from 'lit';
-import {customElement, property} from 'lit/decorators.js';
+import {LitElement, html, css} from 'lit';
+import {customElement} from 'lit/decorators.js';
+import {CheckedInputMixin} from './mixins/checked-input';
/**
* A custom element that represents a checkbox.
@@ -9,39 +10,55 @@ import {customElement, property} from 'lit/decorators.js';
* @extends {LitElement}
*/
@customElement('searchalicious-checkbox')
-export class SearchaliciousCheckbox extends LitElement {
+export class SearchaliciousCheckbox extends CheckedInputMixin(LitElement) {
/**
- * Represents the checked state of the checkbox.
- * @type {boolean}
+ * The styles for the checkbox.
+ * "appearance: none" is used to remove the default checkbox style.
+ * margin-right: 0 is used to remove the default margin between the checkbox and the label.
+ * We use an svg icon for the checked state, it is located in the public/icons folder.
+ * @type {import('lit').CSSResult}
*/
- @property({type: Boolean})
- checked = false;
-
- /**
- * Represents the name of the checkbox.
- * @type {string}
- */
- @property({type: String})
- name = '';
+ static override styles = css`
+ .checkbox-wrapper {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ }
- /**
- * Refreshes the checkbox to reflect the current state of the `checked` property.
- */
- refreshCheckbox() {
- const inputElement = this.shadowRoot?.querySelector('input');
- if (inputElement) {
- inputElement.checked = this.checked;
+ input[type='checkbox'] {
+ cursor: pointer;
+ position: relative;
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+ margin-right: 0;
+ appearance: none;
+ border: 1px solid var(--searchalicious-checkbox-color, black);
+ background-color: transparent;
+ }
+ input[type='checkbox']:checked {
+ background-color: var(--searchalicious-checkbox-color, black);
+ }
+ input[type='checkbox']:focus {
+ outline: 1px solid var(--searchalicious-checkbox-focus-color, black);
+ }
+ input[type='checkbox']:checked:after {
+ position: absolute;
+ content: '';
+ height: 12px;
+ width: 12px;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: url('/static/icons/checkbox-check.svg') no-repeat center
+ center;
}
- }
- /**
- * Called when the element’s DOM has been updated and rendered.
- * @param {PropertyValues} _changedProperties - The changed properties.
- */
- protected override updated(_changedProperties: PropertyValues) {
- this.refreshCheckbox();
- super.updated(_changedProperties);
- }
+ label {
+ cursor: pointer;
+ padding-left: 8px;
+ }
+ `;
/**
* Renders the checkbox.
@@ -49,37 +66,24 @@ export class SearchaliciousCheckbox extends LitElement {
*/
override render() {
return html`
-
+
+
+
+
`;
}
-
- /**
- * Handles the change event on the checkbox.
- * @param {Event} e - The change event.
- */
- _handleChange(e: {target: HTMLInputElement}) {
- this.checked = e.target.checked;
- const inputEvent = new CustomEvent('change', {
- detail: {checked: this.checked, name: this.name},
- bubbles: true,
- composed: true,
- });
- this.dispatchEvent(inputEvent);
- }
}
declare global {
- /**
- * The HTMLElementTagNameMap interface represents a map of custom element tag names to custom element constructors.
- * Here, it's extended to include 'searchalicious-checkbox' as a valid custom element tag name.
- */
interface HTMLElementTagNameMap {
'searchalicious-checkbox': SearchaliciousCheckbox;
}
diff --git a/frontend/src/search-facets.ts b/frontend/src/search-facets.ts
index 86194594..3353fa86 100644
--- a/frontend/src/search-facets.ts
+++ b/frontend/src/search-facets.ts
@@ -226,9 +226,6 @@ export class SearchaliciousTermsFacet extends SearchActionMixin(
fieldset {
margin-top: 1rem;
}
- .term-wrapper {
- display: block;
- }
.button {
margin-left: auto;
margin-right: auto;
@@ -383,18 +380,20 @@ export class SearchaliciousTermsFacet extends SearchActionMixin(
*/
renderTerm(term: FacetTerm) {
return html`
-