@mcp-elements/angular 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +22 -0
- package/src/accordion.component.ts +74 -0
- package/src/ai-badge.component.ts +26 -0
- package/src/alert.component.ts +25 -0
- package/src/avatar.component.ts +24 -0
- package/src/badge.component.ts +17 -0
- package/src/button.component.ts +27 -0
- package/src/card.component.ts +46 -0
- package/src/chat-bubble.component.ts +53 -0
- package/src/chips.component.ts +33 -0
- package/src/counter.component.ts +48 -0
- package/src/dialog.component.ts +42 -0
- package/src/drawer.component.ts +48 -0
- package/src/dropdown-menu.component.ts +62 -0
- package/src/feedback.component.ts +71 -0
- package/src/index.ts +86 -0
- package/src/input.component.ts +46 -0
- package/src/loader.component.ts +12 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/mcp-app-frame.component.ts +60 -0
- package/src/mcp/mcp-consent-dialog.component.ts +63 -0
- package/src/mcp/mcp-resource-browser.component.ts +86 -0
- package/src/mcp/mcp-scope-inspector.component.ts +81 -0
- package/src/mcp/mcp-server-status.component.ts +44 -0
- package/src/mcp/mcp-tool-call.component.ts +105 -0
- package/src/mcp/mcp-tool-form.component.ts +127 -0
- package/src/password-input.component.ts +35 -0
- package/src/popover.component.ts +40 -0
- package/src/progress.component.ts +20 -0
- package/src/prompt-input.component.ts +70 -0
- package/src/select.component.ts +106 -0
- package/src/separator.component.ts +15 -0
- package/src/skeleton.component.ts +11 -0
- package/src/source-card.component.ts +34 -0
- package/src/streaming-text.component.ts +43 -0
- package/src/suggestion-chips.component.ts +23 -0
- package/src/switch.component.ts +32 -0
- package/src/tabs.component.ts +95 -0
- package/src/textarea.component.ts +22 -0
- package/src/toast.component.ts +62 -0
- package/src/tooltip.directive.ts +63 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Component, input, output, signal, computed, OnInit } from '@angular/core'
|
|
2
|
+
import { CommonModule } from '@angular/common'
|
|
3
|
+
import { cn, schemaToFields } from '@mcp-elements/core'
|
|
4
|
+
import type { JsonSchema, FieldDescriptor } from '@mcp-elements/core'
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'mcpe-mcp-tool-form',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [CommonModule],
|
|
10
|
+
template: `
|
|
11
|
+
<form [class]="classes()" (ngSubmit)="handleSubmit()">
|
|
12
|
+
@if (fields().length === 0) {
|
|
13
|
+
<p class="text-sm text-muted-foreground">This tool takes no inputs.</p>
|
|
14
|
+
}
|
|
15
|
+
@for (field of fields(); track field.key) {
|
|
16
|
+
<div class="mcpe-mcp-tool-form-field">
|
|
17
|
+
<label
|
|
18
|
+
[for]="field.key"
|
|
19
|
+
[class]="labelClass(field)"
|
|
20
|
+
>{{ field.label }}</label>
|
|
21
|
+
@switch (field.kind) {
|
|
22
|
+
@case ('switch') {
|
|
23
|
+
<input type="checkbox" [id]="field.key" class="mcpe-switch"
|
|
24
|
+
[checked]="getBool(field.key)"
|
|
25
|
+
(change)="onCheckChange(field.key, $event)" />
|
|
26
|
+
}
|
|
27
|
+
@case ('select') {
|
|
28
|
+
<select [id]="field.key" class="mcpe-select"
|
|
29
|
+
[value]="getStr(field.key)"
|
|
30
|
+
(change)="onInputChange(field.key, $event)">
|
|
31
|
+
<option value="">Select…</option>
|
|
32
|
+
@for (opt of field.options ?? []; track opt.value) {
|
|
33
|
+
<option [value]="opt.value">{{ opt.label }}</option>
|
|
34
|
+
}
|
|
35
|
+
</select>
|
|
36
|
+
}
|
|
37
|
+
@case ('textarea') {
|
|
38
|
+
<textarea [id]="field.key" class="mcpe-textarea" rows="4"
|
|
39
|
+
[value]="getStr(field.key)"
|
|
40
|
+
(input)="onInputChange(field.key, $event)"></textarea>
|
|
41
|
+
}
|
|
42
|
+
@case ('number') {
|
|
43
|
+
<input type="number" [id]="field.key" class="mcpe-input"
|
|
44
|
+
[value]="getStr(field.key)"
|
|
45
|
+
(input)="onNumberChange(field.key, $event)" />
|
|
46
|
+
}
|
|
47
|
+
@default {
|
|
48
|
+
<input [type]="inputType(field)" [id]="field.key" class="mcpe-input"
|
|
49
|
+
[value]="getStr(field.key)"
|
|
50
|
+
(input)="onInputChange(field.key, $event)" />
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
@if (field.help) {
|
|
54
|
+
<p class="mcpe-mcp-tool-form-help">{{ field.help }}</p>
|
|
55
|
+
}
|
|
56
|
+
</div>
|
|
57
|
+
}
|
|
58
|
+
<div class="mcpe-mcp-tool-form-submit">
|
|
59
|
+
<button type="submit" class="mcpe-btn mcpe-btn-primary mcpe-btn-sm" [disabled]="loading()">
|
|
60
|
+
{{ loading() ? 'Running…' : submitLabel() }}
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</form>
|
|
64
|
+
`,
|
|
65
|
+
})
|
|
66
|
+
export class McpeMcpToolFormComponent implements OnInit {
|
|
67
|
+
schema = input.required<JsonSchema>()
|
|
68
|
+
loading = input(false)
|
|
69
|
+
submitLabel = input('Run')
|
|
70
|
+
class = input('')
|
|
71
|
+
onSubmit = output<Record<string, unknown>>()
|
|
72
|
+
|
|
73
|
+
fields = computed(() => schemaToFields(this.schema()))
|
|
74
|
+
values = signal<Record<string, unknown>>({})
|
|
75
|
+
|
|
76
|
+
ngOnInit() {
|
|
77
|
+
const defaults: Record<string, unknown> = {}
|
|
78
|
+
for (const f of this.fields()) {
|
|
79
|
+
if (f.defaultValue !== undefined) defaults[f.key] = f.defaultValue
|
|
80
|
+
}
|
|
81
|
+
this.values.set(defaults)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
classes = computed(() => cn('mcpe-mcp-tool-form', this.class()))
|
|
85
|
+
|
|
86
|
+
setValue(key: string, value: unknown) {
|
|
87
|
+
this.values.update((v: Record<string, unknown>) => ({ ...v, [key]: value }))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
onInputChange(key: string, event: Event) {
|
|
91
|
+
this.setValue(key, (event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement).value)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onNumberChange(key: string, event: Event) {
|
|
95
|
+
this.setValue(key, (event.target as HTMLInputElement).valueAsNumber)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
onCheckChange(key: string, event: Event) {
|
|
99
|
+
this.setValue(key, (event.target as HTMLInputElement).checked)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getStr(key: string): string {
|
|
103
|
+
const v = this.values()[key]
|
|
104
|
+
return v == null ? '' : String(v)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getBool(key: string): boolean {
|
|
108
|
+
return Boolean(this.values()[key])
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
labelClass(field: FieldDescriptor): string {
|
|
112
|
+
return cn('mcpe-mcp-tool-form-label', field.required ? 'mcpe-mcp-tool-form-label-required' : '')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
inputType(field: FieldDescriptor): string {
|
|
116
|
+
switch (field.kind) {
|
|
117
|
+
case 'email': return 'email'
|
|
118
|
+
case 'url': return 'url'
|
|
119
|
+
case 'date': return 'date'
|
|
120
|
+
default: return 'text'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
handleSubmit() {
|
|
125
|
+
this.onSubmit.emit(this.values())
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Component, input, signal, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-password-input',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `
|
|
7
|
+
<div class="mcpe-password-input-wrapper">
|
|
8
|
+
<input
|
|
9
|
+
[type]="showPassword() ? 'text' : 'password'"
|
|
10
|
+
class="mcpe-password-input"
|
|
11
|
+
[placeholder]="placeholder()"
|
|
12
|
+
[disabled]="disabled()"
|
|
13
|
+
/>
|
|
14
|
+
<button type="button" class="mcpe-password-toggle" (click)="showPassword.update(v => !v)"
|
|
15
|
+
[attr.aria-label]="showPassword() ? 'Hide password' : 'Show password'" tabindex="-1">
|
|
16
|
+
@if (showPassword()) {
|
|
17
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
18
|
+
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
|
|
19
|
+
<line x1="1" y1="1" x2="23" y2="23" />
|
|
20
|
+
</svg>
|
|
21
|
+
} @else {
|
|
22
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
23
|
+
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
|
24
|
+
<circle cx="12" cy="12" r="3" />
|
|
25
|
+
</svg>
|
|
26
|
+
}
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
`,
|
|
30
|
+
})
|
|
31
|
+
export class SnxPasswordInputComponent {
|
|
32
|
+
placeholder = input('')
|
|
33
|
+
disabled = input(false)
|
|
34
|
+
showPassword = signal(false)
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Component, input, signal, ElementRef, viewChild } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-popover',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `
|
|
7
|
+
<div class="relative inline-block" #container>
|
|
8
|
+
<div (click)="toggle()">
|
|
9
|
+
<ng-content select="[trigger]" />
|
|
10
|
+
</div>
|
|
11
|
+
@if (isOpen()) {
|
|
12
|
+
<div
|
|
13
|
+
class="mcpe-popover-content absolute top-full mt-2"
|
|
14
|
+
role="dialog"
|
|
15
|
+
(keydown.escape)="close()"
|
|
16
|
+
>
|
|
17
|
+
<ng-content />
|
|
18
|
+
</div>
|
|
19
|
+
}
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
host: {
|
|
23
|
+
'(document:click)': 'onDocumentClick($event)',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
export class SnxPopoverComponent {
|
|
27
|
+
isOpen = signal(false)
|
|
28
|
+
container = viewChild<ElementRef>('container')
|
|
29
|
+
|
|
30
|
+
toggle() { this.isOpen.update(v => !v) }
|
|
31
|
+
close() { this.isOpen.set(false) }
|
|
32
|
+
show() { this.isOpen.set(true) }
|
|
33
|
+
|
|
34
|
+
onDocumentClick(event: MouseEvent) {
|
|
35
|
+
const el = this.container()?.nativeElement
|
|
36
|
+
if (el && !el.contains(event.target as Node)) {
|
|
37
|
+
this.isOpen.set(false)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Component, input, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-progress',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `
|
|
7
|
+
<div class="mcpe-progress" role="progressbar"
|
|
8
|
+
[attr.aria-valuenow]="value()" [attr.aria-valuemin]="0" [attr.aria-valuemax]="max()">
|
|
9
|
+
<div class="mcpe-progress-indicator" [style.transform]="transform()"></div>
|
|
10
|
+
</div>
|
|
11
|
+
`,
|
|
12
|
+
})
|
|
13
|
+
export class SnxProgressComponent {
|
|
14
|
+
value = input(0)
|
|
15
|
+
max = input(100)
|
|
16
|
+
transform = computed(() => {
|
|
17
|
+
const pct = Math.min(Math.max((this.value() / this.max()) * 100, 0), 100)
|
|
18
|
+
return `translateX(-${100 - pct}%)`
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Component, input, output, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-prompt-input',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `<div [class]="classes()"><ng-content /></div>`,
|
|
7
|
+
})
|
|
8
|
+
export class SnxPromptInputComponent {
|
|
9
|
+
class = input('')
|
|
10
|
+
classes = computed(() => ['mcpe-prompt-input', this.class()].filter(Boolean).join(' '))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'mcpe-prompt-input-textarea',
|
|
15
|
+
standalone: true,
|
|
16
|
+
template: `<textarea class="mcpe-prompt-input-textarea" [placeholder]="placeholder()" [rows]="rows()"></textarea>`,
|
|
17
|
+
})
|
|
18
|
+
export class SnxPromptInputTextareaComponent {
|
|
19
|
+
placeholder = input('')
|
|
20
|
+
rows = input(3)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'mcpe-prompt-input-footer',
|
|
25
|
+
standalone: true,
|
|
26
|
+
template: `<div class="mcpe-prompt-input-footer"><ng-content /></div>`,
|
|
27
|
+
})
|
|
28
|
+
export class SnxPromptInputFooterComponent {}
|
|
29
|
+
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'mcpe-prompt-input-actions',
|
|
32
|
+
standalone: true,
|
|
33
|
+
template: `<div class="mcpe-prompt-input-actions"><ng-content /></div>`,
|
|
34
|
+
})
|
|
35
|
+
export class SnxPromptInputActionsComponent {}
|
|
36
|
+
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'mcpe-prompt-input-char-count',
|
|
39
|
+
standalone: true,
|
|
40
|
+
template: `<span class="mcpe-prompt-input-char-count">{{ count() }}@if (max()) { / {{ max() }} }</span>`,
|
|
41
|
+
})
|
|
42
|
+
export class SnxPromptInputCharCountComponent {
|
|
43
|
+
count = input(0)
|
|
44
|
+
max = input<number | undefined>(undefined)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Component({
|
|
48
|
+
selector: 'mcpe-prompt-input-attachments',
|
|
49
|
+
standalone: true,
|
|
50
|
+
template: `<div class="mcpe-prompt-input-attachments"><ng-content /></div>`,
|
|
51
|
+
})
|
|
52
|
+
export class SnxPromptInputAttachmentsComponent {}
|
|
53
|
+
|
|
54
|
+
@Component({
|
|
55
|
+
selector: 'mcpe-prompt-input-attachment',
|
|
56
|
+
standalone: true,
|
|
57
|
+
template: `
|
|
58
|
+
<span class="mcpe-prompt-input-attachment">
|
|
59
|
+
<ng-content />
|
|
60
|
+
<button type="button" class="mcpe-prompt-input-attachment-remove" (click)="remove.emit()" aria-label="Remove">
|
|
61
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
62
|
+
<path d="M18 6 6 18" /><path d="m6 6 12 12" />
|
|
63
|
+
</svg>
|
|
64
|
+
</button>
|
|
65
|
+
</span>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class SnxPromptInputAttachmentComponent {
|
|
69
|
+
remove = output()
|
|
70
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Component, input, output, signal, computed, ElementRef, viewChild, HostListener } from '@angular/core'
|
|
2
|
+
import { createSelect, type SelectOption } from '@mcp-elements/core'
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'mcpe-select',
|
|
6
|
+
standalone: true,
|
|
7
|
+
template: `
|
|
8
|
+
<div class="relative" #container>
|
|
9
|
+
<button
|
|
10
|
+
class="mcpe-select-trigger"
|
|
11
|
+
[attr.aria-expanded]="isOpen()"
|
|
12
|
+
aria-haspopup="listbox"
|
|
13
|
+
(click)="toggle()"
|
|
14
|
+
(keydown)="onKeyDown($event)"
|
|
15
|
+
>
|
|
16
|
+
<span>{{ selectedLabel() || placeholder() }}</span>
|
|
17
|
+
<svg class="h-4 w-4 opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
18
|
+
<path d="m6 9 6 6 6-6" />
|
|
19
|
+
</svg>
|
|
20
|
+
</button>
|
|
21
|
+
@if (isOpen()) {
|
|
22
|
+
<div class="mcpe-select-content absolute top-full mt-1 w-full">
|
|
23
|
+
<div class="mcpe-select-viewport" role="listbox">
|
|
24
|
+
@for (option of options(); track option.value; let i = $index) {
|
|
25
|
+
<div
|
|
26
|
+
role="option"
|
|
27
|
+
[attr.aria-selected]="option.value === selectedValue()"
|
|
28
|
+
[attr.aria-disabled]="option.disabled"
|
|
29
|
+
[class]="getItemClasses(option, i)"
|
|
30
|
+
(click)="selectOption(option)"
|
|
31
|
+
(mouseenter)="highlightedIndex.set(i)"
|
|
32
|
+
>
|
|
33
|
+
{{ option.label }}
|
|
34
|
+
</div>
|
|
35
|
+
}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
}
|
|
39
|
+
</div>
|
|
40
|
+
`,
|
|
41
|
+
host: {
|
|
42
|
+
'(document:click)': 'onDocumentClick($event)',
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
export class SnxSelectComponent {
|
|
46
|
+
options = input<SelectOption[]>([])
|
|
47
|
+
placeholder = input('Select...')
|
|
48
|
+
valueChange = output<string>()
|
|
49
|
+
|
|
50
|
+
isOpen = signal(false)
|
|
51
|
+
selectedValue = signal<string | null>(null)
|
|
52
|
+
highlightedIndex = signal(0)
|
|
53
|
+
|
|
54
|
+
container = viewChild<ElementRef>('container')
|
|
55
|
+
|
|
56
|
+
selectedLabel = computed(() => {
|
|
57
|
+
const val = this.selectedValue()
|
|
58
|
+
return this.options().find(o => o.value === val)?.label ?? ''
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
toggle() { this.isOpen.update(v => !v) }
|
|
62
|
+
|
|
63
|
+
selectOption(option: SelectOption) {
|
|
64
|
+
if (option.disabled) return
|
|
65
|
+
this.selectedValue.set(option.value)
|
|
66
|
+
this.valueChange.emit(option.value)
|
|
67
|
+
this.isOpen.set(false)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getItemClasses(option: SelectOption, index: number): string {
|
|
71
|
+
return [
|
|
72
|
+
'mcpe-select-item',
|
|
73
|
+
index === this.highlightedIndex() ? 'mcpe-select-item-active' : '',
|
|
74
|
+
option.value === this.selectedValue() ? 'mcpe-select-item-selected' : '',
|
|
75
|
+
].filter(Boolean).join(' ')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onKeyDown(event: KeyboardEvent) {
|
|
79
|
+
const opts = this.options().filter(o => !o.disabled)
|
|
80
|
+
if (event.key === 'ArrowDown') {
|
|
81
|
+
event.preventDefault()
|
|
82
|
+
if (!this.isOpen()) { this.isOpen.set(true); return }
|
|
83
|
+
this.highlightedIndex.update(i => (i + 1) % opts.length)
|
|
84
|
+
} else if (event.key === 'ArrowUp') {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
this.highlightedIndex.update(i => (i - 1 + opts.length) % opts.length)
|
|
87
|
+
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
88
|
+
event.preventDefault()
|
|
89
|
+
if (this.isOpen()) {
|
|
90
|
+
const opt = opts[this.highlightedIndex()]
|
|
91
|
+
if (opt) this.selectOption(opt)
|
|
92
|
+
} else {
|
|
93
|
+
this.isOpen.set(true)
|
|
94
|
+
}
|
|
95
|
+
} else if (event.key === 'Escape') {
|
|
96
|
+
this.isOpen.set(false)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onDocumentClick(event: MouseEvent) {
|
|
101
|
+
const el = this.container()?.nativeElement
|
|
102
|
+
if (el && !el.contains(event.target as Node)) {
|
|
103
|
+
this.isOpen.set(false)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Component, input, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-separator',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `<div [class]="classes()" role="separator" [attr.aria-orientation]="orientation()"></div>`,
|
|
7
|
+
})
|
|
8
|
+
export class SnxSeparatorComponent {
|
|
9
|
+
orientation = input<'horizontal' | 'vertical'>('horizontal')
|
|
10
|
+
class = input('')
|
|
11
|
+
|
|
12
|
+
classes = computed(() =>
|
|
13
|
+
['mcpe-separator', `mcpe-separator-${this.orientation()}`, this.class()].filter(Boolean).join(' ')
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Component, input, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-skeleton',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `<div [class]="classes()"></div>`,
|
|
7
|
+
})
|
|
8
|
+
export class SnxSkeletonComponent {
|
|
9
|
+
class = input('')
|
|
10
|
+
classes = computed(() => ['mcpe-skeleton', this.class()].filter(Boolean).join(' '))
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Component, input } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-source-cards',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `<div class="mcpe-source-cards"><ng-content /></div>`,
|
|
7
|
+
})
|
|
8
|
+
export class SnxSourceCardsComponent {}
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'mcpe-source-card',
|
|
12
|
+
standalone: true,
|
|
13
|
+
template: `
|
|
14
|
+
<a [href]="href()" class="mcpe-source-card" target="_blank" rel="noopener noreferrer">
|
|
15
|
+
@if (favicon()) {
|
|
16
|
+
<img class="mcpe-source-card-favicon" [src]="favicon()" [alt]="domain()" />
|
|
17
|
+
}
|
|
18
|
+
<div class="mcpe-source-card-body">
|
|
19
|
+
<p class="mcpe-source-card-title">{{ title() }}</p>
|
|
20
|
+
<p class="mcpe-source-card-domain">{{ domain() }}</p>
|
|
21
|
+
</div>
|
|
22
|
+
@if (index()) {
|
|
23
|
+
<span class="mcpe-source-card-index">{{ index() }}</span>
|
|
24
|
+
}
|
|
25
|
+
</a>
|
|
26
|
+
`,
|
|
27
|
+
})
|
|
28
|
+
export class SnxSourceCardComponent {
|
|
29
|
+
href = input.required<string>()
|
|
30
|
+
favicon = input('')
|
|
31
|
+
title = input('')
|
|
32
|
+
domain = input('')
|
|
33
|
+
index = input<number | undefined>(undefined)
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Component } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-streaming-text',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `<div class="mcpe-streaming-text-cursor"><ng-content /></div>`,
|
|
7
|
+
})
|
|
8
|
+
export class SnxStreamingTextComponent {}
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'mcpe-streaming-text-fade-in',
|
|
12
|
+
standalone: true,
|
|
13
|
+
template: `<span class="mcpe-streaming-text-fade-in"><ng-content /></span>`,
|
|
14
|
+
})
|
|
15
|
+
export class SnxStreamingTextFadeInComponent {}
|
|
16
|
+
|
|
17
|
+
@Component({
|
|
18
|
+
selector: 'mcpe-streaming-text-word',
|
|
19
|
+
standalone: true,
|
|
20
|
+
template: `<span class="mcpe-streaming-text-word"><ng-content /></span>`,
|
|
21
|
+
})
|
|
22
|
+
export class SnxStreamingTextWordComponent {}
|
|
23
|
+
|
|
24
|
+
@Component({
|
|
25
|
+
selector: 'mcpe-streaming-text-line',
|
|
26
|
+
standalone: true,
|
|
27
|
+
template: `<div class="mcpe-streaming-text-line"><ng-content /></div>`,
|
|
28
|
+
})
|
|
29
|
+
export class SnxStreamingTextLineComponent {}
|
|
30
|
+
|
|
31
|
+
@Component({
|
|
32
|
+
selector: 'mcpe-streaming-text-skeleton',
|
|
33
|
+
standalone: true,
|
|
34
|
+
template: `<div class="mcpe-streaming-text-skeleton"><ng-content /></div>`,
|
|
35
|
+
})
|
|
36
|
+
export class SnxStreamingTextSkeletonComponent {}
|
|
37
|
+
|
|
38
|
+
@Component({
|
|
39
|
+
selector: 'mcpe-streaming-text-skeleton-line',
|
|
40
|
+
standalone: true,
|
|
41
|
+
template: `<div class="mcpe-streaming-text-skeleton-line"></div>`,
|
|
42
|
+
})
|
|
43
|
+
export class SnxStreamingTextSkeletonLineComponent {}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Component, input, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
type SuggestionChipVariant = 'default' | 'primary' | 'outline'
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'mcpe-suggestion-chips',
|
|
7
|
+
standalone: true,
|
|
8
|
+
template: `<div class="mcpe-suggestion-chips"><ng-content /></div>`,
|
|
9
|
+
})
|
|
10
|
+
export class SnxSuggestionChipsComponent {}
|
|
11
|
+
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'mcpe-suggestion-chip',
|
|
14
|
+
standalone: true,
|
|
15
|
+
template: `<button [class]="classes()" type="button"><ng-content /></button>`,
|
|
16
|
+
})
|
|
17
|
+
export class SnxSuggestionChipComponent {
|
|
18
|
+
variant = input<SuggestionChipVariant>('default')
|
|
19
|
+
class = input('')
|
|
20
|
+
classes = computed(() =>
|
|
21
|
+
['mcpe-suggestion-chip', `mcpe-suggestion-chip-${this.variant()}`, this.class()].filter(Boolean).join(' ')
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Component, input, output, signal, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-switch',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `
|
|
7
|
+
<button
|
|
8
|
+
type="button"
|
|
9
|
+
role="switch"
|
|
10
|
+
[attr.aria-checked]="checked()"
|
|
11
|
+
[attr.aria-disabled]="disabled()"
|
|
12
|
+
[disabled]="disabled()"
|
|
13
|
+
[class]="'mcpe-switch'"
|
|
14
|
+
(click)="toggle()"
|
|
15
|
+
(keydown.space)="$event.preventDefault(); toggle()"
|
|
16
|
+
(keydown.enter)="$event.preventDefault(); toggle()"
|
|
17
|
+
>
|
|
18
|
+
<span class="mcpe-switch-thumb"></span>
|
|
19
|
+
</button>
|
|
20
|
+
`,
|
|
21
|
+
})
|
|
22
|
+
export class SnxSwitchComponent {
|
|
23
|
+
checked = signal(false)
|
|
24
|
+
disabled = input(false)
|
|
25
|
+
checkedChange = output<boolean>()
|
|
26
|
+
|
|
27
|
+
toggle() {
|
|
28
|
+
if (this.disabled()) return
|
|
29
|
+
this.checked.update(v => !v)
|
|
30
|
+
this.checkedChange.emit(this.checked())
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Component, input, output, signal, computed, effect, contentChildren, ElementRef } from '@angular/core'
|
|
2
|
+
import { createTabs, type TabItem } from '@mcp-elements/core'
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'mcpe-tabs',
|
|
6
|
+
standalone: true,
|
|
7
|
+
template: `<div><ng-content /></div>`,
|
|
8
|
+
})
|
|
9
|
+
export class SnxTabsComponent {
|
|
10
|
+
items = input<TabItem[]>([])
|
|
11
|
+
defaultValue = input<string>('')
|
|
12
|
+
activeValue = signal('')
|
|
13
|
+
|
|
14
|
+
private api = computed(() =>
|
|
15
|
+
createTabs(this.items(), {
|
|
16
|
+
defaultValue: this.defaultValue(),
|
|
17
|
+
onValueChange: (v) => this.activeValue.set(v),
|
|
18
|
+
})
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
effect(() => {
|
|
23
|
+
const def = this.defaultValue()
|
|
24
|
+
const items = this.items()
|
|
25
|
+
if (def) {
|
|
26
|
+
this.activeValue.set(def)
|
|
27
|
+
} else if (items.length > 0) {
|
|
28
|
+
this.activeValue.set(items[0].value)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getTabProps(value: string) {
|
|
34
|
+
return this.api().getTriggerProps(value, this.activeValue())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getPanelProps(value: string) {
|
|
38
|
+
return this.api().getPanelProps(value, this.activeValue())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
isActive(value: string): boolean {
|
|
42
|
+
return this.activeValue() === value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
select(value: string) {
|
|
46
|
+
this.activeValue.set(value)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Component({
|
|
51
|
+
selector: 'mcpe-tabs-list',
|
|
52
|
+
standalone: true,
|
|
53
|
+
template: `<div role="tablist" class="mcpe-tabs-list"><ng-content /></div>`,
|
|
54
|
+
})
|
|
55
|
+
export class SnxTabsListComponent {}
|
|
56
|
+
|
|
57
|
+
@Component({
|
|
58
|
+
selector: 'mcpe-tabs-trigger',
|
|
59
|
+
standalone: true,
|
|
60
|
+
template: `
|
|
61
|
+
<button
|
|
62
|
+
role="tab"
|
|
63
|
+
[class]="classes()"
|
|
64
|
+
[attr.aria-selected]="isActive()"
|
|
65
|
+
[attr.tabindex]="isActive() ? 0 : -1"
|
|
66
|
+
(click)="onClick.emit()"
|
|
67
|
+
>
|
|
68
|
+
<ng-content />
|
|
69
|
+
</button>
|
|
70
|
+
`,
|
|
71
|
+
})
|
|
72
|
+
export class SnxTabsTriggerComponent {
|
|
73
|
+
isActive = input(false)
|
|
74
|
+
onClick = output<void>()
|
|
75
|
+
class = input('')
|
|
76
|
+
|
|
77
|
+
classes = computed(() =>
|
|
78
|
+
['mcpe-tabs-trigger', this.isActive() ? 'mcpe-tabs-trigger-active' : '', this.class()]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join(' ')
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Component({
|
|
85
|
+
selector: 'mcpe-tabs-content',
|
|
86
|
+
standalone: true,
|
|
87
|
+
template: `
|
|
88
|
+
@if (isActive()) {
|
|
89
|
+
<div role="tabpanel" class="mcpe-tabs-content"><ng-content /></div>
|
|
90
|
+
}
|
|
91
|
+
`,
|
|
92
|
+
})
|
|
93
|
+
export class SnxTabsContentComponent {
|
|
94
|
+
isActive = input(false)
|
|
95
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Component, input, computed } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'mcpe-textarea',
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `
|
|
7
|
+
<textarea
|
|
8
|
+
[class]="classes()"
|
|
9
|
+
[placeholder]="placeholder()"
|
|
10
|
+
[disabled]="disabled()"
|
|
11
|
+
[rows]="rows()"
|
|
12
|
+
><ng-content /></textarea>
|
|
13
|
+
`,
|
|
14
|
+
})
|
|
15
|
+
export class SnxTextareaComponent {
|
|
16
|
+
placeholder = input('')
|
|
17
|
+
disabled = input(false)
|
|
18
|
+
rows = input(3)
|
|
19
|
+
class = input('')
|
|
20
|
+
|
|
21
|
+
classes = computed(() => ['mcpe-textarea', this.class()].filter(Boolean).join(' '))
|
|
22
|
+
}
|