@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,1521 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "a11y",
|
|
4
|
+
"entryPoint": "ng-primitives/a11y",
|
|
5
|
+
"exports": [
|
|
6
|
+
"injectVisuallyHiddenState",
|
|
7
|
+
"ngpVisuallyHidden",
|
|
8
|
+
"NgpVisuallyHiddenStateToken",
|
|
9
|
+
"provideVisuallyHiddenState",
|
|
10
|
+
"type NgpVisuallyHiddenProps",
|
|
11
|
+
"type NgpVisuallyHiddenState"
|
|
12
|
+
],
|
|
13
|
+
"hasSecondaryEntryPoint": true,
|
|
14
|
+
"category": "utility",
|
|
15
|
+
"description": "a11y primitive component",
|
|
16
|
+
"accessibility": [],
|
|
17
|
+
"examples": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "accordion",
|
|
21
|
+
"entryPoint": "ng-primitives/accordion",
|
|
22
|
+
"exports": [
|
|
23
|
+
"NgpAccordionContent",
|
|
24
|
+
"injectAccordionContentState",
|
|
25
|
+
"ngpAccordionContent",
|
|
26
|
+
"NgpAccordionContentStateToken",
|
|
27
|
+
"provideAccordionContentState",
|
|
28
|
+
"type NgpAccordionContentProps",
|
|
29
|
+
"type NgpAccordionContentState",
|
|
30
|
+
"NgpAccordionItem",
|
|
31
|
+
"injectAccordionItemState",
|
|
32
|
+
"ngpAccordionItem",
|
|
33
|
+
"NgpAccordionItemStateToken",
|
|
34
|
+
"provideAccordionItemState",
|
|
35
|
+
"type NgpAccordionItemProps",
|
|
36
|
+
"type NgpAccordionItemState",
|
|
37
|
+
"NgpAccordionTrigger",
|
|
38
|
+
"NgpAccordion",
|
|
39
|
+
"injectAccordionState",
|
|
40
|
+
"ngpAccordion",
|
|
41
|
+
"NgpAccordionStateToken",
|
|
42
|
+
"NgpAccordionType",
|
|
43
|
+
"provideAccordionState",
|
|
44
|
+
"type NgpAccordionProps",
|
|
45
|
+
"type NgpAccordionState",
|
|
46
|
+
"NgpAccordionConfig",
|
|
47
|
+
"provideAccordionConfig",
|
|
48
|
+
"NgpAccordionTriggerStateToken",
|
|
49
|
+
"ngpAccordionTrigger",
|
|
50
|
+
"injectAccordionTriggerState",
|
|
51
|
+
"provideAccordionTriggerState",
|
|
52
|
+
"type NgpAccordionTriggerState",
|
|
53
|
+
"type NgpAccordionTriggerProps"
|
|
54
|
+
],
|
|
55
|
+
"hasSecondaryEntryPoint": true,
|
|
56
|
+
"category": "layout",
|
|
57
|
+
"description": "Display a series of panels that can be expanded or collapsed.",
|
|
58
|
+
"accessibility": [
|
|
59
|
+
"ARIA design"
|
|
60
|
+
],
|
|
61
|
+
"examples": [
|
|
62
|
+
{
|
|
63
|
+
"name": "example-0",
|
|
64
|
+
"code": "import {\n NgpAccordion,\n NgpAccordionItem,\n NgpAccordionTrigger,\n NgpAccordionContent,\n} from 'ng-primitives/accordion';",
|
|
65
|
+
"description": "Import"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "example-1",
|
|
69
|
+
"code": "<div ngpAccordion ngpAccordionType=\"single\" ngpAccordionCollapsible>\n <div ngpAccordionItem ngpAccordionItemValue=\"item-1\">\n <h3>\n <button ngpAccordionTrigger ngpButton>Would you like to learn more?</button>\n </h3>\n <div ngpAccordionContent>If you would like to learn more please reach out to us on GitHub.</div>\n </div>\n\n <div ngpAccordionItem ngpAccordionItemValue=\"item-2\">\n <h3>\n <button ngpAccordionTrigger ngpButton>Can I use this in my project?</button>\n </h3>\n <div ngpAccordionContent>Yes, this is open source and you can use it in your project.</div>\n </div>\n</div>",
|
|
70
|
+
"description": "Usage"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "example-2",
|
|
74
|
+
"code": "import { provideAccordionConfig } from 'ng-primitives/accordion';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideAccordionConfig({\n type: 'multiple',\n collapsible: true,\n orientation: 'horizontal',\n }),\n ],\n});",
|
|
75
|
+
"description": "Global Configuration"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"reusableComponent": {
|
|
79
|
+
"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",
|
|
80
|
+
"hasVariants": false,
|
|
81
|
+
"hasSizes": true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "ai",
|
|
86
|
+
"entryPoint": "ng-primitives/ai",
|
|
87
|
+
"exports": [
|
|
88
|
+
"NgpPromptComposerInput",
|
|
89
|
+
"NgpPromptComposerSubmit",
|
|
90
|
+
"NgpPromptComposer",
|
|
91
|
+
"NgpThreadMessage",
|
|
92
|
+
"NgpThread",
|
|
93
|
+
"NgpPromptComposerDictation",
|
|
94
|
+
"NgpThreadViewport",
|
|
95
|
+
"NgpThreadSuggestion",
|
|
96
|
+
"providePromptComposerState",
|
|
97
|
+
"injectPromptComposerState",
|
|
98
|
+
"providePromptComposerDictationState",
|
|
99
|
+
"injectPromptComposerDictationState",
|
|
100
|
+
"providePromptComposerInputState",
|
|
101
|
+
"injectPromptComposerInputState",
|
|
102
|
+
"providePromptComposerSubmitState",
|
|
103
|
+
"injectPromptComposerSubmitState",
|
|
104
|
+
"provideThreadState",
|
|
105
|
+
"injectThreadState",
|
|
106
|
+
"provideThreadSuggestionState",
|
|
107
|
+
"injectThreadSuggestionState",
|
|
108
|
+
"provideThreadViewportState",
|
|
109
|
+
"injectThreadViewportState",
|
|
110
|
+
"provideThreadMessageState",
|
|
111
|
+
"injectThreadMessageState"
|
|
112
|
+
],
|
|
113
|
+
"hasSecondaryEntryPoint": true,
|
|
114
|
+
"category": "utility",
|
|
115
|
+
"description": "ai primitive component",
|
|
116
|
+
"accessibility": [],
|
|
117
|
+
"examples": []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "autofill",
|
|
121
|
+
"entryPoint": "ng-primitives/autofill",
|
|
122
|
+
"exports": [
|
|
123
|
+
"NgpAutofill",
|
|
124
|
+
"NgpAutofillStateToken",
|
|
125
|
+
"ngpAutofill",
|
|
126
|
+
"injectAutofillState",
|
|
127
|
+
"provideAutofillState",
|
|
128
|
+
"type NgpAutofillState",
|
|
129
|
+
"type NgpAutofillProps"
|
|
130
|
+
],
|
|
131
|
+
"hasSecondaryEntryPoint": true,
|
|
132
|
+
"category": "utility",
|
|
133
|
+
"description": "autofill primitive component",
|
|
134
|
+
"accessibility": [],
|
|
135
|
+
"examples": []
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "avatar",
|
|
139
|
+
"entryPoint": "ng-primitives/avatar",
|
|
140
|
+
"exports": [
|
|
141
|
+
"NgpAvatarFallback",
|
|
142
|
+
"injectAvatarFallbackState",
|
|
143
|
+
"ngpAvatarFallback",
|
|
144
|
+
"NgpAvatarFallbackStateToken",
|
|
145
|
+
"provideAvatarFallbackState",
|
|
146
|
+
"type NgpAvatarFallbackProps",
|
|
147
|
+
"type NgpAvatarFallbackState",
|
|
148
|
+
"NgpAvatarImage",
|
|
149
|
+
"injectAvatarImageState",
|
|
150
|
+
"ngpAvatarImage",
|
|
151
|
+
"NgpAvatarImageStateToken",
|
|
152
|
+
"provideAvatarImageState",
|
|
153
|
+
"type NgpAvatarImageProps",
|
|
154
|
+
"type NgpAvatarImageState",
|
|
155
|
+
"NgpAvatar",
|
|
156
|
+
"injectAvatarState",
|
|
157
|
+
"ngpAvatar",
|
|
158
|
+
"NgpAvatarStateToken",
|
|
159
|
+
"NgpAvatarStatus",
|
|
160
|
+
"provideAvatarState",
|
|
161
|
+
"type NgpAvatarProps",
|
|
162
|
+
"type NgpAvatarState",
|
|
163
|
+
"NgpAvatarConfig",
|
|
164
|
+
"provideAvatarConfig"
|
|
165
|
+
],
|
|
166
|
+
"hasSecondaryEntryPoint": true,
|
|
167
|
+
"category": "data",
|
|
168
|
+
"description": "Display an image that represents a user with a text fallback.",
|
|
169
|
+
"accessibility": [],
|
|
170
|
+
"examples": [
|
|
171
|
+
{
|
|
172
|
+
"name": "example-0",
|
|
173
|
+
"code": "<span ngpAvatar>\n <img ngpAvatarImage src=\"...\" alt=\"...\" />\n <span ngpAvatarFallback>NG</span>\n</span>",
|
|
174
|
+
"description": "Usage"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "example-1",
|
|
178
|
+
"code": "import { provideAvatarConfig } from 'ng-primitives/avatar';\n\nbootstrapApplication(AppComponent, {\n providers: [provideAvatarConfig({ delay: 1000 })],\n});",
|
|
179
|
+
"description": "Global Configuration"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"reusableComponent": {
|
|
183
|
+
"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",
|
|
184
|
+
"hasVariants": false,
|
|
185
|
+
"hasSizes": false
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "breadcrumbs",
|
|
190
|
+
"entryPoint": "ng-primitives/breadcrumbs",
|
|
191
|
+
"exports": [
|
|
192
|
+
"NgpBreadcrumbEllipsis",
|
|
193
|
+
"NgpBreadcrumbItem",
|
|
194
|
+
"NgpBreadcrumbLink",
|
|
195
|
+
"NgpBreadcrumbList",
|
|
196
|
+
"NgpBreadcrumbPage",
|
|
197
|
+
"NgpBreadcrumbSeparator",
|
|
198
|
+
"NgpBreadcrumbs",
|
|
199
|
+
"NgpBreadcrumbsStateToken",
|
|
200
|
+
"ngpBreadcrumbs",
|
|
201
|
+
"injectBreadcrumbsState",
|
|
202
|
+
"provideBreadcrumbsState",
|
|
203
|
+
"type NgpBreadcrumbsState",
|
|
204
|
+
"type NgpBreadcrumbsProps",
|
|
205
|
+
"NgpBreadcrumbListStateToken",
|
|
206
|
+
"ngpBreadcrumbList",
|
|
207
|
+
"injectBreadcrumbListState",
|
|
208
|
+
"provideBreadcrumbListState",
|
|
209
|
+
"type NgpBreadcrumbListState",
|
|
210
|
+
"type NgpBreadcrumbListProps",
|
|
211
|
+
"NgpBreadcrumbItemStateToken",
|
|
212
|
+
"ngpBreadcrumbItem",
|
|
213
|
+
"injectBreadcrumbItemState",
|
|
214
|
+
"provideBreadcrumbItemState",
|
|
215
|
+
"type NgpBreadcrumbItemState",
|
|
216
|
+
"type NgpBreadcrumbItemProps",
|
|
217
|
+
"NgpBreadcrumbLinkStateToken",
|
|
218
|
+
"ngpBreadcrumbLink",
|
|
219
|
+
"injectBreadcrumbLinkState",
|
|
220
|
+
"provideBreadcrumbLinkState",
|
|
221
|
+
"type NgpBreadcrumbLinkState",
|
|
222
|
+
"type NgpBreadcrumbLinkProps",
|
|
223
|
+
"NgpBreadcrumbPageStateToken",
|
|
224
|
+
"ngpBreadcrumbPage",
|
|
225
|
+
"injectBreadcrumbPageState",
|
|
226
|
+
"provideBreadcrumbPageState",
|
|
227
|
+
"type NgpBreadcrumbPageState",
|
|
228
|
+
"type NgpBreadcrumbPageProps",
|
|
229
|
+
"NgpBreadcrumbSeparatorStateToken",
|
|
230
|
+
"ngpBreadcrumbSeparator",
|
|
231
|
+
"injectBreadcrumbSeparatorState",
|
|
232
|
+
"provideBreadcrumbSeparatorState",
|
|
233
|
+
"type NgpBreadcrumbSeparatorState",
|
|
234
|
+
"type NgpBreadcrumbSeparatorProps",
|
|
235
|
+
"NgpBreadcrumbEllipsisStateToken",
|
|
236
|
+
"ngpBreadcrumbEllipsis",
|
|
237
|
+
"injectBreadcrumbEllipsisState",
|
|
238
|
+
"provideBreadcrumbEllipsisState",
|
|
239
|
+
"type NgpBreadcrumbEllipsisState",
|
|
240
|
+
"type NgpBreadcrumbEllipsisProps"
|
|
241
|
+
],
|
|
242
|
+
"hasSecondaryEntryPoint": true,
|
|
243
|
+
"category": "navigation",
|
|
244
|
+
"description": "Help users understand their location within a hierarchy with a fully accessible breadcrumb trail.",
|
|
245
|
+
"accessibility": [
|
|
246
|
+
"ARIA Breadcrumb"
|
|
247
|
+
],
|
|
248
|
+
"examples": [
|
|
249
|
+
{
|
|
250
|
+
"name": "example-0",
|
|
251
|
+
"code": "import {\n NgpBreadcrumbs,\n NgpBreadcrumbList,\n NgpBreadcrumbItem,\n NgpBreadcrumbLink,\n NgpBreadcrumbSeparator,\n NgpBreadcrumbEllipsis,\n NgpBreadcrumbPage,\n} from 'ng-primitives/breadcrumbs';",
|
|
252
|
+
"description": "Import"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "example-1",
|
|
256
|
+
"code": "<nav aria-label=\"Breadcrumb\" ngpBreadcrumbs>\n <ol ngpBreadcrumbList>\n <li ngpBreadcrumbItem>\n <a ngpBreadcrumbLink href=\"/\">Home</a>\n </li>\n\n <li ngpBreadcrumbSeparator>/</li>\n\n <li ngpBreadcrumbItem>\n <button type=\"button\" aria-label=\"Toggle breadcrumb menu\">\n <span ngpBreadcrumbEllipsis>...</span>\n </button>\n </li>\n\n <li ngpBreadcrumbSeparator>/</li>\n\n <li ngpBreadcrumbItem>\n <a ngpBreadcrumbLink href=\"...\">Components</a>\n </li>\n\n <li ngpBreadcrumbSeparator>/</li>\n\n <li ngpBreadcrumbItem>\n <span ngpBreadcrumbPage>Breadcrumbs</span>\n </li>\n </ol>\n</nav>",
|
|
257
|
+
"description": "Usage"
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"name": "button",
|
|
263
|
+
"entryPoint": "ng-primitives/button",
|
|
264
|
+
"exports": [
|
|
265
|
+
"NgpButton",
|
|
266
|
+
"injectButtonState",
|
|
267
|
+
"ngpButton",
|
|
268
|
+
"NgpButtonProps",
|
|
269
|
+
"NgpButtonState",
|
|
270
|
+
"provideButtonState"
|
|
271
|
+
],
|
|
272
|
+
"hasSecondaryEntryPoint": true,
|
|
273
|
+
"category": "form",
|
|
274
|
+
"description": "A button is a clickable element that can be used to trigger an action. This primitive enhances the native button element with improved accessibility and interaction handling for hover, press and focus.",
|
|
275
|
+
"accessibility": [],
|
|
276
|
+
"examples": [
|
|
277
|
+
{
|
|
278
|
+
"name": "example-0",
|
|
279
|
+
"code": "<button ngpButton>Button</button>",
|
|
280
|
+
"description": "Usage"
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
"reusableComponent": {
|
|
284
|
+
"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",
|
|
285
|
+
"hasVariants": true,
|
|
286
|
+
"hasSizes": true
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"name": "checkbox",
|
|
291
|
+
"entryPoint": "ng-primitives/checkbox",
|
|
292
|
+
"exports": [
|
|
293
|
+
"NgpCheckbox",
|
|
294
|
+
"injectCheckboxState",
|
|
295
|
+
"ngpCheckbox",
|
|
296
|
+
"NgpCheckboxProps",
|
|
297
|
+
"NgpCheckboxState",
|
|
298
|
+
"provideCheckboxState"
|
|
299
|
+
],
|
|
300
|
+
"hasSecondaryEntryPoint": true,
|
|
301
|
+
"category": "form",
|
|
302
|
+
"description": "Perform state toggling.",
|
|
303
|
+
"accessibility": [
|
|
304
|
+
"ARIA design"
|
|
305
|
+
],
|
|
306
|
+
"examples": [
|
|
307
|
+
{
|
|
308
|
+
"name": "example-0",
|
|
309
|
+
"code": "<span ngpCheckbox [(ngpCheckboxChecked)]=\"checked\">\n <ng-icon name=\"checkmark\" aria-hidden=\"true\" />\n</span>",
|
|
310
|
+
"description": "Usage"
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
"reusableComponent": {
|
|
314
|
+
"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",
|
|
315
|
+
"hasVariants": false,
|
|
316
|
+
"hasSizes": true
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"name": "combobox",
|
|
321
|
+
"entryPoint": "ng-primitives/combobox",
|
|
322
|
+
"exports": [
|
|
323
|
+
"NgpComboboxButton",
|
|
324
|
+
"NgpComboboxDropdown",
|
|
325
|
+
"NgpComboboxInput",
|
|
326
|
+
"NgpComboboxOption",
|
|
327
|
+
"NgpComboboxPortal",
|
|
328
|
+
"NgpCombobox",
|
|
329
|
+
"type NgpComboboxPlacement",
|
|
330
|
+
"injectComboboxState",
|
|
331
|
+
"provideComboboxState",
|
|
332
|
+
"NgpComboboxConfig",
|
|
333
|
+
"provideComboboxConfig"
|
|
334
|
+
],
|
|
335
|
+
"hasSecondaryEntryPoint": true,
|
|
336
|
+
"category": "form",
|
|
337
|
+
"description": "The Combobox primitive is a combination of a dropdown and an input field. It allows users to select from a list of options while filtering the list based on their input.",
|
|
338
|
+
"accessibility": [
|
|
339
|
+
"keyboard navigation",
|
|
340
|
+
"Keyboard Navigation",
|
|
341
|
+
"Focus Management"
|
|
342
|
+
],
|
|
343
|
+
"examples": [
|
|
344
|
+
{
|
|
345
|
+
"name": "example-0",
|
|
346
|
+
"code": "import {\n NgpCombobox,\n NgpComboboxButton,\n NgpComboboxDropdown,\n NgpComboboxInput,\n NgpComboboxOption,\n NgpComboboxPortal,\n} from 'ng-primitives/combobox';",
|
|
347
|
+
"description": "Import"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"name": "example-1",
|
|
351
|
+
"code": "<div ngpCombobox>\n <input ngpComboboxInput />\n <button ngpComboboxButton>▼</button>\n <div ngpComboboxDropdown>\n @for (option of options; track option) {\n <div ngpComboboxOption [ngpComboboxOptionValue]=\"option\">{{ option }}</div>\n }\n </div>\n</div>",
|
|
352
|
+
"description": "Usage"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"name": "example-2",
|
|
356
|
+
"code": "<div ngpCombobox>\n <button ngpComboboxButton>{{ selectedOption || 'Select an option' }} ▼</button>\n <div ngpComboboxDropdown>\n @for (option of options; track option) {\n <div ngpComboboxOption [ngpComboboxOptionValue]=\"option\">{{ option }}</div>\n }\n </div>\n</div>",
|
|
357
|
+
"description": "Without Input Field"
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"name": "example-3",
|
|
361
|
+
"code": "<div ngpComboboxOptionValue=\"all\" ngpComboboxOption>Select All</div>",
|
|
362
|
+
"description": "Select All Functionality"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"name": "example-4",
|
|
366
|
+
"code": "import { provideComboboxConfig } from 'ng-primitives/combobox';\n\nbootstrapApplication(AppComponent, {\n providers: [provideComboboxConfig({ placement: 'bottom', container: document.body })],\n});",
|
|
367
|
+
"description": "Global Configuration"
|
|
368
|
+
}
|
|
369
|
+
],
|
|
370
|
+
"reusableComponent": {
|
|
371
|
+
"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",
|
|
372
|
+
"hasVariants": false,
|
|
373
|
+
"hasSizes": true
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"name": "date-picker",
|
|
378
|
+
"entryPoint": "ng-primitives/date-picker",
|
|
379
|
+
"exports": [
|
|
380
|
+
"injectDatePickerConfig",
|
|
381
|
+
"provideDatePickerConfig",
|
|
382
|
+
"NgpDatePickerCellRender",
|
|
383
|
+
"injectDatePickerCellDate",
|
|
384
|
+
"injectDatePickerCellRender",
|
|
385
|
+
"NgpDatePickerCellRenderToken",
|
|
386
|
+
"NgpDatePickerCell",
|
|
387
|
+
"NgpDatePickerDateButton",
|
|
388
|
+
"injectDatePickerDateButton",
|
|
389
|
+
"NgpDatePickerDateButtonToken",
|
|
390
|
+
"NgpDatePickerGrid",
|
|
391
|
+
"NgpDatePickerLabel",
|
|
392
|
+
"injectDatePickerLabel",
|
|
393
|
+
"NgpDatePickerLabelToken",
|
|
394
|
+
"NgpDatePickerNextMonth",
|
|
395
|
+
"NgpDatePickerPreviousMonth",
|
|
396
|
+
"NgpDatePickerRowRender",
|
|
397
|
+
"injectDatePickerRowRender",
|
|
398
|
+
"injectDatePickerWeek",
|
|
399
|
+
"NgpDatePickerRowRenderToken",
|
|
400
|
+
"NgpDatePicker",
|
|
401
|
+
"transformToFirstDayOfWeekNumber",
|
|
402
|
+
"type NgpDatePickerFirstDayOfWeekNumber",
|
|
403
|
+
"type NgpDatePickerFirstDayOfWeekNumberInput",
|
|
404
|
+
"injectDatePickerState",
|
|
405
|
+
"provideDatePickerState",
|
|
406
|
+
"NgpDateRangePicker",
|
|
407
|
+
"provideDateRangePickerState",
|
|
408
|
+
"injectDateRangePickerState"
|
|
409
|
+
],
|
|
410
|
+
"hasSecondaryEntryPoint": true,
|
|
411
|
+
"category": "date-time",
|
|
412
|
+
"description": "A date picker is a component that allows users to select a date from a calendar and navigate through months and years.",
|
|
413
|
+
"accessibility": [
|
|
414
|
+
"ARIA design"
|
|
415
|
+
],
|
|
416
|
+
"examples": [
|
|
417
|
+
{
|
|
418
|
+
"name": "example-0",
|
|
419
|
+
"code": "import {\n NgpDatePicker,\n NgpDateRangePicker,\n NgpDatePickerLabel,\n NgpDatePickerNextMonth,\n NgpDatePickerPreviousMonth,\n NgpDatePickerGrid,\n NgpDatePickerCell,\n NgpDatePickerRowRender,\n NgpDatePickerCellRender,\n NgpDatePickerDateButton,\n} from 'ng-primitives/date-picker';",
|
|
420
|
+
"description": "Import"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"name": "example-1",
|
|
424
|
+
"code": "<div ngpDatePicker>\n <div>\n <button ngpDatePickerPreviousMonth>...</button>\n <h2 ngpDatePickerLabel>...</h2>\n <button ngpDatePickerNextMonth>...</button>\n </div>\n <table ngpDatePickerGrid>\n <thead>\n <tr>\n <th scope=\"col\" abbr=\"Sunday\">S</th>\n ...\n </tr>\n </thead>\n <tbody>\n <tr *ngpDatePickerRowRender>\n <td *ngpDatePickerCellRender=\"let date\" ngpDatePickerCell>\n <button ngpDatePickerDateButton>...</button>\n </td>\n </tr>\n </tbody>\n </table>\n</div>",
|
|
425
|
+
"description": "Usage"
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
"name": "example-2",
|
|
429
|
+
"code": "import { provideDatePickerConfig } from 'ng-primitives/date-picker';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideDatePickerConfig({\n firstDayOfWeek: 1, // Monday\n }),\n ],\n});",
|
|
430
|
+
"description": "Global Configuration"
|
|
431
|
+
}
|
|
432
|
+
],
|
|
433
|
+
"reusableComponent": {
|
|
434
|
+
"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",
|
|
435
|
+
"hasVariants": false,
|
|
436
|
+
"hasSizes": true
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"name": "date-time",
|
|
441
|
+
"entryPoint": "ng-primitives/date-time",
|
|
442
|
+
"exports": [
|
|
443
|
+
"NgpDateAdapter",
|
|
444
|
+
"NgpDateUnits",
|
|
445
|
+
"NgpDuration",
|
|
446
|
+
"injectDateAdapter",
|
|
447
|
+
"provideDateAdapter",
|
|
448
|
+
"NgpNativeDateAdapter"
|
|
449
|
+
],
|
|
450
|
+
"hasSecondaryEntryPoint": true,
|
|
451
|
+
"category": "date-time",
|
|
452
|
+
"description": "date-time primitive component",
|
|
453
|
+
"accessibility": [],
|
|
454
|
+
"examples": []
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
"name": "date-time-luxon",
|
|
458
|
+
"entryPoint": "ng-primitives/date-time-luxon",
|
|
459
|
+
"exports": [],
|
|
460
|
+
"hasSecondaryEntryPoint": true,
|
|
461
|
+
"category": "date-time",
|
|
462
|
+
"description": "date-time-luxon primitive component",
|
|
463
|
+
"accessibility": [],
|
|
464
|
+
"examples": []
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"name": "dialog",
|
|
468
|
+
"entryPoint": "ng-primitives/dialog",
|
|
469
|
+
"exports": [
|
|
470
|
+
"NgpDialogConfig",
|
|
471
|
+
"provideDialogConfig",
|
|
472
|
+
"NgpDialogDescription",
|
|
473
|
+
"NgpDialogOverlay",
|
|
474
|
+
"NgpDialogTitle",
|
|
475
|
+
"NgpDialogTrigger",
|
|
476
|
+
"NgpDialog",
|
|
477
|
+
"injectDialogRef",
|
|
478
|
+
"NgpDialogRef",
|
|
479
|
+
"injectDialogState",
|
|
480
|
+
"provideDialogState",
|
|
481
|
+
"NgpDialogManager"
|
|
482
|
+
],
|
|
483
|
+
"hasSecondaryEntryPoint": true,
|
|
484
|
+
"category": "feedback",
|
|
485
|
+
"description": "A dialog is a floating window that can be used to display information or prompt the user for input.",
|
|
486
|
+
"accessibility": [
|
|
487
|
+
"ARIA design"
|
|
488
|
+
],
|
|
489
|
+
"examples": [
|
|
490
|
+
{
|
|
491
|
+
"name": "example-0",
|
|
492
|
+
"code": "import {\n NgpDialog,\n NgpDialogTitle,\n NgpDialogDescription,\n NgpDialogTrigger,\n NgpDialogOverlay,\n} from 'ng-primitives/dialog';",
|
|
493
|
+
"description": "Import"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"name": "example-1",
|
|
497
|
+
"code": "<button [ngpDialogTrigger]=\"dialog\" ngpButton>Launch Dialog</button>\n\n<ng-template #dialog let-close=\"close\">\n <div ngpDialogOverlay>\n <div ngpDialog>\n <h1 ngpDialogTitle>Publish this article?</h1>\n <p ngpDialogDescription>\n Are you sure you want to publish this article? This action is irreversible.\n </p>\n <div class=\"dialog-footer\">\n <button (click)=\"close()\" ngpButton>Cancel</button>\n <button (click)=\"close()\" ngpButton>Confirm</button>\n </div>\n </div>\n </div>\n</ng-template>",
|
|
498
|
+
"description": "Usage"
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
"reusableComponent": {
|
|
502
|
+
"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",
|
|
503
|
+
"hasVariants": false,
|
|
504
|
+
"hasSizes": true
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"name": "file-upload",
|
|
509
|
+
"entryPoint": "ng-primitives/file-upload",
|
|
510
|
+
"exports": [
|
|
511
|
+
"NgpFileDropzone",
|
|
512
|
+
"injectFileDropzoneState",
|
|
513
|
+
"provideFileDropzoneState",
|
|
514
|
+
"NgpFileUpload",
|
|
515
|
+
"injectFileUploadState",
|
|
516
|
+
"provideFileUploadState"
|
|
517
|
+
],
|
|
518
|
+
"hasSecondaryEntryPoint": true,
|
|
519
|
+
"category": "form",
|
|
520
|
+
"description": "The file upload primitive allows you to trigger a file upload from any element, giving you the more control over the appearance and behavior compared to the native file input.",
|
|
521
|
+
"accessibility": [],
|
|
522
|
+
"examples": [
|
|
523
|
+
{
|
|
524
|
+
"name": "example-0",
|
|
525
|
+
"code": "<button\n ngpFileUpload\n (ngpFileUploadSelected)=\"onFilesSelected($event)\"\n (ngpFileUploadCanceled)=\"onCancel()\"\n></button>",
|
|
526
|
+
"description": "Usage"
|
|
527
|
+
}
|
|
528
|
+
],
|
|
529
|
+
"reusableComponent": {
|
|
530
|
+
"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",
|
|
531
|
+
"hasVariants": false,
|
|
532
|
+
"hasSizes": false
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
"name": "focus-trap",
|
|
537
|
+
"entryPoint": "ng-primitives/focus-trap",
|
|
538
|
+
"exports": [
|
|
539
|
+
"NgpFocusTrap",
|
|
540
|
+
"NgpFocusTrapState",
|
|
541
|
+
"NgpFocusTrapProps",
|
|
542
|
+
"ngpFocusTrap",
|
|
543
|
+
"injectFocusTrapState",
|
|
544
|
+
"provideFocusTrapState"
|
|
545
|
+
],
|
|
546
|
+
"hasSecondaryEntryPoint": true,
|
|
547
|
+
"category": "utility",
|
|
548
|
+
"description": "focus-trap primitive component",
|
|
549
|
+
"accessibility": [],
|
|
550
|
+
"examples": []
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"name": "form-field",
|
|
554
|
+
"entryPoint": "ng-primitives/form-field",
|
|
555
|
+
"exports": [
|
|
556
|
+
"NgpDescription",
|
|
557
|
+
"injectDescriptionState",
|
|
558
|
+
"ngpDescription",
|
|
559
|
+
"provideDescriptionState",
|
|
560
|
+
"type NgpDescriptionProps",
|
|
561
|
+
"type NgpDescriptionState",
|
|
562
|
+
"NgpError",
|
|
563
|
+
"injectErrorState",
|
|
564
|
+
"ngpError",
|
|
565
|
+
"provideErrorState",
|
|
566
|
+
"type NgpErrorProps",
|
|
567
|
+
"type NgpErrorState",
|
|
568
|
+
"NgpFormControl",
|
|
569
|
+
"ngpFormControl",
|
|
570
|
+
"NgpFormField",
|
|
571
|
+
"injectFormFieldState",
|
|
572
|
+
"ngpFormField",
|
|
573
|
+
"provideFormFieldState",
|
|
574
|
+
"type NgpFormFieldProps",
|
|
575
|
+
"type NgpFormFieldState",
|
|
576
|
+
"NgpLabel",
|
|
577
|
+
"injectLabelState",
|
|
578
|
+
"ngpLabel",
|
|
579
|
+
"provideLabelState",
|
|
580
|
+
"type NgpLabelProps",
|
|
581
|
+
"type NgpLabelState"
|
|
582
|
+
],
|
|
583
|
+
"hasSecondaryEntryPoint": true,
|
|
584
|
+
"category": "form",
|
|
585
|
+
"description": "<docs-example name=\"form-field\"></docs-example>",
|
|
586
|
+
"accessibility": [
|
|
587
|
+
"screen reader"
|
|
588
|
+
],
|
|
589
|
+
"examples": [
|
|
590
|
+
{
|
|
591
|
+
"name": "example-0",
|
|
592
|
+
"code": "import {\n NgpFormField,\n NgpLabel,\n NgpDescription,\n NgpError,\n NgpFormControl,\n} from 'ng-primitives/form-field';",
|
|
593
|
+
"description": "Import"
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
"name": "example-1",
|
|
597
|
+
"code": "<div ngpFormField>\n <label ngpLabel>Label</label>\n <!-- Typically ngpFormControl would not be used directly, but a primitive like ngpInput would be used instead -->\n <input ngpFormControl />\n <p ngpDescription>Description</p>\n <p ngpError ngpErrorValidator=\"required\">Error</p>\n</div>",
|
|
598
|
+
"description": "Usage"
|
|
599
|
+
}
|
|
600
|
+
],
|
|
601
|
+
"reusableComponent": {
|
|
602
|
+
"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",
|
|
603
|
+
"hasVariants": false,
|
|
604
|
+
"hasSizes": false
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"name": "input",
|
|
609
|
+
"entryPoint": "ng-primitives/input",
|
|
610
|
+
"exports": [
|
|
611
|
+
"NgpInput",
|
|
612
|
+
"injectInputState",
|
|
613
|
+
"ngpInput",
|
|
614
|
+
"NgpInputProps",
|
|
615
|
+
"NgpInputState",
|
|
616
|
+
"provideInputState"
|
|
617
|
+
],
|
|
618
|
+
"hasSecondaryEntryPoint": true,
|
|
619
|
+
"category": "form",
|
|
620
|
+
"description": "The input primitive can be used to enhance the accessibility of an input element and provide consistent interaction handling for hover, focus, press and autofill states.",
|
|
621
|
+
"accessibility": [],
|
|
622
|
+
"examples": [
|
|
623
|
+
{
|
|
624
|
+
"name": "example-0",
|
|
625
|
+
"code": "<input ngpInput type=\"text\" />",
|
|
626
|
+
"description": "Usage"
|
|
627
|
+
}
|
|
628
|
+
],
|
|
629
|
+
"reusableComponent": {
|
|
630
|
+
"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",
|
|
631
|
+
"hasVariants": false,
|
|
632
|
+
"hasSizes": false
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
"name": "input-otp",
|
|
637
|
+
"entryPoint": "ng-primitives/input-otp",
|
|
638
|
+
"exports": [
|
|
639
|
+
"NgpInputOtpInput",
|
|
640
|
+
"NgpInputOtpSlot",
|
|
641
|
+
"NgpInputOtp",
|
|
642
|
+
"type NgpInputOtpInputMode",
|
|
643
|
+
"injectInputOtpState",
|
|
644
|
+
"provideInputOtpState"
|
|
645
|
+
],
|
|
646
|
+
"hasSecondaryEntryPoint": true,
|
|
647
|
+
"category": "form",
|
|
648
|
+
"description": "One-Time Password (OTP) input component with individual character slots for secure authentication codes.",
|
|
649
|
+
"accessibility": [],
|
|
650
|
+
"examples": [
|
|
651
|
+
{
|
|
652
|
+
"name": "example-0",
|
|
653
|
+
"code": "<div ngpInputOtp [(ngpInputOtpValue)]=\"otpValue\">\n <input ngpInputOtpInput />\n\n <div>\n <div ngpInputOtpSlot></div>\n <div ngpInputOtpSlot></div>\n <div ngpInputOtpSlot></div>\n </div>\n</div>",
|
|
654
|
+
"description": "Usage"
|
|
655
|
+
}
|
|
656
|
+
],
|
|
657
|
+
"reusableComponent": {
|
|
658
|
+
"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",
|
|
659
|
+
"hasVariants": false,
|
|
660
|
+
"hasSizes": true
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
"name": "interactions",
|
|
665
|
+
"entryPoint": "ng-primitives/interactions",
|
|
666
|
+
"exports": [
|
|
667
|
+
"NgpInteractionsConfig",
|
|
668
|
+
"provideInteractionsConfig",
|
|
669
|
+
"NgpFocusVisible",
|
|
670
|
+
"ngpFocusVisible",
|
|
671
|
+
"NgpFocus",
|
|
672
|
+
"ngpFocus",
|
|
673
|
+
"NgpHover",
|
|
674
|
+
"ngpHover",
|
|
675
|
+
"ngpInteractions",
|
|
676
|
+
"NgpMove",
|
|
677
|
+
"NgpMoveEvent",
|
|
678
|
+
"NgpPress",
|
|
679
|
+
"ngpPress"
|
|
680
|
+
],
|
|
681
|
+
"hasSecondaryEntryPoint": true,
|
|
682
|
+
"category": "utility",
|
|
683
|
+
"description": "interactions primitive component",
|
|
684
|
+
"accessibility": [],
|
|
685
|
+
"examples": []
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
"name": "listbox",
|
|
689
|
+
"entryPoint": "ng-primitives/listbox",
|
|
690
|
+
"exports": [
|
|
691
|
+
"NgpListboxHeader",
|
|
692
|
+
"NgpListboxOption",
|
|
693
|
+
"NgpListboxSection",
|
|
694
|
+
"NgpListboxTrigger",
|
|
695
|
+
"NgpListbox",
|
|
696
|
+
"injectListboxState",
|
|
697
|
+
"provideListboxState"
|
|
698
|
+
],
|
|
699
|
+
"hasSecondaryEntryPoint": true,
|
|
700
|
+
"category": "form",
|
|
701
|
+
"description": "A listbox presents a set of choices and lets users select one or multiple options.",
|
|
702
|
+
"accessibility": [
|
|
703
|
+
"ARIA Listbox"
|
|
704
|
+
],
|
|
705
|
+
"examples": [
|
|
706
|
+
{
|
|
707
|
+
"name": "example-0",
|
|
708
|
+
"code": "<div ngpListbox>\n <div ngpListboxOption ngpListboxOptionValue=\"1\">Option 1</div>\n <div ngpListboxOption ngpListboxOptionValue=\"2\">Option 2</div>\n <div ngpListboxOption ngpListboxOptionValue=\"3\">Option 3</div>\n</div>",
|
|
709
|
+
"description": "Usage"
|
|
710
|
+
}
|
|
711
|
+
],
|
|
712
|
+
"reusableComponent": {
|
|
713
|
+
"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",
|
|
714
|
+
"hasVariants": false,
|
|
715
|
+
"hasSizes": true
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"name": "menu",
|
|
720
|
+
"entryPoint": "ng-primitives/menu",
|
|
721
|
+
"exports": [
|
|
722
|
+
"injectOverlayContext as injectMenuContext",
|
|
723
|
+
"NgpMenuConfig",
|
|
724
|
+
"provideMenuConfig",
|
|
725
|
+
"NgpMenuItem",
|
|
726
|
+
"injectMenuItemState",
|
|
727
|
+
"NgpMenuItemProps",
|
|
728
|
+
"NgpMenuItemState",
|
|
729
|
+
"provideMenuItemState",
|
|
730
|
+
"NgpMenuTrigger",
|
|
731
|
+
"type NgpMenuPlacement",
|
|
732
|
+
"injectMenuTriggerState",
|
|
733
|
+
"NgpMenuTriggerProps",
|
|
734
|
+
"NgpMenuTriggerState",
|
|
735
|
+
"provideMenuTriggerState",
|
|
736
|
+
"NgpMenu",
|
|
737
|
+
"injectMenuState",
|
|
738
|
+
"NgpMenuProps",
|
|
739
|
+
"NgpMenuState",
|
|
740
|
+
"provideMenuState",
|
|
741
|
+
"NgpSubmenuTrigger",
|
|
742
|
+
"injectSubmenuTriggerState",
|
|
743
|
+
"NgpSubmenuTriggerProps",
|
|
744
|
+
"NgpSubmenuTriggerState",
|
|
745
|
+
"provideSubmenuTriggerState"
|
|
746
|
+
],
|
|
747
|
+
"hasSecondaryEntryPoint": true,
|
|
748
|
+
"category": "navigation",
|
|
749
|
+
"description": "A menu is a list of options or commands presented to the user in a dropdown list.",
|
|
750
|
+
"accessibility": [
|
|
751
|
+
"ARIA Authoring"
|
|
752
|
+
],
|
|
753
|
+
"examples": [
|
|
754
|
+
{
|
|
755
|
+
"name": "example-0",
|
|
756
|
+
"code": "<button [ngpMenuTrigger]=\"menu\" ngpButton></button>\n\n<ng-template #menu>\n <div ngpMenu>\n <button ngpMenuItem>Item 1</button>\n <button ngpMenuItem>Item 2</button>\n <button ngpMenuItem>Item 3</button>\n </div>\n</ng-template>",
|
|
757
|
+
"description": "Usage"
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
"name": "example-1",
|
|
761
|
+
"code": "<!-- Simple number offset -->\n<button [ngpMenuTrigger]=\"menu\" ngpMenuTriggerOffset=\"12\">Menu with 12px offset</button>\n\n<!-- Object offset for precise control -->\n<button\n [ngpMenuTrigger]=\"menu\"\n [ngpMenuTriggerOffset]=\"{mainAxis: 8, crossAxis: 4, alignmentAxis: 2}\"\n>\n Menu with custom offset\n</button>",
|
|
762
|
+
"description": "Custom Offset"
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
"name": "example-2",
|
|
766
|
+
"code": "import { provideMenuConfig } from 'ng-primitives/menu';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideMenuConfig({\n offset: 4,\n placement: 'top',\n flip: true,\n container: document.body,\n scrollBehavior: 'reposition',\n }),\n ],\n});",
|
|
767
|
+
"description": "Global Configuration"
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
"name": "example-3",
|
|
771
|
+
"code": "offset: {\n mainAxis: 8, // Distance between menu and trigger element\n crossAxis: 4, // Skidding along the alignment axis \n alignmentAxis: 2 // Same as crossAxis but for aligned placements\n }",
|
|
772
|
+
"description": "NgpMenuConfig"
|
|
773
|
+
}
|
|
774
|
+
],
|
|
775
|
+
"reusableComponent": {
|
|
776
|
+
"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",
|
|
777
|
+
"hasVariants": false,
|
|
778
|
+
"hasSizes": true
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"name": "meter",
|
|
783
|
+
"entryPoint": "ng-primitives/meter",
|
|
784
|
+
"exports": [
|
|
785
|
+
"NgpMeter",
|
|
786
|
+
"provideMeterState",
|
|
787
|
+
"injectMeterState",
|
|
788
|
+
"NgpMeterTrack",
|
|
789
|
+
"NgpMeterIndicator",
|
|
790
|
+
"NgpMeterValue",
|
|
791
|
+
"NgpMeterLabel"
|
|
792
|
+
],
|
|
793
|
+
"hasSecondaryEntryPoint": true,
|
|
794
|
+
"category": "feedback",
|
|
795
|
+
"description": "A meter is a visual representation of a value within a range.",
|
|
796
|
+
"accessibility": [],
|
|
797
|
+
"examples": [
|
|
798
|
+
{
|
|
799
|
+
"name": "example-0",
|
|
800
|
+
"code": "import {\n NgpMeter,\n NgpMeterValue,\n NgpMeterIndicator,\n NgpMeterTrack,\n NgpMeterLabel,\n} from 'ng-primitives/meter';",
|
|
801
|
+
"description": "Import"
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"name": "example-1",
|
|
805
|
+
"code": "<div ngpMeter>\n <span ngpMeterLabel>Label</span>\n <span ngpMeterValue>Value</span>\n <div ngpMeterTrack>\n <div ngpMeterIndicator></div>\n </div>\n</div>",
|
|
806
|
+
"description": "Usage"
|
|
807
|
+
}
|
|
808
|
+
],
|
|
809
|
+
"reusableComponent": {
|
|
810
|
+
"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",
|
|
811
|
+
"hasVariants": false,
|
|
812
|
+
"hasSizes": true
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
"name": "pagination",
|
|
817
|
+
"entryPoint": "ng-primitives/pagination",
|
|
818
|
+
"exports": [
|
|
819
|
+
"NgpPaginationButton",
|
|
820
|
+
"NgpPaginationFirst",
|
|
821
|
+
"NgpPaginationLast",
|
|
822
|
+
"NgpPaginationNext",
|
|
823
|
+
"NgpPaginationPrevious",
|
|
824
|
+
"NgpPagination",
|
|
825
|
+
"injectPaginationState",
|
|
826
|
+
"providePaginationState"
|
|
827
|
+
],
|
|
828
|
+
"hasSecondaryEntryPoint": true,
|
|
829
|
+
"category": "navigation",
|
|
830
|
+
"description": "The Pagination primitives provide a set of directives to create a pagination control. The pagination control is used to navigate through a set of data that is split into multiple pages.",
|
|
831
|
+
"accessibility": [],
|
|
832
|
+
"examples": [
|
|
833
|
+
{
|
|
834
|
+
"name": "example-0",
|
|
835
|
+
"code": "import {\n NgpPagination,\n NgpPaginationButton,\n NgpPaginationFirst,\n NgpPaginationNext,\n NgpPaginationPrevious,\n NgpPaginationLast,\n} from 'ng-primitives/pagination';",
|
|
836
|
+
"description": "Import"
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
"name": "example-1",
|
|
840
|
+
"code": "<nav\n [(ngpPaginationPage)]=\"page\"\n ngpPagination\n ngpPaginationPageCount=\"5\"\n aria-label=\"Pagination Navigation\"\n>\n <ul>\n <li>\n <a ngpPaginationFirst aria-label=\"First Page\">\n <ng-icon name=\"heroChevronDoubleLeft\" />\n </a>\n </li>\n\n <li>\n <a ngpPaginationPrevious aria-label=\"Previous Page\">\n <ng-icon name=\"heroChevronLeft\" />\n </a>\n </li>\n\n <li>\n <a ngpPaginationButton ngpPaginationButtonPage=\"1\" aria-label=\"Page 1\">1</a>\n </li>\n <li>\n <a ngpPaginationButton ngpPaginationButtonPage=\"2\" aria-label=\"Page 2\">2</a>\n </li>\n <li>\n <a ngpPaginationButton ngpPaginationButtonPage=\"3\" aria-label=\"Page 3\">3</a>\n </li>\n <li>\n <a ngpPaginationButton ngpPaginationButtonPage=\"4\" aria-label=\"Page 4\">4</a>\n </li>\n <li>\n <a ngpPaginationButton ngpPaginationButtonPage=\"5\" aria-label=\"Page 5\">5</a>\n </li>\n\n <li>\n <a ngpPaginationNext aria-label=\"Next Page\">\n <ng-icon name=\"heroChevronRight\" />\n </a>\n </li>\n\n <li>\n <a ngpPaginationLast aria-label=\"Last Page\">\n <ng-icon name=\"heroChevronDoubleRight\" />\n </a>\n </li>\n </ul>\n</nav>",
|
|
841
|
+
"description": "Usage"
|
|
842
|
+
}
|
|
843
|
+
],
|
|
844
|
+
"reusableComponent": {
|
|
845
|
+
"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",
|
|
846
|
+
"hasVariants": false,
|
|
847
|
+
"hasSizes": true
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
"name": "popover",
|
|
852
|
+
"entryPoint": "ng-primitives/popover",
|
|
853
|
+
"exports": [
|
|
854
|
+
"injectOverlayContext as injectPopoverContext",
|
|
855
|
+
"NgpPopoverConfig",
|
|
856
|
+
"providePopoverConfig",
|
|
857
|
+
"NgpPopoverArrow",
|
|
858
|
+
"NgpPopoverTrigger",
|
|
859
|
+
"type NgpPopoverPlacement",
|
|
860
|
+
"injectPopoverTriggerState",
|
|
861
|
+
"providePopoverTriggerState",
|
|
862
|
+
"NgpPopover"
|
|
863
|
+
],
|
|
864
|
+
"hasSecondaryEntryPoint": true,
|
|
865
|
+
"category": "feedback",
|
|
866
|
+
"description": "Display arbitrary content inside floating panels.",
|
|
867
|
+
"accessibility": [],
|
|
868
|
+
"examples": [
|
|
869
|
+
{
|
|
870
|
+
"name": "example-0",
|
|
871
|
+
"code": "<button [ngpPopoverTrigger]=\"popover\" (ngpPopoverTriggerOpenChange)=\"onPopoverStateChange($event)\">\n Click me\n</button>\n\n<ng-template #popover>\n <div ngpPopover>Popover content</div>\n</ng-template>",
|
|
872
|
+
"description": "Usage"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"name": "example-1",
|
|
876
|
+
"code": "<!-- Simple number offset -->\n<button [ngpPopoverTrigger]=\"popover\" ngpPopoverTriggerOffset=\"12\">Popover with 12px offset</button>\n\n<!-- Object offset for precise control -->\n<button\n [ngpPopoverTrigger]=\"popover\"\n [ngpPopoverTriggerOffset]=\"{mainAxis: 8, crossAxis: 4, alignmentAxis: 2}\"\n>\n Popover with custom offset\n</button>",
|
|
877
|
+
"description": "Custom Offset"
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"name": "example-2",
|
|
881
|
+
"code": "import { providePopoverConfig } from 'ng-primitives/popover';\n\nbootstrapApplication(AppComponent, {\n providers: [\n providePopoverConfig({\n offset: 4,\n placement: 'top',\n showDelay: 0,\n hideDelay: 0,\n flip: true,\n container: document.body,\n closeOnOutsideClick: true,\n scrollBehavior: 'reposition',\n }),\n ],\n});",
|
|
882
|
+
"description": "Global Configuration"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
"name": "example-3",
|
|
886
|
+
"code": "offset: {\n mainAxis: 8, // Distance between popover and trigger element\n crossAxis: 4, // Skidding along the alignment axis \n alignmentAxis: 2 // Same as crossAxis but for aligned placements\n }",
|
|
887
|
+
"description": "NgpPopoverConfig"
|
|
888
|
+
}
|
|
889
|
+
],
|
|
890
|
+
"reusableComponent": {
|
|
891
|
+
"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",
|
|
892
|
+
"hasVariants": false,
|
|
893
|
+
"hasSizes": false
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
"name": "portal",
|
|
898
|
+
"entryPoint": "ng-primitives/portal",
|
|
899
|
+
"exports": [
|
|
900
|
+
"createOverlay",
|
|
901
|
+
"injectOverlay",
|
|
902
|
+
"NgpOverlay",
|
|
903
|
+
"NgpOverlayConfig",
|
|
904
|
+
"NgpOverlayContent",
|
|
905
|
+
"NgpOverlayTemplateContext",
|
|
906
|
+
"setupOverlayArrow",
|
|
907
|
+
"injectOverlayContext",
|
|
908
|
+
"provideOverlayContext",
|
|
909
|
+
"createPortal",
|
|
910
|
+
"NgpComponentPortal",
|
|
911
|
+
"NgpPortal",
|
|
912
|
+
"NgpTemplatePortal",
|
|
913
|
+
"BlockScrollStrategy",
|
|
914
|
+
"NoopScrollStrategy",
|
|
915
|
+
"ScrollStrategy",
|
|
916
|
+
"NgpOffset",
|
|
917
|
+
"NgpOffsetInput",
|
|
918
|
+
"NgpOffsetOptions",
|
|
919
|
+
"coerceOffset"
|
|
920
|
+
],
|
|
921
|
+
"hasSecondaryEntryPoint": true,
|
|
922
|
+
"category": "layout",
|
|
923
|
+
"description": "portal primitive component",
|
|
924
|
+
"accessibility": [],
|
|
925
|
+
"examples": []
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
"name": "progress",
|
|
929
|
+
"entryPoint": "ng-primitives/progress",
|
|
930
|
+
"exports": [
|
|
931
|
+
"NgpProgressIndicator",
|
|
932
|
+
"NgpProgressLabel",
|
|
933
|
+
"NgpProgressTrack",
|
|
934
|
+
"NgpProgressValue",
|
|
935
|
+
"NgpProgress",
|
|
936
|
+
"NgpProgressValueTextFn",
|
|
937
|
+
"injectProgressState",
|
|
938
|
+
"provideProgressState"
|
|
939
|
+
],
|
|
940
|
+
"hasSecondaryEntryPoint": true,
|
|
941
|
+
"category": "feedback",
|
|
942
|
+
"description": "Display an indicator representing the progress of a task.",
|
|
943
|
+
"accessibility": [
|
|
944
|
+
"ARIA Progressbar"
|
|
945
|
+
],
|
|
946
|
+
"examples": [
|
|
947
|
+
{
|
|
948
|
+
"name": "example-0",
|
|
949
|
+
"code": "import {\n NgpProgress,\n NgpProgressTrack,\n NgpProgressLabel,\n NgpProgressValue,\n NgpProgressIndicator,\n} from 'ng-primitives/progress';",
|
|
950
|
+
"description": "Import"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
"name": "example-1",
|
|
954
|
+
"code": "<div ngpProgress [ngpProgressValue]=\"percentage\">\n <label ngpProgressLabel></label>\n <span ngpProgressValue></span>\n\n <div ngpProgressTrack>\n <div ngpProgressIndicator></div>\n </div>\n</div>",
|
|
955
|
+
"description": "Usage"
|
|
956
|
+
}
|
|
957
|
+
],
|
|
958
|
+
"reusableComponent": {
|
|
959
|
+
"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",
|
|
960
|
+
"hasVariants": false,
|
|
961
|
+
"hasSizes": true
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
"name": "radio",
|
|
966
|
+
"entryPoint": "ng-primitives/radio",
|
|
967
|
+
"exports": [
|
|
968
|
+
"NgpRadioGroup",
|
|
969
|
+
"injectRadioGroupState",
|
|
970
|
+
"provideRadioGroupState",
|
|
971
|
+
"NgpRadioIndicator",
|
|
972
|
+
"NgpRadioItem",
|
|
973
|
+
"injectRadioItemState",
|
|
974
|
+
"provideRadioItemState"
|
|
975
|
+
],
|
|
976
|
+
"hasSecondaryEntryPoint": true,
|
|
977
|
+
"category": "form",
|
|
978
|
+
"description": "Selection within a group.",
|
|
979
|
+
"accessibility": [
|
|
980
|
+
"ARIA design"
|
|
981
|
+
],
|
|
982
|
+
"examples": [
|
|
983
|
+
{
|
|
984
|
+
"name": "example-0",
|
|
985
|
+
"code": "<div ngpRadioGroup [(ngpRadioGroupValue)]=\"value\">\n <button ngpRadioItem ngpRadioItemValue=\"Option 1\">\n <ng-icon ngpRadioIndicator name=\"dot\" />\n Option 1\n </button>\n\n <button ngpRadioItem ngpRadioItemValue=\"Option 2\">\n <ng-icon ngpRadioIndicator name=\"dot\" />\n Option 2\n </button>\n\n <button ngpRadioItem ngpRadioItemValue=\"Option 3\">\n <ng-icon ngpRadioIndicator name=\"dot\" />\n Option 3\n </button>\n</div>",
|
|
986
|
+
"description": "Usage"
|
|
987
|
+
}
|
|
988
|
+
],
|
|
989
|
+
"reusableComponent": {
|
|
990
|
+
"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",
|
|
991
|
+
"hasVariants": false,
|
|
992
|
+
"hasSizes": false
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
"name": "resize",
|
|
997
|
+
"entryPoint": "ng-primitives/resize",
|
|
998
|
+
"exports": [
|
|
999
|
+
"Dimensions",
|
|
1000
|
+
"NgpResize"
|
|
1001
|
+
],
|
|
1002
|
+
"hasSecondaryEntryPoint": true,
|
|
1003
|
+
"category": "utility",
|
|
1004
|
+
"description": "Perform actions on element resize.",
|
|
1005
|
+
"accessibility": [],
|
|
1006
|
+
"examples": [
|
|
1007
|
+
{
|
|
1008
|
+
"name": "example-0",
|
|
1009
|
+
"code": "<div (ngpResize)=\"onResize($event)\"></div>",
|
|
1010
|
+
"description": "Usage"
|
|
1011
|
+
}
|
|
1012
|
+
]
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
"name": "roving-focus",
|
|
1016
|
+
"entryPoint": "ng-primitives/roving-focus",
|
|
1017
|
+
"exports": [
|
|
1018
|
+
"NgpRovingFocusGroup",
|
|
1019
|
+
"injectRovingFocusGroupState",
|
|
1020
|
+
"ngpRovingFocusGroup",
|
|
1021
|
+
"NgpRovingFocusGroupProps",
|
|
1022
|
+
"NgpRovingFocusGroupState",
|
|
1023
|
+
"NgpRovingFocusGroupStateToken",
|
|
1024
|
+
"provideRovingFocusGroupState",
|
|
1025
|
+
"injectRovingFocusGroup",
|
|
1026
|
+
"NgpRovingFocusGroupOptions",
|
|
1027
|
+
"NgpRovingFocusGroupToken",
|
|
1028
|
+
"provideRovingFocusGroup",
|
|
1029
|
+
"NgpRovingFocusItem",
|
|
1030
|
+
"injectRovingFocusItemState",
|
|
1031
|
+
"ngpRovingFocusItem",
|
|
1032
|
+
"NgpRovingFocusItemProps",
|
|
1033
|
+
"NgpRovingFocusItemState",
|
|
1034
|
+
"NgpRovingFocusItemStateToken",
|
|
1035
|
+
"provideRovingFocusItemState"
|
|
1036
|
+
],
|
|
1037
|
+
"hasSecondaryEntryPoint": true,
|
|
1038
|
+
"category": "utility",
|
|
1039
|
+
"description": "Handle focus for a group of elements.",
|
|
1040
|
+
"accessibility": [
|
|
1041
|
+
"ARIA Keyboard"
|
|
1042
|
+
],
|
|
1043
|
+
"examples": [
|
|
1044
|
+
{
|
|
1045
|
+
"name": "example-0",
|
|
1046
|
+
"code": "<div ngpRovingFocusGroup>\n <button ngpRovingFocusItem>Item 1</button>\n <button ngpRovingFocusItem>Item 2</button>\n <button ngpRovingFocusItem>Item 3</button>\n</div>",
|
|
1047
|
+
"description": "Usage"
|
|
1048
|
+
}
|
|
1049
|
+
]
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
"name": "search",
|
|
1053
|
+
"entryPoint": "ng-primitives/search",
|
|
1054
|
+
"exports": [
|
|
1055
|
+
"NgpSearchClear",
|
|
1056
|
+
"NgpSearch",
|
|
1057
|
+
"provideSearchState",
|
|
1058
|
+
"injectSearchState"
|
|
1059
|
+
],
|
|
1060
|
+
"hasSecondaryEntryPoint": true,
|
|
1061
|
+
"category": "form",
|
|
1062
|
+
"description": "The search primitive is a form control that allows users to enter a search query and clear the input.",
|
|
1063
|
+
"accessibility": [
|
|
1064
|
+
"ARIA design"
|
|
1065
|
+
],
|
|
1066
|
+
"examples": [
|
|
1067
|
+
{
|
|
1068
|
+
"name": "example-0",
|
|
1069
|
+
"code": "<div ngpFormField>\n <label ngpLabel>Label</label>\n <div ngpSearch>\n <input ngpInput type=\"search\" />\n <button ngpSearchClear ngpButton aria-label=\"Clear search\">Clear</button>\n </div>\n</div>",
|
|
1070
|
+
"description": "Usage"
|
|
1071
|
+
}
|
|
1072
|
+
],
|
|
1073
|
+
"reusableComponent": {
|
|
1074
|
+
"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",
|
|
1075
|
+
"hasVariants": false,
|
|
1076
|
+
"hasSizes": true
|
|
1077
|
+
}
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
"name": "select",
|
|
1081
|
+
"entryPoint": "ng-primitives/select",
|
|
1082
|
+
"exports": [
|
|
1083
|
+
"NgpNativeSelect",
|
|
1084
|
+
"injectNativeSelectState",
|
|
1085
|
+
"provideNativeSelectState",
|
|
1086
|
+
"NgpSelectDropdown",
|
|
1087
|
+
"NgpSelectOption",
|
|
1088
|
+
"NgpSelectPortal",
|
|
1089
|
+
"NgpSelect",
|
|
1090
|
+
"injectSelectState",
|
|
1091
|
+
"provideSelectState",
|
|
1092
|
+
"provideSelectConfig",
|
|
1093
|
+
"injectSelectConfig"
|
|
1094
|
+
],
|
|
1095
|
+
"hasSecondaryEntryPoint": true,
|
|
1096
|
+
"category": "form",
|
|
1097
|
+
"description": "A select is a form control that allows users to select options from a list.",
|
|
1098
|
+
"accessibility": [],
|
|
1099
|
+
"examples": [
|
|
1100
|
+
{
|
|
1101
|
+
"name": "example-0",
|
|
1102
|
+
"code": "import {\n NgpSelect,\n NgpSelectDropdown,\n NgpSelectPortal,\n NgpSelectOption,\n} from 'ng-primitives/select';",
|
|
1103
|
+
"description": "Import"
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
"name": "example-1",
|
|
1107
|
+
"code": "<div ngpSelect>\n <div *ngpSelectPortal ngpSelectDropdown>\n <div ngpSelectOptionValue=\"option-1\" ngpSelectOption>One</div>\n <div ngpSelectOptionValue=\"option-2\" ngpSelectOption>Two</div>\n <div ngpSelectOptionValue=\"option-3\" ngpSelectOption>Three</div>\n </div>\n</div>",
|
|
1108
|
+
"description": "Usage"
|
|
1109
|
+
}
|
|
1110
|
+
],
|
|
1111
|
+
"reusableComponent": {
|
|
1112
|
+
"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",
|
|
1113
|
+
"hasVariants": false,
|
|
1114
|
+
"hasSizes": true
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
"name": "separator",
|
|
1119
|
+
"entryPoint": "ng-primitives/separator",
|
|
1120
|
+
"exports": [
|
|
1121
|
+
"NgpSeparatorConfig",
|
|
1122
|
+
"provideSeparatorConfig",
|
|
1123
|
+
"NgpSeparator"
|
|
1124
|
+
],
|
|
1125
|
+
"hasSecondaryEntryPoint": true,
|
|
1126
|
+
"category": "layout",
|
|
1127
|
+
"description": "The separator primitive allow you to semantically separate content either horizontally or vertically.",
|
|
1128
|
+
"accessibility": [
|
|
1129
|
+
"ARIA Separator"
|
|
1130
|
+
],
|
|
1131
|
+
"examples": [
|
|
1132
|
+
{
|
|
1133
|
+
"name": "example-0",
|
|
1134
|
+
"code": "<div ngpSeparator></div>",
|
|
1135
|
+
"description": "Usage"
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
"name": "example-1",
|
|
1139
|
+
"code": "import { provideSeparatorConfig } from 'ng-primitives/tabs';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideSeparatorConfig({\n orientation: 'horizontal',\n }),\n ],\n});",
|
|
1140
|
+
"description": "Global Configuration"
|
|
1141
|
+
}
|
|
1142
|
+
],
|
|
1143
|
+
"reusableComponent": {
|
|
1144
|
+
"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",
|
|
1145
|
+
"hasVariants": false,
|
|
1146
|
+
"hasSizes": false
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
"name": "slider",
|
|
1151
|
+
"entryPoint": "ng-primitives/slider",
|
|
1152
|
+
"exports": [
|
|
1153
|
+
"NgpSliderRange",
|
|
1154
|
+
"injectSliderRangeState",
|
|
1155
|
+
"ngpSliderRange",
|
|
1156
|
+
"NgpSliderRangeProps",
|
|
1157
|
+
"NgpSliderRangeState",
|
|
1158
|
+
"provideSliderRangeState",
|
|
1159
|
+
"NgpSliderThumb",
|
|
1160
|
+
"injectSliderThumbState",
|
|
1161
|
+
"ngpSliderThumb",
|
|
1162
|
+
"NgpSliderThumbProps",
|
|
1163
|
+
"NgpSliderThumbState",
|
|
1164
|
+
"provideSliderThumbState",
|
|
1165
|
+
"NgpSliderTrack",
|
|
1166
|
+
"injectSliderTrackState",
|
|
1167
|
+
"ngpSliderTrack",
|
|
1168
|
+
"NgpSliderTrackProps",
|
|
1169
|
+
"NgpSliderTrackState",
|
|
1170
|
+
"provideSliderTrackState",
|
|
1171
|
+
"NgpSlider",
|
|
1172
|
+
"injectSliderState",
|
|
1173
|
+
"ngpSlider",
|
|
1174
|
+
"NgpSliderProps",
|
|
1175
|
+
"NgpSliderState",
|
|
1176
|
+
"provideSliderState",
|
|
1177
|
+
"NgpRangeSliderRange",
|
|
1178
|
+
"NgpRangeSliderThumb",
|
|
1179
|
+
"NgpRangeSliderTrack",
|
|
1180
|
+
"NgpRangeSlider",
|
|
1181
|
+
"injectRangeSliderState",
|
|
1182
|
+
"provideRangeSliderState"
|
|
1183
|
+
],
|
|
1184
|
+
"hasSecondaryEntryPoint": true,
|
|
1185
|
+
"category": "form",
|
|
1186
|
+
"description": "Select a value within a range.",
|
|
1187
|
+
"accessibility": [
|
|
1188
|
+
"ARIA design"
|
|
1189
|
+
],
|
|
1190
|
+
"examples": [
|
|
1191
|
+
{
|
|
1192
|
+
"name": "example-0",
|
|
1193
|
+
"code": "<div ngpSlider>\n <div ngpSliderTrack>\n <div ngpSliderRange></div>\n </div>\n <div ngpSliderThumb></div>\n</div>",
|
|
1194
|
+
"description": "Usage"
|
|
1195
|
+
}
|
|
1196
|
+
],
|
|
1197
|
+
"reusableComponent": {
|
|
1198
|
+
"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",
|
|
1199
|
+
"hasVariants": false,
|
|
1200
|
+
"hasSizes": false
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
"name": "state",
|
|
1205
|
+
"entryPoint": "ng-primitives/state",
|
|
1206
|
+
"exports": [],
|
|
1207
|
+
"hasSecondaryEntryPoint": true,
|
|
1208
|
+
"category": "utility",
|
|
1209
|
+
"description": "state primitive component",
|
|
1210
|
+
"accessibility": [],
|
|
1211
|
+
"examples": []
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
"name": "switch",
|
|
1215
|
+
"entryPoint": "ng-primitives/switch",
|
|
1216
|
+
"exports": [
|
|
1217
|
+
"NgpSwitchThumb",
|
|
1218
|
+
"injectSwitchThumbState",
|
|
1219
|
+
"ngpSwitchThumb",
|
|
1220
|
+
"NgpSwitchThumbProps",
|
|
1221
|
+
"NgpSwitchThumbState",
|
|
1222
|
+
"provideSwitchThumbState",
|
|
1223
|
+
"NgpSwitch",
|
|
1224
|
+
"injectSwitchState",
|
|
1225
|
+
"ngpSwitch",
|
|
1226
|
+
"NgpSwitchProps",
|
|
1227
|
+
"NgpSwitchState",
|
|
1228
|
+
"provideSwitchState"
|
|
1229
|
+
],
|
|
1230
|
+
"hasSecondaryEntryPoint": true,
|
|
1231
|
+
"category": "form",
|
|
1232
|
+
"description": "Perform state toggling.",
|
|
1233
|
+
"accessibility": [
|
|
1234
|
+
"ARIA switch"
|
|
1235
|
+
],
|
|
1236
|
+
"examples": [
|
|
1237
|
+
{
|
|
1238
|
+
"name": "example-0",
|
|
1239
|
+
"code": "<button ngpSwitch [(ngpSwitchChecked)]=\"checked\">\n <span ngpSwitchThumb></span>\n</button>",
|
|
1240
|
+
"description": "Usage"
|
|
1241
|
+
}
|
|
1242
|
+
],
|
|
1243
|
+
"reusableComponent": {
|
|
1244
|
+
"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",
|
|
1245
|
+
"hasVariants": false,
|
|
1246
|
+
"hasSizes": false
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
"name": "tabs",
|
|
1251
|
+
"entryPoint": "ng-primitives/tabs",
|
|
1252
|
+
"exports": [
|
|
1253
|
+
"NgpTabsConfig",
|
|
1254
|
+
"provideTabsConfig",
|
|
1255
|
+
"NgpTabButton",
|
|
1256
|
+
"injectTabButtonState",
|
|
1257
|
+
"ngpTabButton",
|
|
1258
|
+
"NgpTabButtonProps",
|
|
1259
|
+
"NgpTabButtonState",
|
|
1260
|
+
"NgpTabButtonStateToken",
|
|
1261
|
+
"provideTabButtonState",
|
|
1262
|
+
"NgpTabList",
|
|
1263
|
+
"injectTabListState",
|
|
1264
|
+
"ngpTabList",
|
|
1265
|
+
"NgpTabListProps",
|
|
1266
|
+
"NgpTabListState",
|
|
1267
|
+
"NgpTabListStateToken",
|
|
1268
|
+
"provideTabListState",
|
|
1269
|
+
"NgpTabPanel",
|
|
1270
|
+
"injectTabPanelState",
|
|
1271
|
+
"ngpTabPanel",
|
|
1272
|
+
"NgpTabPanelProps",
|
|
1273
|
+
"NgpTabPanelState",
|
|
1274
|
+
"NgpTabPanelStateToken",
|
|
1275
|
+
"provideTabPanelState",
|
|
1276
|
+
"NgpTabset",
|
|
1277
|
+
"injectTabsetState",
|
|
1278
|
+
"ngpTabset",
|
|
1279
|
+
"NgpTabsetProps",
|
|
1280
|
+
"NgpTabsetState",
|
|
1281
|
+
"NgpTabsetStateToken",
|
|
1282
|
+
"provideTabsetState"
|
|
1283
|
+
],
|
|
1284
|
+
"hasSecondaryEntryPoint": true,
|
|
1285
|
+
"category": "navigation",
|
|
1286
|
+
"description": "Dynamically show and hide content based on an active tab.",
|
|
1287
|
+
"accessibility": [
|
|
1288
|
+
"ARIA design"
|
|
1289
|
+
],
|
|
1290
|
+
"examples": [
|
|
1291
|
+
{
|
|
1292
|
+
"name": "example-0",
|
|
1293
|
+
"code": "<div ngpTabset>\n <div ngpTabList>\n <button ngpTabButton ngpTabButtonValue=\"tab1\">Tab 1</button>\n <button ngpTabButton ngpTabButtonValue=\"tab2\">Tab 2</button>\n <button ngpTabButton ngpTabButtonValue=\"tab3\">Tab 3</button>\n </div>\n <div ngpTabPanel ngpTabPanelValue=\"tab1\">Tab 1 content</div>\n <div ngpTabPanel ngpTabPanelValue=\"tab2\">Tab 2 content</div>\n <div ngpTabPanel ngpTabPanelValue=\"tab3\">Tab 3 content</div>\n</div>",
|
|
1294
|
+
"description": "Usage"
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
"name": "example-1",
|
|
1298
|
+
"code": "import { provideTabsConfig } from 'ng-primitives/tabs';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideTabsConfig({\n orientation: 'horizontal',\n activateOnFocus: false,\n wrap: false,\n }),\n ],\n});",
|
|
1299
|
+
"description": "Global Configuration"
|
|
1300
|
+
}
|
|
1301
|
+
],
|
|
1302
|
+
"reusableComponent": {
|
|
1303
|
+
"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",
|
|
1304
|
+
"hasVariants": false,
|
|
1305
|
+
"hasSizes": false
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
"name": "textarea",
|
|
1310
|
+
"entryPoint": "ng-primitives/textarea",
|
|
1311
|
+
"exports": [
|
|
1312
|
+
"NgpTextarea",
|
|
1313
|
+
"injectTextareaState",
|
|
1314
|
+
"ngpTextarea",
|
|
1315
|
+
"NgpTextareaProps",
|
|
1316
|
+
"NgpTextareaState",
|
|
1317
|
+
"provideTextareaState"
|
|
1318
|
+
],
|
|
1319
|
+
"hasSecondaryEntryPoint": true,
|
|
1320
|
+
"category": "form",
|
|
1321
|
+
"description": "The textarea primitive can be used to enhance the accessibility of a textarea element and provide consistent interaction handling for hover, focus and press states.",
|
|
1322
|
+
"accessibility": [],
|
|
1323
|
+
"examples": [
|
|
1324
|
+
{
|
|
1325
|
+
"name": "example-0",
|
|
1326
|
+
"code": "<textarea ngpTextarea></textarea>",
|
|
1327
|
+
"description": "Usage"
|
|
1328
|
+
}
|
|
1329
|
+
],
|
|
1330
|
+
"reusableComponent": {
|
|
1331
|
+
"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",
|
|
1332
|
+
"hasVariants": false,
|
|
1333
|
+
"hasSizes": false
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
"name": "toast",
|
|
1338
|
+
"entryPoint": "ng-primitives/toast",
|
|
1339
|
+
"exports": [
|
|
1340
|
+
"NgpToastConfig",
|
|
1341
|
+
"provideToastConfig",
|
|
1342
|
+
"NgpToast",
|
|
1343
|
+
"injectToastContext",
|
|
1344
|
+
"NgpToastManager"
|
|
1345
|
+
],
|
|
1346
|
+
"hasSecondaryEntryPoint": true,
|
|
1347
|
+
"category": "feedback",
|
|
1348
|
+
"description": "A toast is a non-modal, unobtrusive window element used to display brief, auto-expiring messages to the user.",
|
|
1349
|
+
"accessibility": [
|
|
1350
|
+
"aria live"
|
|
1351
|
+
],
|
|
1352
|
+
"examples": [
|
|
1353
|
+
{
|
|
1354
|
+
"name": "example-0",
|
|
1355
|
+
"code": "<ng-template #toast>\n <div ngpToast>...</div>\n</ng-template>",
|
|
1356
|
+
"description": "Usage"
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
"name": "example-1",
|
|
1360
|
+
"code": "import { NgpToastManager } from 'ng-primitives/toast';\n\nexport class MyComponent {\n private readonly toastManager = inject(NgpToastManager);\n\n showToast(): void {\n this.toastManager.show(toastTemplate);\n // or\n this.toastManager.show(MyToastComponent);\n }\n}",
|
|
1361
|
+
"description": "Usage"
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
"name": "example-2",
|
|
1365
|
+
"code": "import { provideToastConfig } from 'ng-primitives/toast';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideToastConfig({\n placement: 'top-end',\n duration: 5000,\n offsetTop: 16,\n offsetBottom: 16,\n offsetLeft: 16,\n offsetRight: 16,\n dismissible: true,\n maxToasts: 3,\n zIndex: 9999999,\n swipeDirections: ['left', 'right'],\n ariaLive: 'assertive',\n gap: 16,\n }),\n ],\n});",
|
|
1366
|
+
"description": "Global Configuration"
|
|
1367
|
+
}
|
|
1368
|
+
],
|
|
1369
|
+
"reusableComponent": {
|
|
1370
|
+
"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",
|
|
1371
|
+
"hasVariants": false,
|
|
1372
|
+
"hasSizes": true
|
|
1373
|
+
}
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
"name": "toggle",
|
|
1377
|
+
"entryPoint": "ng-primitives/toggle",
|
|
1378
|
+
"exports": [
|
|
1379
|
+
"NgpToggle",
|
|
1380
|
+
"injectToggleState",
|
|
1381
|
+
"ngpToggle",
|
|
1382
|
+
"NgpToggleProps",
|
|
1383
|
+
"NgpToggleState",
|
|
1384
|
+
"provideToggleState"
|
|
1385
|
+
],
|
|
1386
|
+
"hasSecondaryEntryPoint": true,
|
|
1387
|
+
"category": "form",
|
|
1388
|
+
"description": "Toggle a button on and off.",
|
|
1389
|
+
"accessibility": [],
|
|
1390
|
+
"examples": [
|
|
1391
|
+
{
|
|
1392
|
+
"name": "example-0",
|
|
1393
|
+
"code": "<button ngpToggle [(ngpToggleSelected)]=\"selected\">Toggle</button>",
|
|
1394
|
+
"description": "Usage"
|
|
1395
|
+
}
|
|
1396
|
+
],
|
|
1397
|
+
"reusableComponent": {
|
|
1398
|
+
"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",
|
|
1399
|
+
"hasVariants": false,
|
|
1400
|
+
"hasSizes": false
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
"name": "toggle-group",
|
|
1405
|
+
"entryPoint": "ng-primitives/toggle-group",
|
|
1406
|
+
"exports": [
|
|
1407
|
+
"NgpToggleGroupConfig",
|
|
1408
|
+
"provideToggleGroupConfig",
|
|
1409
|
+
"NgpToggleGroupItem",
|
|
1410
|
+
"injectToggleGroupItemState",
|
|
1411
|
+
"provideToggleGroupItemState",
|
|
1412
|
+
"NgpToggleGroup",
|
|
1413
|
+
"injectToggleGroupState",
|
|
1414
|
+
"provideToggleGroupState"
|
|
1415
|
+
],
|
|
1416
|
+
"hasSecondaryEntryPoint": true,
|
|
1417
|
+
"category": "form",
|
|
1418
|
+
"description": "The toggle group primitive is a collection of toggle buttons that can be used to select one or more options.",
|
|
1419
|
+
"accessibility": [],
|
|
1420
|
+
"examples": [
|
|
1421
|
+
{
|
|
1422
|
+
"name": "example-0",
|
|
1423
|
+
"code": "<div ngpToggleGroup>\n <button ngpToggleGroupItem ngpToggleGroupItemValue=\"1\">Option 1</button>\n <button ngpToggleGroupItem ngpToggleGroupItemValue=\"2\">Option 2</button>\n <button ngpToggleGroupItem ngpToggleGroupItemValue=\"3\">Option 3</button>\n</div>",
|
|
1424
|
+
"description": "Usage"
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
"name": "example-1",
|
|
1428
|
+
"code": "import { provideToggleGroupConfig } from 'ng-primitives/toggle-group';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideToggleGroupConfig({\n orientation: 'vertical',\n type: 'multiple',\n }),\n ],\n});",
|
|
1429
|
+
"description": "Global Configuration"
|
|
1430
|
+
}
|
|
1431
|
+
],
|
|
1432
|
+
"reusableComponent": {
|
|
1433
|
+
"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",
|
|
1434
|
+
"hasVariants": false,
|
|
1435
|
+
"hasSizes": true
|
|
1436
|
+
}
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
"name": "toolbar",
|
|
1440
|
+
"entryPoint": "ng-primitives/toolbar",
|
|
1441
|
+
"exports": [
|
|
1442
|
+
"NgpToolbar",
|
|
1443
|
+
"provideToolbarState",
|
|
1444
|
+
"injectToolbarState"
|
|
1445
|
+
],
|
|
1446
|
+
"hasSecondaryEntryPoint": true,
|
|
1447
|
+
"category": "navigation",
|
|
1448
|
+
"description": "The Toolbar primitive is a container for grouping related controls.",
|
|
1449
|
+
"accessibility": [
|
|
1450
|
+
"ARIA Toolbar"
|
|
1451
|
+
],
|
|
1452
|
+
"examples": [
|
|
1453
|
+
{
|
|
1454
|
+
"name": "example-0",
|
|
1455
|
+
"code": "<div ngpToolbar>\n <!-- Toolbar content -->\n</div>",
|
|
1456
|
+
"description": "Usage"
|
|
1457
|
+
}
|
|
1458
|
+
],
|
|
1459
|
+
"reusableComponent": {
|
|
1460
|
+
"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",
|
|
1461
|
+
"hasVariants": false,
|
|
1462
|
+
"hasSizes": false
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
"name": "tooltip",
|
|
1467
|
+
"entryPoint": "ng-primitives/tooltip",
|
|
1468
|
+
"exports": [
|
|
1469
|
+
"injectOverlayContext as injectTooltipContext",
|
|
1470
|
+
"NgpTooltipConfig",
|
|
1471
|
+
"provideTooltipConfig",
|
|
1472
|
+
"NgpTooltipArrow",
|
|
1473
|
+
"NgpTooltipTrigger",
|
|
1474
|
+
"type NgpTooltipPlacement",
|
|
1475
|
+
"injectTooltipTriggerState",
|
|
1476
|
+
"provideTooltipTriggerState",
|
|
1477
|
+
"NgpTooltip"
|
|
1478
|
+
],
|
|
1479
|
+
"hasSecondaryEntryPoint": true,
|
|
1480
|
+
"category": "feedback",
|
|
1481
|
+
"description": "Display additional information on hover.",
|
|
1482
|
+
"accessibility": [],
|
|
1483
|
+
"examples": [
|
|
1484
|
+
{
|
|
1485
|
+
"name": "example-0",
|
|
1486
|
+
"code": "<button [ngpTooltipTrigger]=\"tooltip\" ngpButton>Hover me</button>\n\n<ng-template #tooltip>\n <div ngpTooltip>Tooltip content</div>\n</ng-template>",
|
|
1487
|
+
"description": "Usage"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
"name": "example-1",
|
|
1491
|
+
"code": "<!-- Simple number offset -->\n<button [ngpTooltipTrigger]=\"tooltip\" ngpTooltipTriggerOffset=\"12\">Tooltip with 12px offset</button>\n\n<!-- Object offset for precise control -->\n<button\n [ngpTooltipTrigger]=\"tooltip\"\n [ngpTooltipTriggerOffset]=\"{mainAxis: 8, crossAxis: 4, alignmentAxis: 2}\"\n>\n Tooltip with custom offset\n</button>",
|
|
1492
|
+
"description": "Custom Offset"
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
"name": "example-2",
|
|
1496
|
+
"code": "<!-- Simple usage - uses text content automatically -->\n<div class=\"truncated-text\" ngpTooltipTrigger>\n This text might be truncated with ellipsis and show the full content in the tooltip\n</div>\n\n<!-- Passing content directly takes precedence -->\n<button [ngpTooltipTrigger]=\"myToolip\">This won't show a tooltip unless content is provided</button>",
|
|
1497
|
+
"description": "Using Text Content as Tooltip"
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
"name": "example-3",
|
|
1501
|
+
"code": "<div\n class=\"truncated-text\"\n appTooltipTrigger=\"This tooltip only shows when text overflows\"\n ngpTooltipTriggerShowOnOverflow\n>\n This text might be truncated\n</div>",
|
|
1502
|
+
"description": "Conditional Tooltips"
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
"name": "example-4",
|
|
1506
|
+
"code": "import { provideTooltipConfig } from 'ng-primitives/tooltip';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideTooltipConfig({\n offset: 4,\n placement: 'top',\n showDelay: 0,\n hideDelay: 500,\n flip: true,\n container: document.body,\n showOnOverflow: false,\n useTextContent: true,\n }),\n ],\n});",
|
|
1507
|
+
"description": "Global Configuration"
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
"name": "example-5",
|
|
1511
|
+
"code": "offset: {\n mainAxis: 8, // Distance between tooltip and trigger element\n crossAxis: 4, // Skidding along the alignment axis\n alignmentAxis: 2 // Same as crossAxis but for aligned placements\n}",
|
|
1512
|
+
"description": "NgpTooltipConfig"
|
|
1513
|
+
}
|
|
1514
|
+
],
|
|
1515
|
+
"reusableComponent": {
|
|
1516
|
+
"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",
|
|
1517
|
+
"hasVariants": false,
|
|
1518
|
+
"hasSizes": false
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
]
|