Skip to content

Commit 7862c3e

Browse files
committed
Add new select components and enhance existing ones for improved functionality
1 parent fada80a commit 7862c3e

61 files changed

Lines changed: 872 additions & 1512 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

libs/ui/accordion/src/index.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
import { HlmAccordion } from './lib/hlm-accordion';
22
import { HlmAccordionContent } from './lib/hlm-accordion-content';
3-
import { HlmAccordionIcon } from './lib/hlm-accordion-icon';
43
import { HlmAccordionItem } from './lib/hlm-accordion-item';
54
import { HlmAccordionTrigger } from './lib/hlm-accordion-trigger';
65

76
export * from './lib/hlm-accordion';
87
export * from './lib/hlm-accordion-content';
9-
export * from './lib/hlm-accordion-icon';
108
export * from './lib/hlm-accordion-item';
119
export * from './lib/hlm-accordion-trigger';
1210

13-
export const HlmAccordionImports = [
14-
HlmAccordion,
15-
HlmAccordionItem,
16-
HlmAccordionTrigger,
17-
HlmAccordionIcon,
18-
HlmAccordionContent,
19-
] as const;
11+
export const HlmAccordionImports = [HlmAccordion, HlmAccordionItem, HlmAccordionContent, HlmAccordionTrigger] as const;

libs/ui/accordion/src/lib/hlm-accordion-content.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import { classes } from '@spartan-ng/helm/utils';
66
selector: 'hlm-accordion-content',
77
changeDetection: ChangeDetectionStrategy.OnPush,
88
hostDirectives: [{ directive: BrnAccordionContent, inputs: ['style'] }],
9+
host: {
10+
'data-slot': 'accordion-content',
11+
},
912
template: `
10-
<div class="flex flex-col gap-4 pt-0 pb-4 text-balance">
13+
<div
14+
class="spartan-accordion-content-inner [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4"
15+
>
1116
<ng-content />
1217
</div>
1318
`,
1419
})
1520
export class HlmAccordionContent {
1621
constructor() {
1722
classes(
18-
() => 'text-sm transition-all data-[state=closed]:h-0 data-[state=open]:h-[var(--brn-accordion-content-height)]',
23+
() =>
24+
'spartan-accordion-content transition-all data-[state=closed]:h-0 data-[state=open]:h-(--brn-accordion-content-height)',
1925
);
2026
}
2127
}

libs/ui/accordion/src/lib/hlm-accordion-icon.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

libs/ui/accordion/src/lib/hlm-accordion-item.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ import { BrnAccordionItem } from '@spartan-ng/brain/accordion';
33
import { classes } from '@spartan-ng/helm/utils';
44

55
@Directive({
6-
selector: '[hlmAccordionItem],brn-accordion-item[hlm],hlm-accordion-item',
6+
selector: '[hlmAccordionItem],hlm-accordion-item',
77
hostDirectives: [
88
{
99
directive: BrnAccordionItem,
10-
inputs: ['isOpened'],
10+
inputs: ['isOpened', 'disabled'],
1111
outputs: ['openedChange'],
1212
},
1313
],
14+
host: {
15+
'data-slot': 'accordion-item',
16+
},
1417
})
1518
export class HlmAccordionItem {
1619
constructor() {
17-
classes(() => 'border-border flex flex-1 flex-col border-b');
20+
classes(() => 'spartan-accordion-item flex flex-col');
1821
}
1922
}
Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
1-
import { Directive } from '@angular/core';
2-
import { BrnAccordionTrigger } from '@spartan-ng/brain/accordion';
3-
import { classes } from '@spartan-ng/helm/utils';
1+
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2+
import { NgIcon, provideIcons } from '@ng-icons/core';
3+
import { lucideChevronDown, lucideChevronUp } from '@ng-icons/lucide';
4+
import { BrnAccordionImports } from '@spartan-ng/brain/accordion';
5+
import { hlm } from '@spartan-ng/helm/utils';
6+
import type { ClassValue } from 'clsx';
47

5-
@Directive({
6-
selector: '[hlmAccordionTrigger]',
7-
hostDirectives: [BrnAccordionTrigger],
8-
host: {
9-
'[style.--tw-ring-offset-shadow]': '"0 0 #000"',
10-
},
8+
@Component({
9+
selector: 'hlm-accordion-trigger',
10+
imports: [BrnAccordionImports, NgIcon],
11+
providers: [provideIcons({ lucideChevronDown, lucideChevronUp })],
12+
changeDetection: ChangeDetectionStrategy.OnPush,
13+
template: `
14+
<h3 brnAccordionHeader class="flex">
15+
<button brnAccordionTrigger data-slot="accordion-trigger" [class]="_computedTriggerClass()">
16+
<ng-content />
17+
<ng-icon
18+
name="lucideChevronDown"
19+
data-slot="accordion-trigger-icon"
20+
class="spartan-accordion-trigger-icon pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
21+
/>
22+
<ng-icon
23+
name="lucideChevronUp"
24+
data-slot="accordion-trigger-icon"
25+
class="spartan-accordion-trigger-icon pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:inline group-aria-[expanded=false]/accordion-trigger:hidden"
26+
/>
27+
</button>
28+
</h3>
29+
`,
1130
})
1231
export class HlmAccordionTrigger {
13-
constructor() {
14-
classes(
15-
() =>
16-
'group focus-visible:ring-ring focus-visible:ring-offset-background flex w-full items-center justify-between py-4 text-left font-medium transition-all hover:underline focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none [&[data-state=open]>ng-icon]:rotate-180',
17-
);
18-
}
32+
public readonly triggerClass = input<ClassValue>('');
33+
34+
protected readonly _computedTriggerClass = computed(() =>
35+
hlm(
36+
'spartan-accordion-trigger group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none aria-disabled:pointer-events-none aria-disabled:opacity-50',
37+
this.triggerClass(),
38+
),
39+
);
1940
}

libs/ui/accordion/src/lib/hlm-accordion.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import { classes } from '@spartan-ng/helm/utils';
44

55
@Directive({
66
selector: '[hlmAccordion], hlm-accordion',
7-
hostDirectives: [{ directive: BrnAccordion, inputs: ['type', 'dir', 'orientation'] }],
7+
hostDirectives: [{ directive: BrnAccordion, inputs: ['type', 'orientation'] }],
8+
host: {
9+
'data-slot': 'accordion',
10+
},
811
})
912
export class HlmAccordion {
1013
constructor() {
11-
classes(() => 'flex data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col');
14+
classes(() => 'spartan-accordion flex w-full flex-col');
1215
}
1316
}

libs/ui/checkbox/src/lib/hlm-checkbox.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import {
99
linkedSignal,
1010
model,
1111
output,
12+
viewChild,
1213
} from '@angular/core';
1314
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
1415
import { NgIcon, provideIcons } from '@ng-icons/core';
1516
import { lucideCheck } from '@ng-icons/lucide';
1617
import { BrnCheckbox } from '@spartan-ng/brain/checkbox';
18+
import { BrnFieldControlDescribedBy } from '@spartan-ng/brain/field';
1719
import type { ChangeFn, TouchFn } from '@spartan-ng/brain/forms';
1820
import { HlmIcon } from '@spartan-ng/helm/icon';
1921
import { hlm } from '@spartan-ng/helm/utils';
@@ -31,18 +33,17 @@ export const HLM_CHECKBOX_VALUE_ACCESSOR = {
3133
providers: [HLM_CHECKBOX_VALUE_ACCESSOR],
3234
viewProviders: [provideIcons({ lucideCheck })],
3335
changeDetection: ChangeDetectionStrategy.OnPush,
36+
hostDirectives: [BrnFieldControlDescribedBy],
3437
host: {
3538
class: 'contents peer',
3639
'data-slot': 'checkbox',
37-
'[attr.id]': 'null',
3840
'[attr.aria-label]': 'null',
3941
'[attr.aria-labelledby]': 'null',
40-
'[attr.aria-describedby]': 'null',
4142
'[attr.data-disabled]': '_disabled() ? "" : null',
4243
},
4344
template: `
4445
<brn-checkbox
45-
[id]="id()"
46+
[id]="inputId()"
4647
[name]="name()"
4748
[class]="_computedClass()"
4849
[checked]="checked()"
@@ -52,6 +53,7 @@ export const HLM_CHECKBOX_VALUE_ACCESSOR = {
5253
[aria-label]="ariaLabel()"
5354
[aria-labelledby]="ariaLabelledby()"
5455
[aria-describedby]="ariaDescribedby()"
56+
[forceInvalid]="forceInvalid()"
5557
(checkedChange)="_handleChange($event)"
5658
(touched)="_onTouched?.()"
5759
>
@@ -68,14 +70,15 @@ export class HlmCheckbox implements ControlValueAccessor {
6870

6971
protected readonly _computedClass = computed(() =>
7072
hlm(
71-
'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive peer size-4 shrink-0 cursor-default rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
73+
'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 peer size-4 shrink-0 cursor-default rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
7274
this.userClass(),
7375
this._disabled() ? 'cursor-not-allowed opacity-50' : '',
76+
this._errorStateClass(),
7477
),
7578
);
7679

7780
/** Used to set the id on the underlying brn element. */
78-
public readonly id = input<string | null>(null);
81+
public readonly inputId = input<string | null>(null);
7982

8083
/** Used to set the aria-label attribute on the underlying brn element. */
8184
public readonly ariaLabel = input<string | null>(null, { alias: 'aria-label' });
@@ -107,8 +110,20 @@ export class HlmCheckbox implements ControlValueAccessor {
107110
/** Whether the checkbox is disabled. */
108111
public readonly disabled = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
109112

113+
/** Whether to force the checkbox into an invalid state. */
114+
public readonly forceInvalid = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
115+
110116
protected readonly _disabled = linkedSignal(this.disabled);
111117

118+
private readonly _brnCheckbox = viewChild.required(BrnCheckbox);
119+
120+
private readonly _spartanInvalid = computed(() => this.forceInvalid() || this._brnCheckbox().spartanInvalid?.());
121+
protected readonly _errorStateClass = computed(() =>
122+
this._spartanInvalid()
123+
? 'border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40'
124+
: '',
125+
);
126+
112127
protected _onChange?: ChangeFn<boolean>;
113128
protected _onTouched?: TouchFn;
114129

libs/ui/combobox/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import { HlmComboboxItem } from './lib/hlm-combobox-item';
1010
import { HlmComboboxLabel } from './lib/hlm-combobox-label';
1111
import { HlmComboboxList } from './lib/hlm-combobox-list';
1212
import { HlmComboboxMultiple } from './lib/hlm-combobox-multiple';
13+
import { HlmComboboxPlaceholder } from './lib/hlm-combobox-placeholder';
1314
import { HlmComboboxPortal } from './lib/hlm-combobox-portal';
1415
import { HlmComboboxSeparator } from './lib/hlm-combobox-separator';
1516
import { HlmComboboxStatus } from './lib/hlm-combobox-status';
1617
import { HlmComboboxTrigger } from './lib/hlm-combobox-trigger';
1718
import { HlmComboboxValue } from './lib/hlm-combobox-value';
19+
import { HlmComboboxValueTemplate } from './lib/hlm-combobox-value-template';
1820
import { HlmComboboxValues } from './lib/hlm-combobox-values';
1921

2022
export * from './lib/hlm-combobox';
@@ -29,11 +31,13 @@ export * from './lib/hlm-combobox-item';
2931
export * from './lib/hlm-combobox-label';
3032
export * from './lib/hlm-combobox-list';
3133
export * from './lib/hlm-combobox-multiple';
34+
export * from './lib/hlm-combobox-placeholder';
3235
export * from './lib/hlm-combobox-portal';
3336
export * from './lib/hlm-combobox-separator';
3437
export * from './lib/hlm-combobox-status';
3538
export * from './lib/hlm-combobox-trigger';
3639
export * from './lib/hlm-combobox-value';
40+
export * from './lib/hlm-combobox-value-template';
3741
export * from './lib/hlm-combobox-values';
3842

3943
export const HlmComboboxImports = [
@@ -49,10 +53,12 @@ export const HlmComboboxImports = [
4953
HlmComboboxLabel,
5054
HlmComboboxList,
5155
HlmComboboxMultiple,
56+
HlmComboboxPlaceholder,
5257
HlmComboboxPortal,
5358
HlmComboboxSeparator,
5459
HlmComboboxStatus,
5560
HlmComboboxTrigger,
5661
HlmComboboxValue,
62+
HlmComboboxValueTemplate,
5763
HlmComboboxValues,
5864
] as const;

libs/ui/combobox/src/lib/hlm-combobox-chip-input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { classes } from '@spartan-ng/helm/utils';
44

55
@Directive({
66
selector: 'input[hlmComboboxChipInput]',
7-
hostDirectives: [{ directive: BrnComboboxChipInput, inputs: ['id'] }],
7+
hostDirectives: [{ directive: BrnComboboxChipInput, inputs: ['id', 'aria-invalid'] }],
88
host: {
99
'data-slott': 'combobox-chip-input',
1010
},
Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
1-
import { Directive } from '@angular/core';
2-
import { BrnComboboxAnchor, BrnComboboxInputWrapper, BrnComboboxPopoverTrigger } from '@spartan-ng/brain/combobox';
1+
import { computed, Directive } from '@angular/core';
2+
import { BrnComboboxAnchor, BrnComboboxPopoverTrigger, injectBrnComboboxBase } from '@spartan-ng/brain/combobox';
33
import { classes } from '@spartan-ng/helm/utils';
44

55
@Directive({
66
selector: '[hlmComboboxChips],hlm-combobox-chips',
7-
hostDirectives: [BrnComboboxInputWrapper, BrnComboboxAnchor, BrnComboboxPopoverTrigger],
7+
hostDirectives: [BrnComboboxAnchor, BrnComboboxPopoverTrigger],
88
host: {
9-
'data-slott': 'combobox-chips',
9+
'data-slot': 'combobox-chips',
1010
},
1111
})
1212
export class HlmComboboxChips {
13+
private readonly _combobox = injectBrnComboboxBase();
14+
15+
protected readonly _spartanInvalid = computed(() => this._combobox.controlState?.()?.spartanInvalid);
16+
17+
protected readonly _errorStateClass = computed(() =>
18+
this._spartanInvalid?.()
19+
? 'border-destructive focus-within:border-destructive focus-within:ring-destructive/20 dark:focus-within:ring-destructive/40'
20+
: '',
21+
);
22+
1323
constructor() {
14-
classes(
15-
() =>
16-
'dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 has-data-[slot=combobox-chip]:px-1.5; flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px] has-aria-invalid:ring-[3px]',
17-
);
24+
classes(() => [
25+
'dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-data-[slot=combobox-chip]:px-1.5; flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]',
26+
this._errorStateClass(),
27+
]);
1828
}
1929
}

0 commit comments

Comments
 (0)