@ng-primitives/mcp 0.95.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/package.json +30 -0
- package/src/generated/api-data.json +2802 -0
- package/src/generated/primitives-data.json +1521 -0
- package/src/generated/reusable-components.json +226 -0
- package/src/generated/types.d.ts +26 -0
- package/src/generated/types.js +4 -0
- package/src/generated/types.js.map +1 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +31 -0
- package/src/index.js.map +1 -0
- package/src/primitives-registry.d.ts +40 -0
- package/src/primitives-registry.js +97 -0
- package/src/primitives-registry.js.map +1 -0
- package/src/tools.d.ts +9 -0
- package/src/tools.js +259 -0
- package/src/tools.js.map +1 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "accordion",
|
|
4
|
+
"code": "import { Component, input } from '@angular/core';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDownMini } from '@ng-icons/heroicons/mini';\nimport {\n NgpAccordionContent,\n NgpAccordionItem,\n NgpAccordionTrigger,\n} from 'ng-primitives/accordion';\nimport { NgpButton } from 'ng-primitives/button';\n\n@Component({\n selector: 'app-accordion-item',\n hostDirectives: [\n {\n directive: NgpAccordionItem,\n inputs: ['ngpAccordionItemValue:value', 'ngpAccordionItemDisabled:disabled'],\n },\n ],\n imports: [NgpAccordionContent, NgpAccordionTrigger, NgpButton, NgIcon],\n providers: [provideIcons({ heroChevronDownMini })],\n template: `\n <button ngpAccordionTrigger ngpButton>\n {{ heading() }}\n\n <ng-icon name=\"heroChevronDownMini\" />\n </button>\n <div ngpAccordionContent>\n <ng-content />\n </div>\n `,\n styles: `\n :host {\n display: block;\n }\n\n :host:has(+ :host) {\n border-bottom: 1px solid var(--ngp-border);\n }\n\n [ngpAccordionTrigger] {\n display: flex;\n padding-left: 1rem;\n padding-right: 1rem;\n font-size: 0.875rem;\n line-height: 1.25rem;\n font-weight: 500;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 2.75rem;\n border-radius: 0.75rem;\n outline: none;\n color: var(--ngp-text-primary);\n background-color: var(--ngp-background);\n border: none;\n }\n\n [ngpAccordionTrigger][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n [ngpAccordionContent] {\n font-size: 0.875rem;\n color: var(--ngp-text-secondary);\n overflow: hidden;\n padding: 0 16px;\n box-sizing: border-box;\n }\n\n [ngpAccordionContent][data-open] {\n animation: slideDown 0.2s ease-in-out forwards;\n }\n\n [ngpAccordionContent][data-closed] {\n animation: slideUp 0.2s ease-in-out forwards;\n }\n\n ng-icon {\n font-size: 1.25rem;\n color: var(--ngp-text-secondary);\n }\n\n [ngpAccordionTrigger][data-open] ng-icon {\n transform: rotate(180deg);\n }\n\n @keyframes slideDown {\n from {\n height: 0;\n }\n to {\n height: var(--ngp-accordion-content-height);\n }\n }\n\n @keyframes slideUp {\n from {\n height: var(--ngp-accordion-content-height);\n }\n to {\n height: 0;\n }\n }\n `,\n})\nexport class AccordionItem {\n /** The accordion item heading */\n readonly heading = input.required<string>();\n}\n",
|
|
5
|
+
"primitive": "accordion",
|
|
6
|
+
"hasVariants": false,
|
|
7
|
+
"hasSizes": true
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "avatar",
|
|
11
|
+
"code": "import { Component, input } from '@angular/core';\nimport { NgpAvatar, NgpAvatarFallback, NgpAvatarImage } from 'ng-primitives/avatar';\n\n@Component({\n selector: 'app-avatar',\n hostDirectives: [NgpAvatar],\n imports: [NgpAvatarImage, NgpAvatarFallback],\n template: `\n @if (image()) {\n <img [src]=\"image()\" ngpAvatarImage alt=\"Profile Image\" />\n }\n <span ngpAvatarFallback>{{ fallback() }}</span>\n `,\n styles: `\n :host {\n position: relative;\n display: inline-flex;\n width: 3rem;\n height: 3rem;\n align-items: center;\n justify-content: center;\n border-radius: 9999px;\n border-width: 2px;\n border-color: var(--ngp-avatar-border);\n background-color: var(--ngp-avatar-background);\n vertical-align: middle;\n overflow: hidden;\n }\n\n :host:before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: 9999px;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);\n }\n\n [ngpAvatarImage] {\n width: 100%;\n height: 100%;\n }\n\n [ngpAvatarFallback] {\n text-align: center;\n font-weight: 500;\n color: var(--ngp-text-emphasis);\n }\n `,\n})\nexport class Avatar {\n /** Define the avatar image source */\n readonly image = input<string>();\n\n /** Define the avatar fallback text */\n readonly fallback = input<string>();\n}\n",
|
|
12
|
+
"primitive": "avatar",
|
|
13
|
+
"hasVariants": false,
|
|
14
|
+
"hasSizes": false
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "button",
|
|
18
|
+
"code": "import { Component, input } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\n\n/**\n * The size of the button.\n */\nexport type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';\n\n/**\n * The variant of the button.\n */\nexport type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link';\n\n@Component({\n selector: 'button[app-button]',\n hostDirectives: [{ directive: NgpButton, inputs: ['disabled'] }],\n template: `\n <ng-content select=\"[slot=leading]\" />\n <ng-content />\n <ng-content select=\"[slot=trailing]\" />\n `,\n host: {\n '[attr.data-size]': 'size()',\n '[attr.data-variant]': 'variant()',\n },\n styles: `\n :host {\n padding-left: 1rem;\n padding-right: 1rem;\n border-radius: 0.5rem;\n color: var(--ngp-text-primary);\n border: none;\n height: 2.5rem;\n font-weight: 500;\n background-color: var(--ngp-background);\n transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--ngp-button-shadow);\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n gap: 0.5rem;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n /* Size variants */\n :host[data-size='sm'] {\n height: 2rem;\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n font-size: 0.875rem;\n --ng-icon__size: 0.875rem;\n }\n\n :host[data-size='md'],\n :host:not([data-size]) {\n height: 2.5rem;\n padding-left: 1rem;\n padding-right: 1rem;\n font-size: 0.875rem;\n --ng-icon__size: 0.875rem;\n }\n\n :host[data-size='lg'] {\n height: 3rem;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n font-size: 1rem;\n --ng-icon__size: 1rem;\n }\n\n :host[data-size='xl'] {\n height: 3.5rem;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n font-size: 1.125rem;\n --ng-icon__size: 1.125rem;\n }\n\n /* Variant styles */\n :host[data-variant='primary'],\n :host:not([data-variant]) {\n background-color: var(--ngp-background);\n color: var(--ngp-text-primary);\n border: none;\n }\n\n :host[data-variant='primary'][data-hover],\n :host:not([data-variant])[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-variant='primary'][data-press],\n :host:not([data-variant])[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-variant='secondary'] {\n background-color: var(--ngp-secondary-background, #f1f5f9);\n color: var(--ngp-secondary-text, #0f172a);\n border: none;\n }\n\n :host[data-variant='secondary'][data-hover] {\n background-color: var(--ngp-secondary-background-hover, #e2e8f0);\n }\n\n :host[data-variant='secondary'][data-press] {\n background-color: var(--ngp-secondary-background-active, #cbd5e1);\n }\n\n :host[data-variant='destructive'] {\n background-color: var(--ngp-destructive-background, #ef4444);\n color: var(--ngp-destructive-text, #ffffff);\n border: none;\n }\n\n :host[data-variant='destructive'][data-hover] {\n background-color: var(--ngp-destructive-background-hover, #dc2626);\n }\n\n :host[data-variant='destructive'][data-press] {\n background-color: var(--ngp-destructive-background-active, #b91c1c);\n }\n\n :host[data-variant='outline'] {\n background-color: transparent;\n color: var(--ngp-text-primary);\n border: 1px solid var(--ngp-outline-border, #e2e8f0);\n box-shadow: none;\n }\n\n :host[data-variant='outline'][data-hover] {\n background-color: var(--ngp-background-hover);\n border-color: var(--ngp-outline-border-hover, #cbd5e1);\n }\n\n :host[data-variant='outline'][data-press] {\n background-color: var(--ngp-outline-background-active, rgba(15, 23, 42, 0.1));\n }\n\n :host[data-variant='ghost'] {\n background-color: transparent;\n color: var(--ngp-text-primary);\n border: none;\n box-shadow: none;\n }\n\n :host[data-variant='ghost'][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-variant='ghost'][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-variant='link'] {\n background-color: transparent;\n color: var(--ngp-link-color, #3b82f6);\n border: none;\n box-shadow: none;\n text-decoration: underline;\n text-underline-offset: 4px;\n }\n\n :host[data-variant='link'][data-hover] {\n text-decoration-thickness: 2px;\n }\n\n :host[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n }\n `,\n})\nexport class Button {\n /**\n * The size of the button.\n */\n readonly size = input<ButtonSize>('md');\n\n /**\n * The variant of the button.\n */\n readonly variant = input<ButtonVariant>('primary');\n}\n",
|
|
19
|
+
"primitive": "button",
|
|
20
|
+
"hasVariants": true,
|
|
21
|
+
"hasSizes": true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "checkbox",
|
|
25
|
+
"code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroCheckMini, heroMinusMini } from '@ng-icons/heroicons/mini';\nimport { injectCheckboxState, NgpCheckbox } from 'ng-primitives/checkbox';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-checkbox',\n hostDirectives: [\n {\n directive: NgpCheckbox,\n inputs: [\n 'ngpCheckboxChecked:checked',\n 'ngpCheckboxIndeterminate:indeterminate',\n 'ngpCheckboxDisabled:disabled',\n ],\n outputs: [\n 'ngpCheckboxCheckedChange:checkedChange',\n 'ngpCheckboxIndeterminateChange:indeterminateChange',\n ],\n },\n ],\n providers: [provideValueAccessor(Checkbox), provideIcons({ heroCheckMini, heroMinusMini })],\n imports: [NgIcon],\n template: `\n @if (state().indeterminate()) {\n <ng-icon name=\"heroMinusMini\" />\n } @else if (state().checked()) {\n <ng-icon name=\"heroCheckMini\" />\n }\n `,\n styles: `\n :host {\n display: flex;\n width: 1.25rem;\n height: 1.25rem;\n cursor: pointer;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n border: 1px solid var(--ngp-border);\n background-color: transparent;\n padding: 0;\n outline: none;\n flex: none;\n color: var(--ngp-text-inverse);\n font-size: 0.75rem;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-checked],\n :host[data-indeterminate] {\n border-color: var(--ngp-background-inverse);\n background-color: var(--ngp-background-inverse);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n `,\n host: {\n '(focusout)': 'onTouchedFn?.()',\n },\n})\nexport class Checkbox implements ControlValueAccessor {\n /**\n * The checked state of the checkbox.\n */\n protected readonly state = injectCheckboxState();\n\n /**\n * The onChange function for the checkbox.\n */\n protected onChangeFn?: ChangeFn<boolean>;\n\n /**\n * The onTouched function for the checkbox.\n */\n protected onTouchedFn?: TouchedFn;\n\n constructor() {\n // Whenever the user interacts with the checkbox, call the onChange function with the new value.\n this.state().checkedChange.subscribe(checked => this.onChangeFn?.(checked));\n }\n\n writeValue(checked: boolean): void {\n this.state().setChecked(checked);\n }\n\n registerOnChange(fn: ChangeFn<boolean>): void {\n this.onChangeFn = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouchedFn = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
|
|
26
|
+
"primitive": "checkbox",
|
|
27
|
+
"hasVariants": false,
|
|
28
|
+
"hasSizes": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "combobox",
|
|
32
|
+
"code": "import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, Component, computed, input, model, signal } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDown } from '@ng-icons/heroicons/outline';\nimport {\n NgpCombobox,\n NgpComboboxButton,\n NgpComboboxDropdown,\n NgpComboboxInput,\n NgpComboboxOption,\n NgpComboboxPortal,\n} from 'ng-primitives/combobox';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-combobox',\n imports: [\n NgpCombobox,\n NgpComboboxDropdown,\n NgpComboboxOption,\n NgpComboboxInput,\n NgpComboboxPortal,\n NgpComboboxButton,\n NgIcon,\n ],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Combobox)],\n template: `\n <div\n [(ngpComboboxValue)]=\"value\"\n [ngpComboboxDisabled]=\"disabled() || formDisabled()\"\n (ngpComboboxOpenChange)=\"resetOnClose($event)\"\n (ngpComboboxValueChange)=\"onValueChange($event)\"\n ngpCombobox\n >\n <input\n [value]=\"filter()\"\n [placeholder]=\"placeholder()\"\n (input)=\"onFilterChange($event)\"\n (blur)=\"onTouched?.()\"\n ngpComboboxInput\n />\n\n <button ngpComboboxButton aria-label=\"Toggle dropdown\">\n <ng-icon name=\"heroChevronDown\" />\n </button>\n\n <div *ngpComboboxPortal ngpComboboxDropdown>\n @for (option of filteredOptions(); track option) {\n <div [ngpComboboxOptionValue]=\"option\" ngpComboboxOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n </div>\n `,\n styles: `\n [ngpCombobox] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 36px;\n width: 300px;\n border-radius: 8px;\n border: none;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n box-sizing: border-box;\n }\n\n [ngpCombobox][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n [ngpComboboxInput] {\n flex: 1;\n padding: 0 16px;\n border: none;\n background-color: transparent;\n color: var(--ngp-text);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n outline: none;\n height: 100%;\n }\n\n [ngpComboboxButton] {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 36px;\n background-color: transparent;\n border: none;\n color: var(--ngp-text);\n cursor: pointer;\n box-sizing: border-box;\n }\n\n [ngpComboboxDropdown] {\n background-color: var(--ngp-background);\n border: 1px solid var(--ngp-border);\n padding: 0.25rem;\n border-radius: 0.75rem;\n outline: none;\n position: absolute;\n animation: popover-show 0.1s ease-out;\n width: var(--ngp-combobox-width);\n box-shadow: var(--ngp-shadow-lg);\n box-sizing: border-box;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n transform-origin: var(--ngp-combobox-transform-origin);\n }\n\n [ngpComboboxDropdown][data-enter] {\n animation: combobox-show 0.1s ease-out;\n }\n\n [ngpComboboxDropdown][data-exit] {\n animation: combobox-hide 0.1s ease-out;\n }\n\n [ngpComboboxOption] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n font-size: 14px;\n color: var(--ngp-text-primary);\n box-sizing: border-box;\n }\n\n [ngpComboboxOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpComboboxOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpComboboxOption][data-active] {\n background-color: var(--ngp-background-active);\n }\n\n .empty-message {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0.5rem;\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n }\n\n @keyframes combobox-show {\n 0% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes combobox-hide {\n 0% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n }\n `,\n})\nexport class Combobox implements ControlValueAccessor {\n /** The options for the combobox. */\n readonly options = input<string[]>([]);\n\n /** The selected value. */\n readonly value = model<string | undefined>();\n\n /** The placeholder for the input. */\n readonly placeholder = input<string>('');\n\n /** The disabled state of the combobox. */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** The filter value. */\n protected readonly filter = signal<string>('');\n\n /** Get the filtered options. */\n protected readonly filteredOptions = computed(() =>\n this.options().filter(option => option.toLowerCase().includes(this.filter().toLowerCase())),\n );\n\n /** Store the form disabled state */\n protected readonly formDisabled = signal(false);\n\n /** The on change callback */\n private onChange?: ChangeFn<string>;\n\n /** The on touch callback */\n protected onTouched?: TouchedFn;\n\n onFilterChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.filter.set(input.value);\n }\n\n writeValue(value: string | undefined): void {\n this.value.set(value);\n this.filter.set(value ?? '');\n }\n\n registerOnChange(fn: ChangeFn<string | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n protected onValueChange(value: string): void {\n this.onChange?.(value);\n // update the filter value\n this.filter.set(value);\n }\n\n protected resetOnClose(open: boolean): void {\n // if the dropdown is closed, reset the filter value\n if (open) {\n return;\n }\n\n // if the filter value is empty, set the value to undefined\n if (this.filter() === '') {\n this.value.set(undefined);\n } else {\n // otherwise set the filter value to the selected value\n this.filter.set(this.value() ?? '');\n }\n }\n}\n",
|
|
33
|
+
"primitive": "combobox",
|
|
34
|
+
"hasVariants": false,
|
|
35
|
+
"hasSizes": true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "date-picker",
|
|
39
|
+
"code": "import { Component, computed } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronLeftMini, heroChevronRightMini } from '@ng-icons/heroicons/mini';\nimport {\n injectDatePickerState,\n NgpDatePicker,\n NgpDatePickerCell,\n NgpDatePickerCellRender,\n NgpDatePickerDateButton,\n NgpDatePickerGrid,\n NgpDatePickerLabel,\n NgpDatePickerNextMonth,\n NgpDatePickerPreviousMonth,\n NgpDatePickerRowRender,\n} from 'ng-primitives/date-picker';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-date-picker',\n hostDirectives: [\n {\n directive: NgpDatePicker,\n inputs: [\n 'ngpDatePickerDate: date',\n 'ngpDatePickerMin: min',\n 'ngpDatePickerMax: max',\n 'ngpDatePickerDisabled: disabled',\n ],\n outputs: ['ngpDatePickerDateChange: dateChange'],\n },\n ],\n imports: [\n NgIcon,\n NgpDatePickerLabel,\n NgpDatePickerNextMonth,\n NgpDatePickerPreviousMonth,\n NgpDatePickerGrid,\n NgpDatePickerCell,\n NgpDatePickerRowRender,\n NgpDatePickerCellRender,\n NgpDatePickerDateButton,\n ],\n providers: [\n provideIcons({ heroChevronRightMini, heroChevronLeftMini }),\n provideValueAccessor(DatePicker),\n ],\n template: `\n <div class=\"date-picker-header\">\n <button ngpDatePickerPreviousMonth aria-label=\"previous month\">\n <ng-icon name=\"heroChevronLeftMini\" />\n </button>\n <h2 ngpDatePickerLabel>{{ label() }}</h2>\n <button ngpDatePickerNextMonth aria-label=\"next month\">\n <ng-icon name=\"heroChevronRightMini\" />\n </button>\n </div>\n <table ngpDatePickerGrid>\n <thead>\n <tr>\n <th scope=\"col\" abbr=\"Sunday\">S</th>\n <th scope=\"col\" abbr=\"Monday\">M</th>\n <th scope=\"col\" abbr=\"Tuesday\">T</th>\n <th scope=\"col\" abbr=\"Wednesday\">W</th>\n <th scope=\"col\" abbr=\"Thursday\">T</th>\n <th scope=\"col\" abbr=\"Friday\">F</th>\n <th scope=\"col\" abbr=\"Saturday\">S</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngpDatePickerRowRender>\n <td *ngpDatePickerCellRender=\"let date\" ngpDatePickerCell>\n <button ngpDatePickerDateButton>\n {{ date.getDate() }}\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n `,\n styles: `\n :host {\n display: inline-block;\n background-color: var(--ngp-background);\n border-radius: 12px;\n padding: 16px;\n box-shadow: var(--ngp-shadow);\n border: 1px solid var(--ngp-border);\n }\n\n .date-picker-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 36px;\n margin-bottom: 16px;\n }\n\n th {\n font-size: 14px;\n font-weight: 500;\n width: 40px;\n height: 40px;\n text-align: center;\n color: var(--ngp-text-secondary);\n }\n\n [ngpDatePickerLabel] {\n font-size: 14px;\n font-weight: 500;\n color: var(--ngp-text-primary);\n }\n\n [ngpDatePickerPreviousMonth],\n [ngpDatePickerNextMonth] {\n all: unset;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n font-size: 20px;\n border: 1px solid var(--ngp-border);\n cursor: pointer;\n }\n\n [ngpDatePickerPreviousMonth][data-hover],\n [ngpDatePickerNextMonth][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpDatePickerPreviousMonth][data-focus-visible],\n [ngpDatePickerNextMonth][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n [ngpDatePickerPreviousMonth][data-press],\n [ngpDatePickerNextMonth][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpDatePickerPreviousMonth][data-disabled],\n [ngpDatePickerNextMonth][data-disabled] {\n cursor: not-allowed;\n color: var(--ngp-text-disabled);\n }\n\n [ngpDatePickerDateButton] {\n all: unset;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n cursor: pointer;\n }\n\n [ngpDatePickerDateButton][data-today] {\n color: var(--ngp-text-blue);\n }\n\n [ngpDatePickerDateButton][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpDatePickerDateButton][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n [ngpDatePickerDateButton][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpDatePickerDateButton][data-outside-month] {\n color: var(--ngp-text-disabled);\n }\n\n [ngpDatePickerDateButton][data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n\n [ngpDatePickerDateButton][data-selected][data-outside-month] {\n background-color: var(--ngp-background-disabled);\n color: var(--ngp-text-disabled);\n }\n\n [ngpDatePickerDateButton][data-disabled] {\n cursor: not-allowed;\n color: var(--ngp-text-disabled);\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class DatePicker implements ControlValueAccessor {\n /** Access the date picker host directive */\n private readonly state = injectDatePickerState<Date>();\n\n /**\n * Get the current focused date in string format.\n * @returns The focused date in \"February 2024\" format.\n */\n readonly label = computed(\n () =>\n `${this.state().focusedDate().toLocaleString('default', { month: 'long' })} ${this.state().focusedDate().getFullYear()}`,\n );\n\n /**\n * The onChange callback function for the date picker.\n */\n protected onChange?: ChangeFn<Date | undefined>;\n\n /**\n * The onTouched callback function for the date picker.\n */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Whenever the user interacts with the date picker, call the onChange function with the new value.\n this.state().dateChange.subscribe(date => this.onChange?.(date));\n }\n\n writeValue(date: Date): void {\n this.state().select(date);\n }\n\n registerOnChange(fn: ChangeFn<Date | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().disabled.set(isDisabled);\n }\n}\n",
|
|
40
|
+
"primitive": "date-picker",
|
|
41
|
+
"hasVariants": false,
|
|
42
|
+
"hasSizes": true
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "dialog",
|
|
46
|
+
"code": "import { Component, input } from '@angular/core';\nimport {\n NgpDialog,\n NgpDialogDescription,\n NgpDialogOverlay,\n NgpDialogTitle,\n provideDialogState,\n} from 'ng-primitives/dialog';\n\n@Component({\n selector: 'app-dialog',\n hostDirectives: [NgpDialogOverlay],\n imports: [NgpDialog, NgpDialogTitle, NgpDialogDescription],\n providers: [\n // We need to hoist the dialog state to the host component so that it can be used\n // within ng-content\n provideDialogState(),\n ],\n template: `\n <div ngpDialog>\n <h2 ngpDialogTitle>{{ header() }}</h2>\n <p ngpDialogDescription>\n <ng-content />\n </p>\n </div>\n `,\n styles: `\n :host {\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n position: fixed;\n inset: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n animation: fadeIn 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n :host[data-exit] {\n animation: fadeOut 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpDialog] {\n background-color: var(--ngp-background);\n padding: 24px;\n border-radius: 8px;\n box-shadow: var(--ngp-shadow);\n animation: slideIn 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpDialog][data-exit] {\n animation: slideOut 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpDialogTitle] {\n font-size: 18px;\n line-height: 28px;\n font-weight: 600;\n color: var(--ngp-text-primary);\n margin: 0 0 4px;\n }\n\n [ngpDialogDescription] {\n font-size: 14px;\n line-height: 20px;\n color: var(--ngp-text-secondary);\n margin: 0;\n }\n\n @keyframes fadeIn {\n 0% {\n opacity: 0;\n }\n 100% {\n opacity: 1;\n }\n }\n\n @keyframes fadeOut {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n }\n\n @keyframes slideIn {\n 0% {\n transform: translateY(-20px);\n opacity: 0;\n }\n 100% {\n transform: translateY(0);\n opacity: 1;\n }\n }\n @keyframes slideOut {\n 0% {\n transform: translateY(0);\n opacity: 1;\n }\n\n 100% {\n transform: translateY(-20px);\n opacity: 0;\n }\n }\n `,\n})\nexport class Dialog {\n /** The dialog title */\n readonly header = input.required<string>();\n}\n",
|
|
47
|
+
"primitive": "dialog",
|
|
48
|
+
"hasVariants": false,
|
|
49
|
+
"hasSizes": true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "file-upload",
|
|
53
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpFileUpload } from 'ng-primitives/file-upload';\n\n@Component({\n selector: 'app-file-upload',\n hostDirectives: [\n {\n directive: NgpFileUpload,\n inputs: [\n 'ngpFileUploadFileTypes:types',\n 'ngpFileUploadMultiple:multiple',\n 'ngpFileUploadDirectory:directory',\n 'ngpFileUploadDragDrop:dragDrop',\n 'ngpFileUploadDisabled:disabled',\n ],\n outputs: ['ngpFileUploadSelected:selected', 'ngpFileUploadCanceled:canceled'],\n },\n ],\n template: `\n Drop files here or click to upload\n `,\n styles: `\n :host {\n display: flex;\n cursor: pointer;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n row-gap: 0.25rem;\n border-radius: 0.5rem;\n border: 1px dashed var(--ngp-border-secondary);\n background-color: var(--ngp-background);\n padding: 2rem 3rem;\n }\n\n :host[data-dragover] {\n border-color: var(--ngp-border);\n background-color: var(--ngp-background-hover);\n }\n `,\n})\nexport class FileUpload {}\n",
|
|
54
|
+
"primitive": "file-upload",
|
|
55
|
+
"hasVariants": false,
|
|
56
|
+
"hasSizes": false
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "form-field",
|
|
60
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpFormField } from 'ng-primitives/form-field';\n\n@Component({\n selector: 'app-form-field',\n hostDirectives: [NgpFormField],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: 90%;\n }\n `,\n})\nexport class FormField {}\n",
|
|
61
|
+
"primitive": "form-field",
|
|
62
|
+
"hasVariants": false,
|
|
63
|
+
"hasSizes": false
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "input",
|
|
67
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpInput } from 'ng-primitives/input';\n\n@Component({\n selector: 'input[app-input]',\n hostDirectives: [{ directive: NgpInput, inputs: ['disabled'] }],\n template: '',\n styles: `\n :host {\n height: 36px;\n width: 100%;\n border-radius: 8px;\n padding: 0 16px;\n border: none;\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n box-sizing: border-box;\n }\n\n :host:focus {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n :host::placeholder {\n color: var(--ngp-text-placeholder);\n }\n `,\n})\nexport class Input {}\n",
|
|
68
|
+
"primitive": "input",
|
|
69
|
+
"hasVariants": false,
|
|
70
|
+
"hasSizes": false
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "input-otp",
|
|
74
|
+
"code": "import { BooleanInput, NumberInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n Component,\n computed,\n forwardRef,\n input,\n model,\n numberAttribute,\n signal,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { NgpInputOtp, NgpInputOtpInput, NgpInputOtpSlot } from 'ng-primitives/input-otp';\nimport { ChangeFn, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-input-otp',\n imports: [NgpInputOtp, NgpInputOtpInput, NgpInputOtpSlot],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => InputOtp),\n multi: true,\n },\n ],\n template: `\n <div\n [ngpInputOtpValue]=\"value()\"\n [ngpInputOtpDisabled]=\"disabled() || formDisabled()\"\n [ngpInputOtpPattern]=\"pattern()\"\n [ngpInputOtpPlaceholder]=\"placeholder()\"\n [ngpInputOtpInputMode]=\"inputMode()\"\n (ngpInputOtpValueChange)=\"onValueChange($event)\"\n (ngpInputOtpComplete)=\"onComplete()\"\n ngpInputOtp\n >\n <input ngpInputOtpInput />\n <div class=\"slots\">\n @for (_ of slots(); track $index) {\n <div class=\"slot\" ngpInputOtpSlot></div>\n }\n </div>\n </div>\n `,\n styles: `\n :host {\n display: inline-flex;\n flex-direction: column;\n gap: 12px;\n max-width: 100%;\n }\n\n .slots {\n display: flex;\n gap: 8px;\n }\n\n .slot {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border: 2px solid var(--ngp-border);\n border-radius: 8px;\n background: var(--ngp-background);\n font-size: 18px;\n font-weight: 600;\n color: var(--ngp-text-primary);\n transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);\n cursor: pointer;\n position: relative;\n }\n\n .slot[data-filled] {\n border-color: var(--ngp-border-strong);\n background: var(--ngp-background-accent);\n }\n\n .slot[data-active] {\n border-color: var(--ngp-focus-ring);\n box-shadow: 0 0 0 1px var(--ngp-focus-ring);\n }\n\n .slot[data-placeholder] {\n color: var(--ngp-text-placeholder);\n }\n\n .slot[data-caret]::after {\n content: '';\n position: absolute;\n width: 2px;\n height: 20px;\n background: var(--ngp-focus-ring);\n animation: blink 1s infinite;\n }\n\n @keyframes blink {\n 0%,\n 50% {\n opacity: 1;\n }\n 51%,\n 100% {\n opacity: 0;\n }\n }\n\n :host([data-disabled]) .slot {\n background: var(--ngp-background-disabled);\n color: var(--ngp-text-disabled);\n border-color: var(--ngp-border-disabled);\n cursor: not-allowed;\n }\n `,\n})\nexport class InputOtp implements ControlValueAccessor {\n /**\n * The number of slots to display.\n */\n readonly length = input<number, NumberInput>(6, {\n transform: numberAttribute,\n });\n\n /**\n * Whether the input is disabled.\n */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /**\n * The pattern for allowed characters.\n */\n readonly pattern = input('[0-9]');\n\n /**\n * The placeholder character for empty slots.\n */\n readonly placeholder = input('');\n\n /**\n * The input mode for the hidden input.\n */\n readonly inputMode = input<'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'>(\n 'numeric',\n );\n\n /**\n * Create an array for tracking slots.\n */\n protected readonly slots = computed(() => Array.from({ length: this.length() }, (_, i) => i));\n\n /**\n * The current value.\n */\n readonly value = model<string>('');\n\n private onChange: ChangeFn<string> = () => {};\n private onTouched: TouchedFn = () => {};\n\n protected readonly formDisabled = signal(false);\n\n /**\n * Handle value changes from the input-otp directive.\n */\n onValueChange(value: string): void {\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle completion events from the input-otp directive.\n */\n onComplete(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string): void {\n this.value.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n}\n",
|
|
75
|
+
"primitive": "input-otp",
|
|
76
|
+
"hasVariants": false,
|
|
77
|
+
"hasSizes": true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "listbox",
|
|
81
|
+
"code": "import { Component } from '@angular/core';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroCheckSolid } from '@ng-icons/heroicons/solid';\nimport { NgpListboxOption } from 'ng-primitives/listbox';\n\n@Component({\n selector: 'app-listbox-option',\n hostDirectives: [\n {\n directive: NgpListboxOption,\n inputs: ['id', 'ngpListboxOptionValue:value', 'ngpListboxOptionDisabled:disabled'],\n },\n ],\n imports: [NgIcon],\n providers: [provideIcons({ heroCheckSolid })],\n template: `\n <ng-icon name=\"heroCheckSolid\" size=\"16px\" />\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n box-sizing: border-box;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-active] {\n background-color: var(--ngp-background-active);\n }\n\n :host ng-icon {\n visibility: hidden;\n }\n\n :host[data-selected] ng-icon {\n visibility: visible;\n }\n `,\n})\nexport class ListboxOption {}\n",
|
|
82
|
+
"primitive": "listbox",
|
|
83
|
+
"hasVariants": false,
|
|
84
|
+
"hasSizes": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "menu",
|
|
88
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpMenuItem } from 'ng-primitives/menu';\n\n@Component({\n selector: 'button[app-menu-item]',\n hostDirectives: [NgpMenuItem],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 8px 6px 14px;\n border: none;\n background: none;\n cursor: pointer;\n transition: background-color 0.2s;\n border-radius: 4px;\n min-width: 120px;\n text-align: start;\n outline: none;\n font-size: 14px;\n font-weight: 400;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n z-index: 1;\n }\n `,\n})\nexport class MenuItem {}\n",
|
|
89
|
+
"primitive": "menu",
|
|
90
|
+
"hasVariants": false,
|
|
91
|
+
"hasSizes": true
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "meter",
|
|
95
|
+
"code": "import { NumberInput } from '@angular/cdk/coercion';\nimport { Component, input, numberAttribute } from '@angular/core';\nimport {\n NgpMeter,\n NgpMeterIndicator,\n NgpMeterLabel,\n NgpMeterTrack,\n NgpMeterValue,\n} from 'ng-primitives/meter';\n\n@Component({\n selector: 'app-meter',\n hostDirectives: [\n { directive: NgpMeter, inputs: ['ngpMeterValue:value', 'ngpMeterMin:min', 'ngpMeterMax:max'] },\n ],\n imports: [NgpMeterIndicator, NgpMeterLabel, NgpMeterValue, NgpMeterTrack],\n template: `\n <span ngpMeterLabel>{{ label() }}</span>\n <span ngpMeterValue>{{ value() }}%</span>\n\n <div ngpMeterTrack>\n <div ngpMeterIndicator></div>\n </div>\n `,\n styles: `\n :host {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-row-gap: 0.5rem;\n width: 200px;\n box-sizing: border-box;\n padding: 0.5rem;\n }\n\n [ngpMeterLabel] {\n color: var(--ngp-text-emphasis);\n font-size: 14px;\n font-weight: 600;\n }\n\n [ngpMeterValue] {\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: right;\n grid-column-start: 2;\n text-align: end;\n }\n\n [ngpMeterTrack] {\n grid-column: 1 / 3;\n overflow: hidden;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-shadow-border);\n border-radius: 4px;\n height: 8px;\n }\n\n [ngpMeterIndicator] {\n background-color: var(--ngp-background-success);\n height: 100%;\n transition: width 0.2s ease-in-out;\n inset-inline-start: 0px;\n border-radius: 4px;\n }\n `,\n})\nexport class Meter {\n /** The value of the meter. */\n readonly value = input<number, NumberInput>(0, {\n transform: numberAttribute,\n });\n\n /** The label of the meter. */\n readonly label = input.required<string>();\n}\n",
|
|
96
|
+
"primitive": "meter",
|
|
97
|
+
"hasVariants": false,
|
|
98
|
+
"hasSizes": true
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "native-select",
|
|
102
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpNativeSelect } from 'ng-primitives/select';\n\n@Component({\n selector: 'select[app-select]',\n hostDirectives: [{ directive: NgpNativeSelect, inputs: ['ngpNativeSelectDisabled:disabled'] }],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n all: unset;\n appearance: none;\n display: flex;\n width: 90%;\n align-items: center;\n height: 2.5rem;\n padding: 0 1rem;\n border-radius: 0.5rem;\n background-color: var(--ngp-background);\n text-align: start;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n background-position-x: calc(100% - 10px);\n background-position-y: 50%;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0iIzczNzM3MyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNS4yMiA4LjIyYS43NS43NSAwIDAgMSAxLjA2IDBMMTAgMTEuOTRsMy43Mi0zLjcyYS43NS43NSAwIDEgMSAxLjA2IDEuMDZsLTQuMjUgNC4yNWEuNzUuNzUgMCAwIDEtMS4wNiAwTDUuMjIgOS4yOGEuNzUuNzUgMCAwIDEgMC0xLjA2WiIgY2xpcC1ydWxlPSJldmVub2RkIj48L3BhdGg+PC9zdmc+');\n background-size: 1.25rem;\n background-repeat: no-repeat;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n `,\n})\nexport class NativeSelect {}\n",
|
|
103
|
+
"primitive": "native-select",
|
|
104
|
+
"hasVariants": false,
|
|
105
|
+
"hasSizes": true
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "pagination",
|
|
109
|
+
"code": "import { Component, computed } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport {\n heroChevronDoubleLeft,\n heroChevronDoubleRight,\n heroChevronLeft,\n heroChevronRight,\n} from '@ng-icons/heroicons/outline';\nimport {\n injectPaginationState,\n NgpPagination,\n NgpPaginationButton,\n NgpPaginationFirst,\n NgpPaginationLast,\n NgpPaginationNext,\n NgpPaginationPrevious,\n} from 'ng-primitives/pagination';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-pagination',\n hostDirectives: [\n {\n directive: NgpPagination,\n inputs: [\n 'ngpPaginationPage:page',\n 'ngpPaginationPageCount:pageCount',\n 'ngpPaginationDisabled:disabled',\n ],\n outputs: ['ngpPaginationPageChange:pageChange'],\n },\n ],\n imports: [\n NgpPaginationButton,\n NgpPaginationFirst,\n NgpPaginationLast,\n NgpPaginationNext,\n NgpPaginationPrevious,\n NgIcon,\n ],\n providers: [\n provideValueAccessor(NgpPagination),\n provideIcons({\n heroChevronDoubleLeft,\n heroChevronDoubleRight,\n heroChevronLeft,\n heroChevronRight,\n }),\n ],\n template: `\n <ul>\n <li>\n <button ngpPaginationFirst aria-label=\"First Page\">\n <ng-icon name=\"heroChevronDoubleLeft\" />\n </button>\n </li>\n\n <li>\n <button ngpPaginationPrevious aria-label=\"Previous Page\">\n <ng-icon name=\"heroChevronLeft\" />\n </button>\n </li>\n\n @for (page of pages(); track page) {\n <li>\n <button\n [ngpPaginationButtonPage]=\"page\"\n [attr.aria-label]=\"'Page ' + page\"\n ngpPaginationButton\n >\n {{ page }}\n </button>\n </li>\n }\n\n <li>\n <button ngpPaginationNext aria-label=\"Next Page\">\n <ng-icon name=\"heroChevronRight\" />\n </button>\n </li>\n\n <li>\n <button ngpPaginationLast aria-label=\"Last Page\">\n <ng-icon name=\"heroChevronDoubleRight\" />\n </button>\n </li>\n </ul>\n `,\n styles: `\n ul {\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n list-style: none;\n }\n\n [ngpPaginationFirst],\n [ngpPaginationPrevious],\n [ngpPaginationButton],\n [ngpPaginationNext],\n [ngpPaginationLast] {\n all: unset;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 2rem;\n height: 2rem;\n border-radius: 0.5rem;\n color: var(--ngp-text-primary);\n outline: none;\n font-size: 14px;\n font-weight: 500;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-button-shadow);\n cursor: pointer;\n transition: background-color 0.2s;\n\n &[data-hover]:not([data-disabled]):not([data-selected]) {\n background-color: var(--ngp-background-hover);\n }\n\n &[data-focus-visible]:not([data-disabled]) {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n &[data-press]:not([data-disabled]):not([data-selected]) {\n background-color: var(--ngp-background-active);\n }\n\n &[data-disabled] {\n color: rgb(210 210 210);\n background-color: var(--ngp-background-disabled);\n cursor: not-allowed;\n box-shadow: none;\n }\n\n &[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Pagination implements ControlValueAccessor {\n /** Access the pagination state */\n protected readonly state = injectPaginationState();\n\n /** Get the pages as an array we can iterate over */\n protected readonly pages = computed(() =>\n Array.from({ length: this.state().pageCount() }).map((_, i) => i + 1),\n );\n\n /** The onChange callback */\n private onChange?: ChangeFn<number>;\n\n /** The onTouched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n this.state().pageChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the control */\n writeValue(value: number): void {\n this.state().page.set(value);\n }\n\n /** Register a callback to be called when the value changes */\n registerOnChange(fn: ChangeFn<number>): void {\n this.onChange = fn;\n }\n\n /** Register a callback to be called when the control is touched */\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n}\n",
|
|
110
|
+
"primitive": "pagination",
|
|
111
|
+
"hasVariants": false,
|
|
112
|
+
"hasSizes": true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "popover",
|
|
116
|
+
"code": "import { Directive, input } from '@angular/core';\nimport { injectPopoverTriggerState } from 'ng-primitives/popover';\nimport { NgpPopoverTrigger } from 'ng-primitives/popover';\nimport { Popover } from './popover';\n\n@Directive({\n selector: '[appPopoverTrigger]',\n hostDirectives: [\n {\n directive: NgpPopoverTrigger,\n inputs: [\n 'ngpPopoverTriggerDisabled:appPopoverTriggerDisabled',\n 'ngpPopoverTriggerPlacement:appPopoverTriggerPlacement',\n 'ngpPopoverTriggerOffset:appPopoverTriggerOffset',\n 'ngpPopoverTriggerShowDelay:appPopoverTriggerShowDelay',\n 'ngpPopoverTriggerHideDelay:appPopoverTriggerHideDelay',\n 'ngpPopoverTriggerFlip:appPopoverTriggerFlip',\n 'ngpPopoverTriggerContainer:appPopoverTriggerContainer',\n 'ngpPopoverTriggerCloseOnOutsideClick:appPopoverTriggerCloseOnOutsideClick',\n 'ngpPopoverTriggerCloseOnEscape:appPopoverTriggerCloseOnEscape',\n 'ngpPopoverTriggerScrollBehavior:appPopoverTriggerScrollBehavior',\n 'ngpPopoverTriggerContext:appPopoverTrigger',\n ],\n },\n ],\n})\nexport class PopoverTrigger {\n /** Access the popover trigger */\n private readonly popoverTrigger = injectPopoverTriggerState();\n\n /** Define the content of the popover */\n readonly content = input.required<string>({\n alias: 'appPopoverTrigger',\n });\n\n constructor() {\n this.popoverTrigger().popover.set(Popover);\n }\n}\n",
|
|
117
|
+
"primitive": "popover",
|
|
118
|
+
"hasVariants": false,
|
|
119
|
+
"hasSizes": false
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "progress",
|
|
123
|
+
"code": "import { NumberInput } from '@angular/cdk/coercion';\nimport { Component, input, numberAttribute } from '@angular/core';\nimport {\n NgpProgress,\n NgpProgressIndicator,\n NgpProgressLabel,\n NgpProgressTrack,\n NgpProgressValue,\n} from 'ng-primitives/progress';\n\n@Component({\n selector: 'app-progress',\n hostDirectives: [\n {\n directive: NgpProgress,\n inputs: ['ngpProgressValue:value', 'ngpProgressMax:max', 'ngpProgressValueLabel:valueLabel'],\n },\n ],\n imports: [NgpProgressIndicator, NgpProgressTrack, NgpProgressLabel, NgpProgressValue],\n template: `\n <label ngpProgressLabel>{{ label() }}</label>\n <span ngpProgressValue>{{ value() }}%</span>\n\n <div ngpProgressTrack>\n <div ngpProgressIndicator></div>\n </div>\n `,\n styles: `\n :host {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-row-gap: 0.5rem;\n width: 200px;\n box-sizing: border-box;\n padding: 0.5rem;\n }\n\n [ngpProgressLabel] {\n color: var(--ngp-text-emphasis);\n font-size: 14px;\n font-weight: 600;\n }\n\n [ngpProgressValue] {\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: right;\n grid-column-start: 2;\n text-align: end;\n }\n\n [ngpProgressTrack] {\n grid-column: 1 / 3;\n position: relative;\n height: 12px;\n width: 100%;\n max-width: 320px;\n overflow: hidden;\n border-radius: 0.5rem;\n border: 1px solid var(--ngp-border);\n background-color: var(--ngp-background);\n }\n\n [ngpProgressIndicator] {\n height: 100%;\n border-radius: 0.5rem;\n background-color: var(--ngp-background-inverse);\n transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n `,\n})\nexport class Progress {\n /** The value of the progress. */\n readonly value = input<number, NumberInput>(0, {\n transform: numberAttribute,\n });\n\n /** The label of the progress. */\n readonly label = input.required<string>();\n}\n",
|
|
124
|
+
"primitive": "progress",
|
|
125
|
+
"hasVariants": false,
|
|
126
|
+
"hasSizes": true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "radio",
|
|
130
|
+
"code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { injectRadioGroupState, NgpRadioGroup } from 'ng-primitives/radio';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-radio-group',\n hostDirectives: [\n {\n directive: NgpRadioGroup,\n inputs: [\n 'ngpRadioGroupValue:value',\n 'ngpRadioGroupDisabled:disabled',\n 'ngpRadioGroupOrientation:orientation',\n ],\n outputs: ['ngpRadioGroupValueChange:valueChange'],\n },\n ],\n providers: [provideValueAccessor(RadioGroup)],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n flex-direction: column;\n row-gap: 1rem;\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class RadioGroup implements ControlValueAccessor {\n /** Access the radio group state */\n protected readonly state = injectRadioGroupState<string>();\n\n /** The on change callback */\n private onChange?: ChangeFn<string | null>;\n\n /** The on touched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n this.state().valueChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the radio group */\n writeValue(value: string): void {\n this.state().value.set(value);\n }\n\n /** Register the on change callback */\n registerOnChange(onChange: ChangeFn<string | null>): void {\n this.onChange = onChange;\n }\n\n /** Register the on touched callback */\n registerOnTouched(onTouched: TouchedFn): void {\n this.onTouched = onTouched;\n }\n}\n",
|
|
131
|
+
"primitive": "radio",
|
|
132
|
+
"hasVariants": false,
|
|
133
|
+
"hasSizes": false
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "range-slider",
|
|
137
|
+
"code": "import { Component, input } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { explicitEffect } from 'ng-primitives/internal';\nimport {\n injectRangeSliderState,\n NgpRangeSlider,\n NgpRangeSliderRange,\n NgpRangeSliderThumb,\n NgpRangeSliderTrack,\n provideRangeSliderState,\n} from 'ng-primitives/slider';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-range-slider',\n hostDirectives: [\n {\n directive: NgpRangeSlider,\n inputs: [\n 'ngpRangeSliderLow:low',\n 'ngpRangeSliderHigh:high',\n 'ngpRangeSliderMin:min',\n 'ngpRangeSliderMax:max',\n 'ngpRangeSliderStep:step',\n 'ngpRangeSliderDisabled:disabled',\n 'ngpRangeSliderOrientation:orientation',\n ],\n outputs: ['ngpRangeSliderLowChange:lowChange', 'ngpRangeSliderHighChange:highChange'],\n },\n ],\n imports: [NgpRangeSliderTrack, NgpRangeSliderRange, NgpRangeSliderThumb],\n providers: [provideRangeSliderState(), provideValueAccessor(RangeSlider)],\n template: `\n <div ngpRangeSliderTrack>\n <div ngpRangeSliderRange></div>\n </div>\n <div ngpRangeSliderThumb></div>\n <div ngpRangeSliderThumb></div>\n `,\n styles: `\n :host {\n display: flex;\n position: relative;\n width: 200px;\n height: 20px;\n touch-action: none;\n user-select: none;\n align-items: center;\n }\n\n [ngpRangeSliderTrack] {\n position: relative;\n height: 5px;\n width: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-secondary);\n }\n\n [ngpRangeSliderRange] {\n position: absolute;\n height: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-inverse);\n }\n\n [ngpRangeSliderThumb] {\n position: absolute;\n display: block;\n height: 20px;\n width: 20px;\n border-radius: 10px;\n background-color: white;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n transform: translateX(-50%);\n }\n\n [ngpRangeSliderThumb][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n\n [ngpRangeSliderThumb][data-thumb='high'] {\n z-index: 2;\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class RangeSlider implements ControlValueAccessor {\n /** Access the range slider state */\n private readonly state = injectRangeSliderState();\n\n /** Forward aria-labels to each thumb */\n readonly ariaLabelLow = input<string | null>(null);\n readonly ariaLabelHigh = input<string | null>(null);\n\n /** The onChange callback function. */\n private onChange?: ChangeFn<[number, number]>;\n\n /** The onTouched callback function. */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Whenever either value changes, call the onChange function with the new tuple [low, high].\n explicitEffect([this.state().low, this.state().high], ([low, high]) =>\n this.onChange?.([low, high]),\n );\n }\n\n writeValue(value: [number, number]): void {\n if (!value || value.length !== 2) {\n return;\n }\n\n const [low, high] = value;\n // Use the directive's clamping setters to respect min/max and ordering\n this.state().setLowValue(low);\n this.state().setHighValue(high);\n }\n\n registerOnChange(fn: ChangeFn<[number, number]>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().disabled.set(isDisabled);\n }\n}\n",
|
|
138
|
+
"primitive": "range-slider",
|
|
139
|
+
"hasVariants": false,
|
|
140
|
+
"hasSizes": false
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "search",
|
|
144
|
+
"code": "import { Component, input, model } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroMagnifyingGlass } from '@ng-icons/heroicons/outline';\nimport { NgpButton } from 'ng-primitives/button';\nimport { NgpInput } from 'ng-primitives/input';\nimport { NgpSearch, NgpSearchClear } from 'ng-primitives/search';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-search',\n hostDirectives: [NgpSearch],\n imports: [NgIcon, NgpSearchClear, NgpInput, NgpButton],\n providers: [provideValueAccessor(Search), provideIcons({ heroMagnifyingGlass })],\n template: `\n <ng-icon name=\"heroMagnifyingGlass\" />\n <input\n [value]=\"query()\"\n [placeholder]=\"placeholder()\"\n (input)=\"onQueryChange($event)\"\n ngpInput\n type=\"search\"\n />\n <button ngpSearchClear ngpButton aria-label=\"Clear search\">Clear</button>\n `,\n styles: `\n :host {\n display: block;\n position: relative;\n }\n\n [ngpInput] {\n height: 36px;\n width: 100%;\n border-radius: 8px;\n padding: 0 16px 0 40px;\n border: none;\n background: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n }\n\n /* clears the 'X' from Chrome */\n [ngpInput]::-webkit-search-decoration,\n [ngpInput]::-webkit-search-cancel-button,\n [ngpInput]::-webkit-search-results-button,\n [ngpInput]::-webkit-search-results-decoration {\n display: none;\n }\n\n [ngpInput][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0px;\n }\n\n [ngpInput]::placeholder {\n color: var(--ngp-text-placeholder);\n }\n\n [ngpButton] {\n position: absolute;\n top: 0;\n right: 0;\n height: 36px;\n padding: 0 16px;\n border: none;\n border-radius: 0 8px 8px 0;\n background-color: transparent;\n color: var(--ngp-text-blue);\n font-size: 0.875rem;\n line-height: 1.25rem;\n cursor: pointer;\n outline: none;\n display: none;\n }\n\n [ngpButton]:not([data-empty]) {\n display: block;\n }\n\n ng-icon {\n position: absolute;\n font-size: 1.25rem;\n top: 18px;\n left: 12px;\n transform: translateY(-50%);\n color: var(--ngp-text-tertiary);\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Search implements ControlValueAccessor {\n /** The search query */\n readonly query = model<string>('');\n\n /** The placeholder text */\n readonly placeholder = input<string>('');\n\n /** The function to call when the value changes */\n private onChange?: ChangeFn<string>;\n\n /** The function to call when the control is touched */\n protected onTouched?: TouchedFn;\n\n writeValue(value: string): void {\n this.query.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n protected onQueryChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.query.set(input.value);\n this.onChange?.(input.value);\n }\n}\n",
|
|
145
|
+
"primitive": "search",
|
|
146
|
+
"hasVariants": false,
|
|
147
|
+
"hasSizes": true
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "select",
|
|
151
|
+
"code": "import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, Component, input, model, signal } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDown } from '@ng-icons/heroicons/outline';\nimport {\n NgpSelect,\n NgpSelectDropdown,\n NgpSelectOption,\n NgpSelectPortal,\n} from 'ng-primitives/select';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-select',\n imports: [NgpSelect, NgpSelectDropdown, NgpSelectOption, NgpSelectPortal, NgIcon],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Select)],\n template: `\n <div\n [(ngpSelectValue)]=\"value\"\n [ngpSelectDisabled]=\"disabled() || formDisabled()\"\n (ngpSelectValueChange)=\"onValueChange($event)\"\n ngpSelect\n >\n @if (value(); as value) {\n <span class=\"select-value\">{{ value }}</span>\n } @else {\n <span class=\"select-placeholder\">{{ placeholder() }}</span>\n }\n\n <ng-icon name=\"heroChevronDown\" />\n\n <div *ngpSelectPortal ngpSelectDropdown>\n @for (option of options(); track option) {\n <div [ngpSelectOptionValue]=\"option\" ngpSelectOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n </div>\n `,\n styles: `\n [ngpSelect] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 36px;\n width: 300px;\n border-radius: 8px;\n border: none;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n box-sizing: border-box;\n }\n\n [ngpSelect][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n .select-value,\n .select-placeholder {\n display: flex;\n align-items: center;\n flex: 1;\n padding: 0 16px;\n background-color: transparent;\n color: var(--ngp-text-primary);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n height: 100%;\n }\n\n .select-placeholder {\n color: var(--ngp-text-secondary);\n }\n\n ng-icon {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n margin-inline: 8px;\n font-size: 14px;\n }\n\n [ngpSelectDropdown] {\n background-color: var(--ngp-background);\n border: 1px solid var(--ngp-border);\n padding: 0.25rem;\n border-radius: 0.75rem;\n outline: none;\n position: absolute;\n animation: popover-show 0.1s ease-out;\n width: var(--ngp-select-width);\n box-shadow: var(--ngp-shadow-lg);\n box-sizing: border-box;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n transform-origin: var(--ngp-select-transform-origin);\n }\n\n [ngpSelectDropdown][data-enter] {\n animation: select-show 0.1s ease-out;\n }\n\n [ngpSelectDropdown][data-exit] {\n animation: select-hide 0.1s ease-out;\n }\n\n [ngpSelectOption] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n font-size: 14px;\n color: var(--ngp-text-primary);\n box-sizing: border-box;\n }\n\n [ngpSelectOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpSelectOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpSelectOption][data-active] {\n background-color: var(--ngp-background-active);\n }\n\n .empty-message {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0.5rem;\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n }\n\n @keyframes select-show {\n 0% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes select-hide {\n 0% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n }\n `,\n})\nexport class Select implements ControlValueAccessor {\n /** The options for the select. */\n readonly options = input<string[]>([]);\n\n /** The selected value. */\n readonly value = model<string | undefined>();\n\n /** The placeholder for the input. */\n readonly placeholder = input<string>('');\n\n /** The disabled state of the select. */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Store the form disabled state */\n protected readonly formDisabled = signal(false);\n\n /** The on change callback */\n private onChange?: ChangeFn<string>;\n\n /** The on touch callback */\n protected onTouched?: TouchedFn;\n\n writeValue(value: string | undefined): void {\n this.value.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n protected onValueChange(value: string): void {\n this.onChange?.(value);\n }\n}\n",
|
|
152
|
+
"primitive": "select",
|
|
153
|
+
"hasVariants": false,
|
|
154
|
+
"hasSizes": true
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "separator",
|
|
158
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpSeparator } from 'ng-primitives/separator';\n\n@Component({\n selector: '[app-separator]',\n hostDirectives: [{ directive: NgpSeparator, inputs: ['ngpSeparatorOrientation:orientation'] }],\n template: ``,\n styles: `\n :host {\n background-color: var(--ngp-border);\n height: 1px;\n }\n `,\n})\nexport class Separator {}\n",
|
|
159
|
+
"primitive": "separator",
|
|
160
|
+
"hasVariants": false,
|
|
161
|
+
"hasSizes": false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "slider",
|
|
165
|
+
"code": "import { Component, input } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor } from '@angular/forms';\nimport {\n injectSliderState,\n NgpSlider,\n NgpSliderRange,\n NgpSliderThumb,\n NgpSliderTrack,\n} from 'ng-primitives/slider';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-slider',\n hostDirectives: [\n {\n directive: NgpSlider,\n inputs: [\n 'ngpSliderValue:value',\n 'ngpSliderMin:min',\n 'ngpSliderMax:max',\n 'ngpSliderDisabled:disabled',\n ],\n outputs: ['ngpSliderValueChange:valueChange'],\n },\n ],\n imports: [NgpSliderTrack, NgpSliderRange, NgpSliderThumb],\n providers: [provideValueAccessor(Slider)],\n template: `\n <div ngpSliderTrack>\n <div ngpSliderRange></div>\n </div>\n <div [ariaLabel]=\"ariaLabel()\" ngpSliderThumb></div>\n `,\n styles: `\n :host {\n display: flex;\n position: relative;\n width: 200px;\n height: 20px;\n touch-action: none;\n user-select: none;\n align-items: center;\n }\n\n [ngpSliderTrack] {\n position: relative;\n height: 5px;\n width: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-secondary);\n }\n\n [ngpSliderRange] {\n position: absolute;\n height: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-inverse);\n }\n\n [ngpSliderThumb] {\n position: absolute;\n display: block;\n height: 20px;\n width: 20px;\n border-radius: 10px;\n background-color: white;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n transform: translateX(-50%);\n }\n\n [ngpSliderThumb][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Slider implements ControlValueAccessor {\n /** Access the slider state */\n private readonly state = injectSliderState();\n\n /** Forward the aria-label to the thumb */\n readonly ariaLabel = input<string | null>(null, {\n alias: 'aria-label',\n });\n\n /**\n * The onChange callback function.\n */\n private onChange?: ChangeFn<number>;\n\n /**\n * The onTouched callback function.\n */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Whenever the user interacts with the slider, call the onChange function with the new value.\n this.state()\n .valueChange.pipe(takeUntilDestroyed())\n .subscribe(value => this.onChange?.(value));\n }\n\n writeValue(value: number): void {\n this.state().setValue(value);\n }\n\n registerOnChange(fn: ChangeFn<number>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
|
|
166
|
+
"primitive": "slider",
|
|
167
|
+
"hasVariants": false,
|
|
168
|
+
"hasSizes": false
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "switch",
|
|
172
|
+
"code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { injectSwitchState, NgpSwitch, NgpSwitchThumb } from 'ng-primitives/switch';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-switch',\n hostDirectives: [\n {\n directive: NgpSwitch,\n inputs: ['ngpSwitchChecked:checked', 'ngpSwitchDisabled:disabled'],\n outputs: ['ngpSwitchCheckedChange:checkedChange'],\n },\n ],\n imports: [NgpSwitchThumb],\n template: `\n <span ngpSwitchThumb></span>\n `,\n styles: `\n :host {\n display: inline-flex;\n align-items: center;\n position: relative;\n width: 2.5rem;\n height: 1.5rem;\n border-radius: 999px;\n background-color: var(--ngp-background-secondary);\n border: 1px solid var(--ngp-border);\n padding: 0;\n outline: none;\n transition-property:\n color, background-color, border-color, text-decoration-color, fill, stroke;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n box-sizing: border-box;\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-checked] {\n background-color: var(--ngp-background-blue);\n border-color: var(--ngp-border-blue);\n }\n\n [ngpSwitchThumb] {\n display: block;\n height: 1.25rem;\n width: 1.25rem;\n border-radius: 999px;\n background-color: white;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);\n transform: translateX(1px);\n box-sizing: border-box;\n }\n\n [ngpSwitchThumb][data-checked] {\n transform: translateX(17px);\n }\n `,\n providers: [provideValueAccessor(Switch)],\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Switch implements ControlValueAccessor {\n /** Access the switch state. */\n private readonly switch = injectSwitchState();\n\n /** The on change callback */\n private onChange?: ChangeFn<boolean>;\n\n /** The on touched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Any time the switch changes, update the form value.\n this.switch().checkedChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the switch. */\n writeValue(value: boolean): void {\n this.switch().setChecked(value);\n }\n\n /** Register a callback function to be called when the value changes. */\n registerOnChange(fn: ChangeFn<boolean>): void {\n this.onChange = fn;\n }\n\n /** Register a callback function to be called when the switch is touched. */\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n /** Set the disabled state of the switch. */\n setDisabledState(isDisabled: boolean): void {\n this.switch().setDisabled(isDisabled);\n }\n}\n",
|
|
173
|
+
"primitive": "switch",
|
|
174
|
+
"hasVariants": false,
|
|
175
|
+
"hasSizes": false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "tabs",
|
|
179
|
+
"code": "import { Component, input, TemplateRef, viewChild } from '@angular/core';\n\n@Component({\n selector: 'app-tab',\n template: `\n <ng-template #content>\n <ng-content />\n </ng-template>\n `,\n})\nexport class Tab {\n /**\n * The value of the tab.\n */\n readonly value = input<string>();\n\n /**\n * The label of the tab.\n */\n readonly label = input<string>();\n\n /**\n * The content of the tab.\n */\n readonly content = viewChild.required<TemplateRef<void>>('content');\n}\n",
|
|
180
|
+
"primitive": "tabs",
|
|
181
|
+
"hasVariants": false,
|
|
182
|
+
"hasSizes": false
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "textarea",
|
|
186
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpTextarea } from 'ng-primitives/textarea';\n\n@Component({\n selector: 'textarea[app-textarea]',\n hostDirectives: [{ directive: NgpTextarea, inputs: ['disabled'] }],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n height: 72px;\n width: 90%;\n border-radius: 8px;\n padding: 8px 12px;\n border: none;\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n font-family: inherit;\n }\n\n :host[data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n\n :host::placeholder {\n color: var(--ngp-text-placeholder);\n }\n `,\n})\nexport class Textarea {}\n",
|
|
187
|
+
"primitive": "textarea",
|
|
188
|
+
"hasVariants": false,
|
|
189
|
+
"hasSizes": false
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "toast",
|
|
193
|
+
"code": "import { Component, inject } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\nimport { injectToastContext, NgpToast, NgpToastManager } from 'ng-primitives/toast';\n\n@Component({\n selector: 'app-toast',\n imports: [NgpButton],\n hostDirectives: [NgpToast],\n template: `\n <p class=\"toast-title\">{{ context.header }}</p>\n <p class=\"toast-description\">{{ context.description }}</p>\n <button class=\"toast-dismiss\" (click)=\"dismiss()\" ngpButton>Dismiss</button>\n `,\n styles: `\n :host {\n position: absolute;\n touch-action: none;\n transition:\n transform 0.4s,\n opacity 0.4s,\n height 0.4s,\n box-shadow 0.2s;\n box-sizing: border-box;\n align-items: center;\n gap: 6px;\n display: inline-grid;\n background: var(--ngp-background);\n box-shadow: var(--ngp-shadow);\n border: 1px solid var(--ngp-border);\n padding: 12px 16px;\n opacity: 0;\n transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);\n border-radius: 8px;\n z-index: var(--ngp-toast-z-index);\n grid-template-columns: 1fr auto;\n grid-template-rows: min-content min-content;\n column-gap: 12px;\n align-items: center;\n width: var(--ngp-toast-width);\n height: fit-content;\n transform: var(--y);\n }\n\n .toast-title {\n color: var(--ngp-text-primary);\n font-size: 0.75rem;\n font-weight: 600;\n margin: 0;\n grid-column: 1 / 2;\n grid-row: 1;\n line-height: 16px;\n user-select: none;\n }\n\n .toast-description {\n font-size: 0.75rem;\n margin: 0;\n color: var(--ngp-text-secondary);\n grid-column: 1 / 2;\n grid-row: 2;\n line-height: 16px;\n user-select: none;\n }\n\n .toast-dismiss {\n background: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n border: none;\n border-radius: 8px;\n padding: 4px 8px;\n font-weight: 600;\n font-size: 12px;\n cursor: pointer;\n grid-column: 2 / 3;\n grid-row: 1 / 3;\n max-height: 27px;\n }\n\n :host[data-position-x='end'] {\n right: 0;\n }\n\n :host[data-position-x='start'] {\n left: 0;\n }\n\n :host[data-position-y='top'] {\n top: 0;\n --lift: 1;\n --lift-amount: calc(var(--lift) * var(--ngp-toast-gap));\n --y: translateY(-100%);\n }\n\n :host[data-position-y='bottom'] {\n bottom: 0;\n --lift: -1;\n --lift-amount: calc(var(--lift) * var(--ngp-toast-gap));\n --y: translateY(100%);\n }\n\n :host[data-enter] {\n opacity: 1;\n --y: translateY(0);\n }\n\n :host[data-exit] {\n opacity: 0;\n --y: translateY(calc(calc(var(--lift) * var(--ngp-toast-gap)) * -1));\n }\n\n :host[data-visible='false'] {\n opacity: 0;\n pointer-events: none;\n }\n\n :host[data-expanded='true']::after {\n content: '';\n position: absolute;\n left: 0;\n height: calc(var(--ngp-toast-gap) + 1px);\n bottom: 100%;\n width: 100%;\n }\n\n :host[data-expanded='false'][data-front='false'] {\n --scale: var(--ngp-toasts-before) * 0.05 + 1;\n --y: translateY(calc(var(--lift-amount) * var(--ngp-toasts-before)))\n scale(calc(-1 * var(--scale)));\n height: var(--ngp-toast-front-height);\n }\n\n :host[data-expanded='true'] {\n --y: translateY(calc(var(--lift) * var(--ngp-toast-offset)));\n height: var(--ngp-toast-height);\n }\n\n :host[data-swiping='true'] {\n transform: var(--y) translateY(var(--ngp-toast-swipe-amount-y, 0))\n translateX(var(--ngp-toast-swipe-amount-x, 0));\n transition: none;\n }\n\n :host[data-swiping='true'][data-swipe-direction='left'] {\n /* Fade out from -45px to -100px swipe */\n opacity: calc(1 - clamp(0, ((-1 * var(--ngp-toast-swipe-x, 0px)) - 45) / 55, 1));\n }\n\n :host[data-swiping='true'][data-swipe-direction='right'] {\n /* Fade out from 45px to 100px swipe */\n opacity: calc(1 - clamp(0, (var(--ngp-toast-swipe-x, 0px) - 45) / 55, 1));\n }\n\n :host[data-swiping='true'][data-swipe-direction='top'] {\n /* Fade out from -45px to -100px swipe */\n opacity: calc(1 - clamp(0, ((-1 * var(--ngp-toast-swipe-y, 0px)) - 45) / 55, 1));\n }\n\n :host[data-swiping='true'][data-swipe-direction='bottom'] {\n /* Fade out from 45px to 100px swipe */\n opacity: calc(1 - clamp(0, (var(--ngp-toast-swipe-y, 0px) - 45) / 55, 1));\n }\n `,\n})\nexport class Toast {\n private readonly toastManager = inject(NgpToastManager);\n private readonly toast = inject(NgpToast);\n protected readonly context = injectToastContext<ToastContext>();\n\n dismiss(): void {\n this.toastManager.dismiss(this.toast);\n }\n}\n\ninterface ToastContext {\n header: string;\n description: string;\n}\n",
|
|
194
|
+
"primitive": "toast",
|
|
195
|
+
"hasVariants": false,
|
|
196
|
+
"hasSizes": true
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "toggle",
|
|
200
|
+
"code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgpButton } from 'ng-primitives/button';\nimport { injectToggleState, NgpToggle } from 'ng-primitives/toggle';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'button[app-toggle]',\n hostDirectives: [\n {\n directive: NgpToggle,\n inputs: ['ngpToggleSelected:selected', 'ngpToggleDisabled:disabled'],\n outputs: ['ngpToggleSelectedChange:selectedChange'],\n },\n { directive: NgpButton, inputs: ['disabled'] },\n ],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n padding-left: 1rem;\n padding-right: 1rem;\n border-radius: 0.5rem;\n color: var(--ngp-text-primary);\n border: none;\n outline: none;\n height: 2.5rem;\n font-weight: 500;\n background-color: var(--ngp-background);\n transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--ngp-button-shadow);\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n `,\n providers: [provideValueAccessor(Toggle)],\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Toggle implements ControlValueAccessor {\n /** Access the toggle state. */\n private readonly toggle = injectToggleState();\n\n /** The on change callback */\n private onChange?: ChangeFn<boolean>;\n\n /** The on touched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Any time the toggle changes, update the form value.\n this.toggle().selectedChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the toggle. */\n writeValue(value: boolean): void {\n this.toggle().setSelected(value);\n }\n\n /** Register a callback function to be called when the value changes. */\n registerOnChange(fn: ChangeFn<boolean>): void {\n this.onChange = fn;\n }\n\n /** Register a callback function to be called when the toggle is touched. */\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n /** Set the disabled state of the toggle. */\n setDisabledState(isDisabled: boolean): void {\n this.toggle().setDisabled(isDisabled);\n }\n}\n",
|
|
201
|
+
"primitive": "toggle",
|
|
202
|
+
"hasVariants": false,
|
|
203
|
+
"hasSizes": false
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"name": "toggle-group",
|
|
207
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\nimport { NgpToggleGroupItem } from 'ng-primitives/toggle-group';\n\n@Component({\n selector: 'button[app-toggle-group-item]',\n hostDirectives: [\n {\n directive: NgpToggleGroupItem,\n inputs: ['ngpToggleGroupItemValue:value', 'ngpToggleGroupItemDisabled:disabled'],\n },\n {\n directive: NgpButton,\n inputs: ['disabled'],\n },\n ],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n width: 2rem;\n height: 2rem;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n border: 1px solid transparent;\n background: transparent;\n outline: none;\n transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n box-sizing: border-box;\n color: var(--ngp-text-primary);\n font-size: 1.125rem;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n border-color: var(--ngp-border);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n border-color: transparent;\n }\n `,\n})\nexport class ToggleGroupItem {}\n",
|
|
208
|
+
"primitive": "toggle-group",
|
|
209
|
+
"hasVariants": false,
|
|
210
|
+
"hasSizes": true
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "toolbar",
|
|
214
|
+
"code": "import { Component } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\nimport { NgpRovingFocusItem } from 'ng-primitives/roving-focus';\n\n@Component({\n selector: 'button[app-toolbar-button]',\n hostDirectives: [\n { directive: NgpButton, inputs: ['disabled'] },\n {\n directive: NgpRovingFocusItem,\n inputs: ['ngpRovingFocusItemDisabled:disabled'],\n },\n ],\n host: {\n type: 'button',\n },\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n width: 2rem;\n height: 2rem;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n border: 1px solid transparent;\n background: transparent;\n outline: none;\n transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n box-sizing: border-box;\n color: var(--ngp-text-primary);\n cursor: pointer;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n border-color: var(--ngp-border);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n `,\n})\nexport class ToolbarButton {}\n",
|
|
215
|
+
"primitive": "toolbar",
|
|
216
|
+
"hasVariants": false,
|
|
217
|
+
"hasSizes": false
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "tooltip",
|
|
221
|
+
"code": "import { Directive, input } from '@angular/core';\nimport { injectTooltipTriggerState, NgpTooltipTrigger } from 'ng-primitives/tooltip';\nimport { Tooltip } from './tooltip';\n\n@Directive({\n selector: '[appTooltipTrigger]',\n hostDirectives: [\n {\n directive: NgpTooltipTrigger,\n inputs: [\n 'ngpTooltipTriggerPlacement:appTooltipTriggerPlacement',\n 'ngpTooltipTriggerDisabled:appTooltipTriggerDisabled',\n 'ngpTooltipTriggerOffset:appTooltipTriggerOffset',\n 'ngpTooltipTriggerShowDelay:appTooltipTriggerShowDelay',\n 'ngpTooltipTriggerHideDelay:appTooltipTriggerHideDelay',\n 'ngpTooltipTriggerFlip:appTooltipTriggerFlip',\n 'ngpTooltipTriggerContainer:appTooltipTriggerContainer',\n 'ngpTooltipTriggerShowOnOverflow:appTooltipTriggerShowOnOverflow',\n 'ngpTooltipTriggerContext:appTooltipTrigger',\n ],\n },\n ],\n})\nexport class TooltipTrigger {\n /** Access the tooltip trigger */\n private readonly tooltipTrigger = injectTooltipTriggerState();\n\n /** Define the content of the tooltip */\n readonly content = input.required<string>({\n alias: 'appTooltipTrigger',\n });\n\n constructor() {\n this.tooltipTrigger().tooltip.set(Tooltip);\n }\n}\n",
|
|
222
|
+
"primitive": "tooltip",
|
|
223
|
+
"hasVariants": false,
|
|
224
|
+
"hasSizes": false
|
|
225
|
+
}
|
|
226
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PrimitiveDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
entryPoint: string;
|
|
4
|
+
exports: string[];
|
|
5
|
+
hasSecondaryEntryPoint: boolean;
|
|
6
|
+
category: string;
|
|
7
|
+
description: string;
|
|
8
|
+
accessibility: string[];
|
|
9
|
+
examples?: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
code: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}>;
|
|
14
|
+
reusableComponent?: {
|
|
15
|
+
code: string;
|
|
16
|
+
hasVariants: boolean;
|
|
17
|
+
hasSizes: boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface ReusableComponentDefinition {
|
|
21
|
+
name: string;
|
|
22
|
+
code: string;
|
|
23
|
+
primitive: string;
|
|
24
|
+
hasVariants: boolean;
|
|
25
|
+
hasSizes: boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../packages/mcp/src/generated/types.ts"],"names":[],"mappings":";AAAA,qDAAqD"}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
|
+
const tools_js_1 = require("./tools.js");
|
|
8
|
+
// Server setup
|
|
9
|
+
const server = new mcp_js_1.McpServer({
|
|
10
|
+
name: 'ngp-mcp',
|
|
11
|
+
version: '0.1.0',
|
|
12
|
+
}, {
|
|
13
|
+
capabilities: {
|
|
14
|
+
tools: {},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
// Register all tools
|
|
18
|
+
(0, tools_js_1.registerTools)(server);
|
|
19
|
+
// Start server
|
|
20
|
+
function main() {
|
|
21
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
23
|
+
yield server.connect(transport);
|
|
24
|
+
console.error('Angular Primitives MCP server running on stdio');
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
main().catch(error => {
|
|
28
|
+
console.error('Server error:', error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/mcp/src/index.ts"],"names":[],"mappings":";;;;AACA,oEAAoE;AACpE,wEAAiF;AACjF,yCAA2C;AAE3C,eAAe;AACf,MAAM,MAAM,GAAG,IAAI,kBAAS,CAC1B;IACE,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;CACjB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;KACV;CACF,CACF,CAAC;AAEF,qBAAqB;AACrB,IAAA,wBAAa,EAAC,MAAM,CAAC,CAAC;AAEtB,eAAe;AACf,SAAe,IAAI;;QACjB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAClE,CAAC;CAAA;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface PrimitiveDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
entryPoint: string;
|
|
5
|
+
exports: string[];
|
|
6
|
+
category: string;
|
|
7
|
+
accessibility: string[];
|
|
8
|
+
hasSecondaryEntryPoint: boolean;
|
|
9
|
+
examples?: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
code: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}>;
|
|
14
|
+
reusableComponent?: {
|
|
15
|
+
code: string;
|
|
16
|
+
hasVariants: boolean;
|
|
17
|
+
hasSizes: boolean;
|
|
18
|
+
};
|
|
19
|
+
apiData?: {
|
|
20
|
+
selector?: string;
|
|
21
|
+
exportAs?: string[];
|
|
22
|
+
inputs?: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
type: string;
|
|
25
|
+
description: string;
|
|
26
|
+
isRequired: boolean;
|
|
27
|
+
defaultValue?: string;
|
|
28
|
+
}>;
|
|
29
|
+
outputs?: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
type: string;
|
|
32
|
+
description: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export declare function loadPrimitivesData(): PrimitiveDefinition[];
|
|
37
|
+
export declare function loadApiData(): Record<string, any>;
|
|
38
|
+
export declare function loadReusableComponentsData(): any[];
|
|
39
|
+
export declare function getEnrichedPrimitivesRegistry(): PrimitiveDefinition[];
|
|
40
|
+
export declare function generateUsageExample(primitive: PrimitiveDefinition): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadPrimitivesData = loadPrimitivesData;
|
|
4
|
+
exports.loadApiData = loadApiData;
|
|
5
|
+
exports.loadReusableComponentsData = loadReusableComponentsData;
|
|
6
|
+
exports.getEnrichedPrimitivesRegistry = getEnrichedPrimitivesRegistry;
|
|
7
|
+
exports.generateUsageExample = generateUsageExample;
|
|
8
|
+
// This file loads primitives data from the generated files.
|
|
9
|
+
// Data is extracted at build time using the mcp-data-extraction executor.
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
// Function to load primitives data from generated JSON
|
|
13
|
+
function loadPrimitivesData() {
|
|
14
|
+
try {
|
|
15
|
+
const dataPath = (0, path_1.join)(__dirname, 'generated', 'primitives-data.json');
|
|
16
|
+
const data = (0, fs_1.readFileSync)(dataPath, 'utf8');
|
|
17
|
+
return JSON.parse(data);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.error('Could not load primitives data:', error);
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Function to load API data from extracted JSON
|
|
25
|
+
function loadApiData() {
|
|
26
|
+
try {
|
|
27
|
+
const apiPath = (0, path_1.join)(__dirname, 'generated', 'api-data.json');
|
|
28
|
+
const data = (0, fs_1.readFileSync)(apiPath, 'utf8');
|
|
29
|
+
return JSON.parse(data);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.warn('Could not load API data:', error);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Function to load reusable components data
|
|
37
|
+
function loadReusableComponentsData() {
|
|
38
|
+
try {
|
|
39
|
+
const dataPath = (0, path_1.join)(__dirname, 'generated', 'reusable-components.json');
|
|
40
|
+
const data = (0, fs_1.readFileSync)(dataPath, 'utf8');
|
|
41
|
+
return JSON.parse(data);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.warn('Could not load reusable components data:', error);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Function to merge primitives with API data
|
|
49
|
+
function getEnrichedPrimitivesRegistry() {
|
|
50
|
+
const primitives = loadPrimitivesData();
|
|
51
|
+
const apiData = loadApiData();
|
|
52
|
+
return primitives.map(primitive => {
|
|
53
|
+
// Try to find API data for the main export
|
|
54
|
+
const mainExport = primitive.exports[0];
|
|
55
|
+
const extractedData = apiData[mainExport];
|
|
56
|
+
if (extractedData) {
|
|
57
|
+
return Object.assign(Object.assign({}, primitive), { apiData: {
|
|
58
|
+
selector: extractedData.selector,
|
|
59
|
+
exportAs: extractedData.exportAs,
|
|
60
|
+
inputs: extractedData.inputs,
|
|
61
|
+
outputs: extractedData.outputs,
|
|
62
|
+
} });
|
|
63
|
+
}
|
|
64
|
+
return primitive;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Generate usage examples for primitives
|
|
68
|
+
function generateUsageExample(primitive) {
|
|
69
|
+
var _a;
|
|
70
|
+
// Priority 1: Use reusable component if available
|
|
71
|
+
if ((_a = primitive.reusableComponent) === null || _a === void 0 ? void 0 : _a.code) {
|
|
72
|
+
const importStatement = `import { ${primitive.exports.slice(0, 3).join(', ')}${primitive.exports.length > 3 ? ', ...' : ''} } from '${primitive.entryPoint}';`;
|
|
73
|
+
return `${importStatement}\n\n// Reusable Component Implementation:\n\n${primitive.reusableComponent.code}`;
|
|
74
|
+
}
|
|
75
|
+
// Priority 2: Use extracted examples from documentation
|
|
76
|
+
if (primitive.examples && primitive.examples.length > 0) {
|
|
77
|
+
const firstExample = primitive.examples[0];
|
|
78
|
+
const importStatement = `import { ${primitive.exports.slice(0, 3).join(', ')}${primitive.exports.length > 3 ? ', ...' : ''} } from '${primitive.entryPoint}';`;
|
|
79
|
+
let exampleHeader = '';
|
|
80
|
+
if (firstExample.description) {
|
|
81
|
+
exampleHeader = `// ${firstExample.description}\n\n`;
|
|
82
|
+
}
|
|
83
|
+
return `${importStatement}\n\n${exampleHeader}${firstExample.code}`;
|
|
84
|
+
}
|
|
85
|
+
// Priority 3: Generate minimal usage based on exports
|
|
86
|
+
const mainExport = primitive.exports.find(exp => exp.startsWith('Ngp') &&
|
|
87
|
+
!exp.includes('State') &&
|
|
88
|
+
!exp.includes('Props') &&
|
|
89
|
+
!exp.includes('Config'));
|
|
90
|
+
if (!mainExport) {
|
|
91
|
+
return `import { ${primitive.exports.slice(0, 3).join(', ')}${primitive.exports.length > 3 ? ', ...' : ''} } from '${primitive.entryPoint}';\n\n// See documentation for usage details`;
|
|
92
|
+
}
|
|
93
|
+
// Generate selector from export name (e.g., NgpButton -> ngpButton)
|
|
94
|
+
const selector = mainExport.charAt(0).toLowerCase() + mainExport.slice(1);
|
|
95
|
+
return `import { ${mainExport} } from '${primitive.entryPoint}';\n\n// Basic usage:\n<element ${selector}>Content</element>`;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=primitives-registry.js.map
|