Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
],
"rules": {
"selector-class-pattern": [
"^(tedi-[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:__[a-z][a-z0-9]*(?:-[a-z0-9]+)*)*(?:--[a-z][a-z0-9]+(?:-[a-z0-9]+)*)?|ng-[a-z]+(?:-[a-z]+)*|float-ui-[a-z]+(?:-[a-z]+)*)$",
"^((tedi|cdk)-[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:__[a-z][a-z0-9]*(?:-[a-z0-9]+)*)*(?:--[a-z][a-z0-9]+(?:-[a-z0-9]+)*)?|ng-[a-z]+(?:-[a-z]+)*|float-ui-[a-z]+(?:-[a-z]+)*)$",
{
"message": "Class selector must start with 'tedi-' prefix and follow BEM naming (e.g., .tedi-button, .tedi-button__icon, .tedi-button--primary). Selector: \"%s\"",
"resolveNestedSelectors": true
Expand Down
5 changes: 5 additions & 0 deletions community/components/form/select/multiselect.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import {
const meta: Meta<MultiselectComponent> = {
title: "Community/Form/Select/Multiselect",
component: MultiselectComponent,
parameters: {
status: {
type: ["existsInTediReady", "deprecated"],
},
},
decorators: [
moduleMetadata({
imports: [
Expand Down
10 changes: 6 additions & 4 deletions community/components/form/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import {
} from "@tedi-design-system/angular/tedi";
import { CardComponent, CardContentComponent } from "../../../components/cards";

/**
* @deprecated Use Select from TEDI-ready instead. This component will be removed from future versions.
*/
@Component({
selector: "tedi-select",
imports: [
Expand Down Expand Up @@ -70,8 +73,7 @@ import { CardComponent, CardContentComponent } from "../../../components/cards";
],
})
export class SelectComponent
implements AfterContentChecked, ControlValueAccessor
{
implements AfterContentChecked, ControlValueAccessor {
/**
* The id of the select input (for label association).
* @default ""
Expand Down Expand Up @@ -205,8 +207,8 @@ export class SelectComponent
}

// ControlValueAccessor implementation
onChange: (value: string | null) => void = () => {};
onTouched: () => void = () => {};
onChange: (value: string | null) => void = () => { };
onTouched: () => void = () => { };

writeValue(value: string): void {
this.selectedOptions.set(value ? [value] : []);
Expand Down
5 changes: 5 additions & 0 deletions community/components/form/select/select.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import { IconComponent } from "@tedi-design-system/angular/tedi";
const meta: Meta<SelectComponent> = {
title: "Community/Form/Select/Single Select",
component: SelectComponent,
parameters: {
status: {
type: ["existsInTediReady", "deprecated"],
},
},
decorators: [
moduleMetadata({
imports: [
Expand Down
28 changes: 28 additions & 0 deletions setup-jest.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
import { setupZoneTestEnv } from "jest-preset-angular/setup-env/zone";

setupZoneTestEnv();

// Mock scrollIntoView which is not implemented in jsdom
Element.prototype.scrollIntoView = jest.fn();

// Mock window.matchMedia which is not implemented in jsdom
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

// Suppress CSS parsing errors from jsdom (it doesn't support all modern CSS features)
const originalConsoleError = console.error;
console.error = (...args: unknown[]) => {
const message = args[0]?.toString() ?? "";
if (message.includes("Could not parse CSS stylesheet")) {
return;
}
originalConsoleError.apply(console, args);
};
3 changes: 2 additions & 1 deletion tedi/components/form/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./checkbox/checkbox.component";
export * from "./date-picker/date-picker.component";
export * from "./feedback-text/feedback-text.component";
export * from "./label/label.component";
export * from "./number-field/number-field.component";
export * from "./select";
export * from "./toggle/toggle.component";
export * from "./date-picker/date-picker.component";
2 changes: 2 additions & 0 deletions tedi/components/form/select/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./select.component";
export * from "./select-templates.directive";
126 changes: 126 additions & 0 deletions tedi/components/form/select/select-templates.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Directive, TemplateRef, inject } from "@angular/core";

/**
* Context provided to custom option templates.
*/
export interface SelectOptionContext<T = unknown> {
/** The option item data */
$implicit: T;
/** The option item data (explicit reference) */
item: T;
/** Index of the option in the list */
index: number;
/** Whether this option is currently selected */
selected: boolean;
/** Whether this option is disabled */
disabled: boolean;
}

/**
* Context provided to custom label templates (for displaying selected value).
*/
export interface SelectLabelContext<T = unknown> {
/** The selected item (single select) or items (multiple select) */
$implicit: T | T[];
/** The selected item(s) */
item: T | T[];
/** Function to clear a specific item (for multiple select) */
clear: (item: T) => void;
}

/**
* Context provided to custom value templates (for displaying selected value in trigger).
*/
export interface SelectValueContext<T = unknown> {
/** The selected item data */
$implicit: T;
/** The selected item data (explicit reference) */
item: T;
/** The label string for the selected item */
label: string;
}

/**
* Directive for custom option template rendering in the dropdown.
*
* @example
* ```html
* <tedi-select [items]="options">
* <ng-template tediSelectOption let-item>
* <div class="custom-option">
* <strong>{{ item.title }}</strong>
* <span>{{ item.description }}</span>
* </div>
* </ng-template>
* </tedi-select>
* ```
*/
@Directive({
selector: "[tediSelectOption]",
standalone: true,
})
export class SelectOptionTemplateDirective<T = unknown> {
template = inject<TemplateRef<SelectOptionContext<T>>>(TemplateRef);

static ngTemplateContextGuard<T>(
_dir: SelectOptionTemplateDirective<T>,
ctx: unknown
): ctx is SelectOptionContext<T> {
return true;
}
}

/**
* Directive for custom label template rendering (selected value display).
*
* @example
* ```html
* <tedi-select [items]="options">
* <ng-template tediSelectLabel let-item>
* <span class="custom-label">{{ item.name }} ({{ item.code }})</span>
* </ng-template>
* </tedi-select>
* ```
*/
@Directive({
selector: "[tediSelectLabel]",
standalone: true,
})
export class SelectLabelTemplateDirective<T = unknown> {
template = inject<TemplateRef<SelectLabelContext<T>>>(TemplateRef);

static ngTemplateContextGuard<T>(
_dir: SelectLabelTemplateDirective<T>,
ctx: unknown
): ctx is SelectLabelContext<T> {
return true;
}
}

/**
* Directive for custom value template rendering (selected value display in trigger).
* Used for single-select to display custom content like colors, icons, etc.
*
* @example
* ```html
* <tedi-select [items]="colors" bindLabel="name" bindValue="id">
* <ng-template tediSelectValue let-item>
* <div class="color-swatch" [style.background]="item.color"></div>
* </ng-template>
* </tedi-select>
* ```
*/
@Directive({
selector: "[tediSelectValue]",
standalone: true,
})
export class SelectValueTemplateDirective<T = unknown> {
template = inject<TemplateRef<SelectValueContext<T>>>(TemplateRef);

static ngTemplateContextGuard<T>(
_dir: SelectValueTemplateDirective<T>,
ctx: unknown
): ctx is SelectValueContext<T> {
return true;
}
}
Loading
Loading