@shadng/sng-ui 1.0.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.
Files changed (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/cli/sng-ui.js +331 -0
  4. package/ng-package.json +29 -0
  5. package/package.json +64 -0
  6. package/registry.json +72 -0
  7. package/src/lib/accordion/cn.ts +6 -0
  8. package/src/lib/accordion/index.ts +18 -0
  9. package/src/lib/accordion/sng-accordion-content.ts +131 -0
  10. package/src/lib/accordion/sng-accordion-item.ts +299 -0
  11. package/src/lib/accordion/sng-accordion-trigger.ts +137 -0
  12. package/src/lib/accordion/sng-accordion.ts +118 -0
  13. package/src/lib/accordion/sng-accordion.types.ts +82 -0
  14. package/src/lib/alert/cn.ts +6 -0
  15. package/src/lib/alert/index.ts +3 -0
  16. package/src/lib/alert/sng-alert-description.ts +49 -0
  17. package/src/lib/alert/sng-alert-title.ts +46 -0
  18. package/src/lib/alert/sng-alert.ts +48 -0
  19. package/src/lib/avatar/cn.ts +6 -0
  20. package/src/lib/avatar/index.ts +3 -0
  21. package/src/lib/avatar/sng-avatar-fallback.ts +50 -0
  22. package/src/lib/avatar/sng-avatar-image.ts +73 -0
  23. package/src/lib/avatar/sng-avatar.ts +60 -0
  24. package/src/lib/badge/cn.ts +6 -0
  25. package/src/lib/badge/index.ts +1 -0
  26. package/src/lib/badge/sng-badge.ts +36 -0
  27. package/src/lib/breadcrumb/cn.ts +6 -0
  28. package/src/lib/breadcrumb/index.ts +7 -0
  29. package/src/lib/breadcrumb/sng-breadcrumb-ellipsis.ts +61 -0
  30. package/src/lib/breadcrumb/sng-breadcrumb-item.ts +47 -0
  31. package/src/lib/breadcrumb/sng-breadcrumb-link.ts +43 -0
  32. package/src/lib/breadcrumb/sng-breadcrumb-list.ts +42 -0
  33. package/src/lib/breadcrumb/sng-breadcrumb-page.ts +44 -0
  34. package/src/lib/breadcrumb/sng-breadcrumb-separator.ts +60 -0
  35. package/src/lib/breadcrumb/sng-breadcrumb.ts +52 -0
  36. package/src/lib/button/cn.ts +6 -0
  37. package/src/lib/button/index.ts +2 -0
  38. package/src/lib/button/sng-button.ts +264 -0
  39. package/src/lib/calendar/cn.ts +6 -0
  40. package/src/lib/calendar/index.ts +2 -0
  41. package/src/lib/calendar/sng-calendar.ts +753 -0
  42. package/src/lib/card/cn.ts +6 -0
  43. package/src/lib/card/index.ts +6 -0
  44. package/src/lib/card/sng-card-content.ts +36 -0
  45. package/src/lib/card/sng-card-description.ts +38 -0
  46. package/src/lib/card/sng-card-footer.ts +34 -0
  47. package/src/lib/card/sng-card-header.ts +34 -0
  48. package/src/lib/card/sng-card-title.ts +48 -0
  49. package/src/lib/card/sng-card.ts +43 -0
  50. package/src/lib/carousel/cn.ts +6 -0
  51. package/src/lib/carousel/index.ts +18 -0
  52. package/src/lib/carousel/sng-carousel.ts +526 -0
  53. package/src/lib/checkbox/cn.ts +6 -0
  54. package/src/lib/checkbox/index.ts +1 -0
  55. package/src/lib/checkbox/sng-checkbox.ts +154 -0
  56. package/src/lib/code-block/cn.ts +6 -0
  57. package/src/lib/code-block/index.ts +1 -0
  58. package/src/lib/code-block/sng-code-block.ts +296 -0
  59. package/src/lib/dialog/cn.ts +6 -0
  60. package/src/lib/dialog/index.ts +37 -0
  61. package/src/lib/dialog/sng-dialog-close.ts +76 -0
  62. package/src/lib/dialog/sng-dialog-content.ts +132 -0
  63. package/src/lib/dialog/sng-dialog-description.ts +36 -0
  64. package/src/lib/dialog/sng-dialog-footer.ts +39 -0
  65. package/src/lib/dialog/sng-dialog-header.ts +39 -0
  66. package/src/lib/dialog/sng-dialog-title.ts +52 -0
  67. package/src/lib/dialog/sng-dialog.service.ts +222 -0
  68. package/src/lib/dialog/sng-dialog.ts +224 -0
  69. package/src/lib/drawer/cn.ts +6 -0
  70. package/src/lib/drawer/index.ts +36 -0
  71. package/src/lib/drawer/sng-drawer-close.ts +28 -0
  72. package/src/lib/drawer/sng-drawer-content.ts +135 -0
  73. package/src/lib/drawer/sng-drawer-description.ts +29 -0
  74. package/src/lib/drawer/sng-drawer-footer.ts +34 -0
  75. package/src/lib/drawer/sng-drawer-handle.ts +30 -0
  76. package/src/lib/drawer/sng-drawer-header.ts +30 -0
  77. package/src/lib/drawer/sng-drawer-title.ts +27 -0
  78. package/src/lib/drawer/sng-drawer-trigger.ts +21 -0
  79. package/src/lib/drawer/sng-drawer-wrapper.ts +27 -0
  80. package/src/lib/drawer/sng-drawer.ts +166 -0
  81. package/src/lib/file-input/cn.ts +6 -0
  82. package/src/lib/file-input/index.ts +1 -0
  83. package/src/lib/file-input/sng-file-input.ts +288 -0
  84. package/src/lib/hover-card/cn.ts +6 -0
  85. package/src/lib/hover-card/index.ts +3 -0
  86. package/src/lib/hover-card/sng-hover-card-content.ts +100 -0
  87. package/src/lib/hover-card/sng-hover-card-trigger.ts +43 -0
  88. package/src/lib/hover-card/sng-hover-card.ts +246 -0
  89. package/src/lib/input/cn.ts +6 -0
  90. package/src/lib/input/index.ts +1 -0
  91. package/src/lib/input/sng-input.ts +160 -0
  92. package/src/lib/layout/cn.ts +6 -0
  93. package/src/lib/layout/index.ts +98 -0
  94. package/src/lib/layout/sng-layout-footer.ts +37 -0
  95. package/src/lib/layout/sng-layout-header.ts +38 -0
  96. package/src/lib/layout/sng-layout-sidebar-content.ts +149 -0
  97. package/src/lib/layout/sng-layout-sidebar-footer.ts +54 -0
  98. package/src/lib/layout/sng-layout-sidebar-group-action.ts +67 -0
  99. package/src/lib/layout/sng-layout-sidebar-group-content.ts +41 -0
  100. package/src/lib/layout/sng-layout-sidebar-group-label.ts +53 -0
  101. package/src/lib/layout/sng-layout-sidebar-group.ts +41 -0
  102. package/src/lib/layout/sng-layout-sidebar-header.ts +54 -0
  103. package/src/lib/layout/sng-layout-sidebar-input.ts +112 -0
  104. package/src/lib/layout/sng-layout-sidebar-inset.ts +45 -0
  105. package/src/lib/layout/sng-layout-sidebar-menu-action.ts +84 -0
  106. package/src/lib/layout/sng-layout-sidebar-menu-badge.ts +47 -0
  107. package/src/lib/layout/sng-layout-sidebar-menu-button.ts +160 -0
  108. package/src/lib/layout/sng-layout-sidebar-menu-item.ts +40 -0
  109. package/src/lib/layout/sng-layout-sidebar-menu-skeleton.ts +71 -0
  110. package/src/lib/layout/sng-layout-sidebar-menu-sub-button.ts +142 -0
  111. package/src/lib/layout/sng-layout-sidebar-menu-sub-item.ts +38 -0
  112. package/src/lib/layout/sng-layout-sidebar-menu-sub.ts +48 -0
  113. package/src/lib/layout/sng-layout-sidebar-menu.ts +41 -0
  114. package/src/lib/layout/sng-layout-sidebar-provider.ts +189 -0
  115. package/src/lib/layout/sng-layout-sidebar-rail.ts +60 -0
  116. package/src/lib/layout/sng-layout-sidebar-separator.ts +38 -0
  117. package/src/lib/layout/sng-layout-sidebar-trigger.ts +97 -0
  118. package/src/lib/layout/sng-layout-sidebar.ts +254 -0
  119. package/src/lib/menu/cn.ts +6 -0
  120. package/src/lib/menu/index.ts +21 -0
  121. package/src/lib/menu/sng-context-trigger.ts +128 -0
  122. package/src/lib/menu/sng-menu-checkbox-item.ts +91 -0
  123. package/src/lib/menu/sng-menu-item.ts +80 -0
  124. package/src/lib/menu/sng-menu-label.ts +47 -0
  125. package/src/lib/menu/sng-menu-radio-group.ts +38 -0
  126. package/src/lib/menu/sng-menu-radio-item.ts +94 -0
  127. package/src/lib/menu/sng-menu-separator.ts +27 -0
  128. package/src/lib/menu/sng-menu-shortcut.ts +25 -0
  129. package/src/lib/menu/sng-menu-sub-content.ts +267 -0
  130. package/src/lib/menu/sng-menu-sub-trigger.ts +68 -0
  131. package/src/lib/menu/sng-menu-sub.ts +124 -0
  132. package/src/lib/menu/sng-menu-tokens.ts +52 -0
  133. package/src/lib/menu/sng-menu-trigger.ts +266 -0
  134. package/src/lib/menu/sng-menu.ts +100 -0
  135. package/src/lib/nav-menu/cn.ts +6 -0
  136. package/src/lib/nav-menu/index.ts +6 -0
  137. package/src/lib/nav-menu/sng-nav-menu-content.ts +72 -0
  138. package/src/lib/nav-menu/sng-nav-menu-item.ts +109 -0
  139. package/src/lib/nav-menu/sng-nav-menu-link.ts +54 -0
  140. package/src/lib/nav-menu/sng-nav-menu-list.ts +43 -0
  141. package/src/lib/nav-menu/sng-nav-menu-trigger.ts +98 -0
  142. package/src/lib/nav-menu/sng-nav-menu.ts +99 -0
  143. package/src/lib/otp-input/cn.ts +6 -0
  144. package/src/lib/otp-input/index.ts +14 -0
  145. package/src/lib/otp-input/sng-otp-input-group.ts +38 -0
  146. package/src/lib/otp-input/sng-otp-input-separator.ts +43 -0
  147. package/src/lib/otp-input/sng-otp-input-slot.ts +128 -0
  148. package/src/lib/otp-input/sng-otp-input-tokens.ts +20 -0
  149. package/src/lib/otp-input/sng-otp-input.ts +301 -0
  150. package/src/lib/popover/cn.ts +6 -0
  151. package/src/lib/popover/index.ts +3 -0
  152. package/src/lib/popover/sng-popover-content.ts +66 -0
  153. package/src/lib/popover/sng-popover-trigger.ts +44 -0
  154. package/src/lib/popover/sng-popover.ts +218 -0
  155. package/src/lib/preview-box/cn.ts +6 -0
  156. package/src/lib/preview-box/index.ts +5 -0
  157. package/src/lib/preview-box/sng-code-block.ts +80 -0
  158. package/src/lib/preview-box/sng-html-block.ts +79 -0
  159. package/src/lib/preview-box/sng-preview-block.ts +47 -0
  160. package/src/lib/preview-box/sng-preview-box.ts +369 -0
  161. package/src/lib/preview-box/sng-style-block.ts +80 -0
  162. package/src/lib/progress/cn.ts +6 -0
  163. package/src/lib/progress/index.ts +1 -0
  164. package/src/lib/progress/sng-progress.ts +65 -0
  165. package/src/lib/radio/cn.ts +6 -0
  166. package/src/lib/radio/index.ts +5 -0
  167. package/src/lib/radio/sng-radio-item.ts +100 -0
  168. package/src/lib/radio/sng-radio.ts +54 -0
  169. package/src/lib/resizable/cn.ts +6 -0
  170. package/src/lib/resizable/index.ts +3 -0
  171. package/src/lib/resizable/sng-resizable-group.ts +188 -0
  172. package/src/lib/resizable/sng-resizable-handle.ts +236 -0
  173. package/src/lib/resizable/sng-resizable-panel.ts +71 -0
  174. package/src/lib/search-input/cn.ts +6 -0
  175. package/src/lib/search-input/index.ts +16 -0
  176. package/src/lib/search-input/sng-search-input-context.ts +24 -0
  177. package/src/lib/search-input/sng-search-input-empty.ts +42 -0
  178. package/src/lib/search-input/sng-search-input-group.ts +69 -0
  179. package/src/lib/search-input/sng-search-input-item.ts +164 -0
  180. package/src/lib/search-input/sng-search-input-list.ts +34 -0
  181. package/src/lib/search-input/sng-search-input-separator.ts +32 -0
  182. package/src/lib/search-input/sng-search-input-shortcut.ts +29 -0
  183. package/src/lib/search-input/sng-search-input.ts +368 -0
  184. package/src/lib/select/cn.ts +6 -0
  185. package/src/lib/select/index.ts +7 -0
  186. package/src/lib/select/sng-select-content.ts +27 -0
  187. package/src/lib/select/sng-select-empty.ts +48 -0
  188. package/src/lib/select/sng-select-group.ts +29 -0
  189. package/src/lib/select/sng-select-item.ts +140 -0
  190. package/src/lib/select/sng-select-label.ts +29 -0
  191. package/src/lib/select/sng-select-separator.ts +29 -0
  192. package/src/lib/select/sng-select.ts +326 -0
  193. package/src/lib/separator/cn.ts +6 -0
  194. package/src/lib/separator/index.ts +1 -0
  195. package/src/lib/separator/sng-separator.ts +40 -0
  196. package/src/lib/skeleton/cn.ts +6 -0
  197. package/src/lib/skeleton/index.ts +1 -0
  198. package/src/lib/skeleton/sng-skeleton.ts +49 -0
  199. package/src/lib/slider/cn.ts +6 -0
  200. package/src/lib/slider/index.ts +2 -0
  201. package/src/lib/slider/sng-slider.ts +137 -0
  202. package/src/lib/sng-table/cn.ts +6 -0
  203. package/src/lib/sng-table/flex-render.ts +222 -0
  204. package/src/lib/sng-table/index.ts +85 -0
  205. package/src/lib/sng-table/sng-table-body.ts +59 -0
  206. package/src/lib/sng-table/sng-table-caption.ts +49 -0
  207. package/src/lib/sng-table/sng-table-cell.ts +62 -0
  208. package/src/lib/sng-table/sng-table-footer.ts +60 -0
  209. package/src/lib/sng-table/sng-table-head.ts +66 -0
  210. package/src/lib/sng-table/sng-table-header.ts +48 -0
  211. package/src/lib/sng-table/sng-table-pagination.ts +265 -0
  212. package/src/lib/sng-table/sng-table-row.ts +65 -0
  213. package/src/lib/sng-table/sng-table.ts +67 -0
  214. package/src/lib/sng-table-core/core/create-cell.ts +117 -0
  215. package/src/lib/sng-table-core/core/create-column.ts +266 -0
  216. package/src/lib/sng-table-core/core/create-header.ts +271 -0
  217. package/src/lib/sng-table-core/core/create-row.ts +293 -0
  218. package/src/lib/sng-table-core/core/create-table.ts +534 -0
  219. package/src/lib/sng-table-core/core/types.ts +1197 -0
  220. package/src/lib/sng-table-core/core/utils.ts +307 -0
  221. package/src/lib/sng-table-core/features/column-filtering.ts +376 -0
  222. package/src/lib/sng-table-core/features/column-ordering.ts +159 -0
  223. package/src/lib/sng-table-core/features/column-pinning.ts +219 -0
  224. package/src/lib/sng-table-core/features/column-sizing.ts +268 -0
  225. package/src/lib/sng-table-core/features/column-visibility.ts +128 -0
  226. package/src/lib/sng-table-core/features/faceting.ts +279 -0
  227. package/src/lib/sng-table-core/features/fuzzy-filtering.ts +188 -0
  228. package/src/lib/sng-table-core/features/global-filtering.ts +128 -0
  229. package/src/lib/sng-table-core/features/pagination.ts +179 -0
  230. package/src/lib/sng-table-core/features/row-expanding.ts +181 -0
  231. package/src/lib/sng-table-core/features/row-grouping.ts +235 -0
  232. package/src/lib/sng-table-core/features/row-pinning.ts +196 -0
  233. package/src/lib/sng-table-core/features/row-selection.ts +298 -0
  234. package/src/lib/sng-table-core/features/sorting.ts +425 -0
  235. package/src/lib/sng-table-core/features/virtualization.ts +298 -0
  236. package/src/lib/sng-table-core/index.ts +235 -0
  237. package/src/lib/sng-table-core/row-models/core-row-model.ts +256 -0
  238. package/src/lib/sng-table-core/row-models/expanded-row-model.ts +175 -0
  239. package/src/lib/sng-table-core/row-models/filtered-row-model.ts +307 -0
  240. package/src/lib/sng-table-core/row-models/grouped-row-model.ts +290 -0
  241. package/src/lib/sng-table-core/row-models/paginated-row-model.ts +135 -0
  242. package/src/lib/sng-table-core/row-models/sorted-row-model.ts +197 -0
  243. package/src/lib/styles/sng-themes.css +164 -0
  244. package/src/lib/switch/cn.ts +6 -0
  245. package/src/lib/switch/index.ts +1 -0
  246. package/src/lib/switch/sng-switch.ts +137 -0
  247. package/src/lib/tabs/cn.ts +6 -0
  248. package/src/lib/tabs/index.ts +4 -0
  249. package/src/lib/tabs/sng-tabs-content.ts +66 -0
  250. package/src/lib/tabs/sng-tabs-list.ts +55 -0
  251. package/src/lib/tabs/sng-tabs-trigger.ts +86 -0
  252. package/src/lib/tabs/sng-tabs.ts +83 -0
  253. package/src/lib/toast/cn.ts +6 -0
  254. package/src/lib/toast/index.ts +3 -0
  255. package/src/lib/toast/sng-toast.service.ts +258 -0
  256. package/src/lib/toast/sng-toast.ts +101 -0
  257. package/src/lib/toast/sng-toaster.ts +67 -0
  258. package/src/lib/toggle/cn.ts +6 -0
  259. package/src/lib/toggle/index.ts +6 -0
  260. package/src/lib/toggle/sng-toggle-group-item.ts +89 -0
  261. package/src/lib/toggle/sng-toggle-group.ts +85 -0
  262. package/src/lib/toggle/sng-toggle.ts +78 -0
  263. package/src/lib/toggle-group/index.ts +6 -0
  264. package/src/lib/tooltip/cn.ts +6 -0
  265. package/src/lib/tooltip/index.ts +5 -0
  266. package/src/lib/tooltip/sng-tooltip-content.ts +64 -0
  267. package/src/lib/tooltip/sng-tooltip.ts +216 -0
  268. package/src/public-api.ts +207 -0
  269. package/tsconfig.json +24 -0
  270. package/tsconfig.lib.json +17 -0
  271. package/tsconfig.lib.prod.json +11 -0
@@ -0,0 +1,43 @@
1
+ import { Component, ChangeDetectionStrategy, input, inject, computed } from '@angular/core';
2
+ import { SngNavMenu } from './sng-nav-menu';
3
+ import { cn } from './cn';
4
+
5
+ /**
6
+ * Container for navigation menu items.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <sng-nav-menu>
11
+ * <sng-nav-menu-list>
12
+ * <sng-nav-menu-item>
13
+ * <sng-nav-menu-trigger>Getting Started</sng-nav-menu-trigger>
14
+ * <sng-nav-menu-content>...</sng-nav-menu-content>
15
+ * </sng-nav-menu-item>
16
+ * </sng-nav-menu-list>
17
+ * </sng-nav-menu>
18
+ * ```
19
+ */
20
+ @Component({
21
+ selector: 'sng-nav-menu-list',
22
+ standalone: true,
23
+ changeDetection: ChangeDetectionStrategy.OnPush,
24
+ host: {
25
+ '[class]': 'hostClasses()',
26
+ },
27
+ template: `<ng-content />`,
28
+ })
29
+ export class SngNavMenuList {
30
+ /** Custom CSS classes. */
31
+ class = input<string>('');
32
+
33
+ private menu = inject(SngNavMenu, { optional: true });
34
+
35
+ hostClasses = computed(() => {
36
+ const isVertical = this.menu?.layout() === 'vertical';
37
+ return cn(
38
+ 'relative group flex flex-1 list-none items-center',
39
+ isVertical ? 'flex-col space-y-1' : 'justify-center space-x-1',
40
+ this.class()
41
+ );
42
+ });
43
+ }
@@ -0,0 +1,98 @@
1
+ import { Component, ChangeDetectionStrategy, ViewEncapsulation, input, inject, computed } from '@angular/core';
2
+ import { SngNavMenuItem } from './sng-nav-menu-item';
3
+ import { SngNavMenu, type SngNavMenuSide } from './sng-nav-menu';
4
+ import { cn } from './cn';
5
+
6
+ const CHEVRON_CLOSED: Record<SngNavMenuSide, string> = {
7
+ bottom: '',
8
+ top: 'rotate-180',
9
+ right: '-rotate-90',
10
+ left: 'rotate-90',
11
+ };
12
+
13
+ const CHEVRON_OPEN: Record<SngNavMenuSide, string> = {
14
+ bottom: 'rotate-180',
15
+ top: '',
16
+ right: 'rotate-90',
17
+ left: '-rotate-90',
18
+ };
19
+
20
+ /**
21
+ * Button that triggers the display of navigation menu content.
22
+ *
23
+ * @example
24
+ * ```html
25
+ * <sng-nav-menu-item>
26
+ * <sng-nav-menu-trigger>Getting Started</sng-nav-menu-trigger>
27
+ * <sng-nav-menu-content>...</sng-nav-menu-content>
28
+ * </sng-nav-menu-item>
29
+ * ```
30
+ */
31
+ @Component({
32
+ selector: 'sng-nav-menu-trigger',
33
+ standalone: true,
34
+ changeDetection: ChangeDetectionStrategy.OnPush,
35
+ encapsulation: ViewEncapsulation.None,
36
+ host: {
37
+ 'class': 'contents',
38
+ },
39
+ template: `
40
+ <button
41
+ type="button"
42
+ [class]="buttonClasses()"
43
+ [attr.data-state]="dataState()"
44
+ aria-haspopup="menu"
45
+ [attr.aria-expanded]="ariaExpanded()"
46
+ (click)="onClick()"
47
+ >
48
+ <ng-content />
49
+ <svg
50
+ [class]="chevronClasses()"
51
+ xmlns="http://www.w3.org/2000/svg"
52
+ viewBox="0 0 24 24"
53
+ fill="none"
54
+ stroke="currentColor"
55
+ stroke-width="2"
56
+ stroke-linecap="round"
57
+ stroke-linejoin="round"
58
+ >
59
+ <path d="m6 9 6 6 6-6"/>
60
+ </svg>
61
+ </button>
62
+ `,
63
+ })
64
+ export class SngNavMenuTrigger {
65
+ /** Custom CSS classes. */
66
+ class = input<string>('');
67
+
68
+ private item = inject(SngNavMenuItem, { optional: true });
69
+ private menu = inject(SngNavMenu, { optional: true });
70
+
71
+ buttonClasses = computed(() =>
72
+ cn(
73
+ 'group inline-flex h-9 w-max cursor-pointer items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium',
74
+ 'hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground',
75
+ 'disabled:pointer-events-none disabled:opacity-50',
76
+ 'data-[state=open]:bg-accent/50 data-[state=open]:text-accent-foreground',
77
+ 'focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px]',
78
+ this.class()
79
+ )
80
+ );
81
+
82
+ chevronClasses = computed(() => {
83
+ const side = this.menu?.resolvedSide() ?? 'bottom';
84
+ const isOpen = this.item?.isOpen() ?? false;
85
+ return cn(
86
+ 'relative top-[1px] ml-1 h-3 w-3 transition duration-300',
87
+ isOpen ? CHEVRON_OPEN[side] : CHEVRON_CLOSED[side]
88
+ );
89
+ });
90
+
91
+ dataState = computed(() => this.item?.isOpen() ? 'open' : 'closed');
92
+
93
+ ariaExpanded = computed(() => this.item?.isOpen() ?? false);
94
+
95
+ onClick(): void {
96
+ this.item?.toggle();
97
+ }
98
+ }
@@ -0,0 +1,99 @@
1
+ import { Component, ChangeDetectionStrategy, ElementRef, inject, input, signal, computed, booleanAttribute } from '@angular/core';
2
+ import { cn } from './cn';
3
+
4
+ export type SngNavMenuLayout = 'horizontal' | 'vertical';
5
+ export type SngNavMenuSide = 'bottom' | 'top' | 'left' | 'right';
6
+ export type SngNavMenuAlign = 'item' | 'full';
7
+
8
+ /**
9
+ * Root container for the navigation menu.
10
+ *
11
+ * @example
12
+ * ```html
13
+ * <sng-nav-menu>
14
+ * <sng-nav-menu-list>
15
+ * <sng-nav-menu-item>
16
+ * <sng-nav-menu-trigger>Products</sng-nav-menu-trigger>
17
+ * <sng-nav-menu-content>
18
+ * <a sngNavMenuLink href="/products">All Products</a>
19
+ * </sng-nav-menu-content>
20
+ * </sng-nav-menu-item>
21
+ * </sng-nav-menu-list>
22
+ * </sng-nav-menu>
23
+ * ```
24
+ */
25
+ @Component({
26
+ selector: 'sng-nav-menu',
27
+ standalone: true,
28
+ changeDetection: ChangeDetectionStrategy.OnPush,
29
+ host: {
30
+ '[class]': 'hostClasses()',
31
+ '(document:click)': 'onDocumentClick($event)',
32
+ },
33
+ template: `<ng-content />`,
34
+ })
35
+ export class SngNavMenu {
36
+ /** Custom CSS classes. */
37
+ class = input<string>('');
38
+
39
+ /** Layout direction. */
40
+ layout = input<SngNavMenuLayout>('horizontal');
41
+
42
+ /** Side where content panels appear. Defaults to bottom (horizontal) or right (vertical). */
43
+ side = input<SngNavMenuSide | undefined>(undefined);
44
+
45
+ /** Panel alignment: 'full' positions relative to the nav bar, 'item' relative to trigger. */
46
+ align = input<SngNavMenuAlign>('full');
47
+
48
+ /** Whether menus open on hover. */
49
+ hover = input(true, { transform: booleanAttribute });
50
+
51
+ private elementRef = inject(ElementRef);
52
+
53
+ /** Resolved side based on layout when not explicitly set. */
54
+ resolvedSide = computed<SngNavMenuSide>(() => {
55
+ const explicit = this.side();
56
+ if (explicit) return explicit;
57
+ return this.layout() === 'horizontal' ? 'bottom' : 'right';
58
+ });
59
+
60
+ hostClasses = computed(() =>
61
+ cn(
62
+ 'relative flex max-w-max flex-1 items-center justify-center',
63
+ this.openItemId() ? 'z-50' : 'z-10',
64
+ this.class()
65
+ )
66
+ );
67
+
68
+ /** Currently open item ID. */
69
+ openItemId = signal<string | null>(null);
70
+
71
+ private itemCounter = 0;
72
+
73
+ /** @internal Generate unique ID for item. */
74
+ generateItemId(): string {
75
+ return `nav-item-${++this.itemCounter}`;
76
+ }
77
+
78
+ /** Open specific item, close others. */
79
+ openItem(itemId: string): void {
80
+ this.openItemId.set(itemId);
81
+ }
82
+
83
+ /** Close all items. */
84
+ closeAll(): void {
85
+ this.openItemId.set(null);
86
+ }
87
+
88
+ /** Check if item is open. */
89
+ isItemOpen(itemId: string): boolean {
90
+ return this.openItemId() === itemId;
91
+ }
92
+
93
+ /** @internal */
94
+ onDocumentClick(event: MouseEvent): void {
95
+ if (this.openItemId() && !this.elementRef.nativeElement.contains(event.target)) {
96
+ this.closeAll();
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,14 @@
1
+ // Main component
2
+ export { SngOtpInput, REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from './sng-otp-input';
3
+
4
+ // Sub-components
5
+ export { SngOtpInputGroup } from './sng-otp-input-group';
6
+ export { SngOtpInputSlot } from './sng-otp-input-slot';
7
+ export { SngOtpInputSeparator } from './sng-otp-input-separator';
8
+
9
+ // Types and tokens
10
+ export {
11
+ SNG_OTP_INPUT_CONTEXT,
12
+ type OtpSlotState,
13
+ type SngOtpInputContext,
14
+ } from './sng-otp-input-tokens';
@@ -0,0 +1,38 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ input,
5
+ computed,
6
+ } from '@angular/core';
7
+ import { cn } from './cn';
8
+
9
+ /**
10
+ * Groups OTP slots together visually (e.g., for 3-3 or 4-2 patterns).
11
+ * Use multiple groups with separators for common OTP layouts.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <sng-otp-input-group>
16
+ * <sng-otp-input-slot [index]="0" />
17
+ * <sng-otp-input-slot [index]="1" />
18
+ * <sng-otp-input-slot [index]="2" />
19
+ * </sng-otp-input-group>
20
+ * ```
21
+ */
22
+ @Component({
23
+ selector: 'sng-otp-input-group',
24
+ standalone: true,
25
+ changeDetection: ChangeDetectionStrategy.OnPush,
26
+ host: {
27
+ '[class]': 'hostClasses()',
28
+ },
29
+ template: `<ng-content />`,
30
+ })
31
+ export class SngOtpInputGroup {
32
+ /** Custom CSS classes. */
33
+ class = input<string>('');
34
+
35
+ hostClasses = computed(() =>
36
+ cn('flex items-center', this.class())
37
+ );
38
+ }
@@ -0,0 +1,43 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ ViewEncapsulation,
5
+ input,
6
+ computed,
7
+ } from '@angular/core';
8
+ import { cn } from './cn';
9
+
10
+ /**
11
+ * Visual separator between OTP slot groups.
12
+ * Displays a dash/line icon between groups.
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <sng-otp-input-group>...</sng-otp-input-group>
17
+ * <sng-otp-input-separator />
18
+ * <sng-otp-input-group>...</sng-otp-input-group>
19
+ * ```
20
+ */
21
+ @Component({
22
+ selector: 'sng-otp-input-separator',
23
+ standalone: true,
24
+ changeDetection: ChangeDetectionStrategy.OnPush,
25
+ encapsulation: ViewEncapsulation.None,
26
+ host: {
27
+ '[class]': 'hostClasses()',
28
+ 'role': 'separator',
29
+ },
30
+ template: `
31
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
32
+ <path d="M5 12h14"/>
33
+ </svg>
34
+ `,
35
+ })
36
+ export class SngOtpInputSeparator {
37
+ /** Custom CSS classes. */
38
+ class = input<string>('');
39
+
40
+ hostClasses = computed(() =>
41
+ cn('flex items-center justify-center', this.class())
42
+ );
43
+ }
@@ -0,0 +1,128 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ ViewEncapsulation,
5
+ input,
6
+ computed,
7
+ inject,
8
+ signal,
9
+ ElementRef,
10
+ AfterViewInit,
11
+ } from '@angular/core';
12
+ import { cn } from './cn';
13
+ import { SNG_OTP_INPUT_CONTEXT, OTP_CONTEXT_KEY, type SngOtpInputContext } from './sng-otp-input-tokens';
14
+
15
+ /**
16
+ * Individual character slot within an OTP input.
17
+ * Must be used inside a sng-otp-input.
18
+ *
19
+ * @example
20
+ * ```html
21
+ * <sng-otp-input-slot [index]="0" />
22
+ * <sng-otp-input-slot [index]="1" class="border-primary" />
23
+ * ```
24
+ */
25
+ @Component({
26
+ selector: 'sng-otp-input-slot',
27
+ standalone: true,
28
+ changeDetection: ChangeDetectionStrategy.OnPush,
29
+ encapsulation: ViewEncapsulation.None,
30
+ host: {
31
+ '[class]': 'hostClasses()',
32
+ '[attr.data-active]': 'isActive()',
33
+ '(click)': 'onClick()',
34
+ },
35
+ template: `
36
+ {{ char() }}
37
+ @if (hasFakeCaret()) {
38
+ <div class="pointer-events-none absolute inset-0 flex items-center justify-center">
39
+ <div class="h-4 w-px bg-foreground"></div>
40
+ </div>
41
+ }
42
+ `,
43
+ })
44
+ export class SngOtpInputSlot implements AfterViewInit {
45
+ private elementRef = inject(ElementRef);
46
+
47
+ // Try DI first (works in normal usage), fall back to manual context (for content projection)
48
+ private injectedContext = inject(SNG_OTP_INPUT_CONTEXT, { optional: true });
49
+ private _manualContext = signal<SngOtpInputContext | null>(null);
50
+
51
+ private context = computed(() => this.injectedContext || this._manualContext());
52
+
53
+ ngAfterViewInit(): void {
54
+ // If DI didn't provide context, find parent through DOM
55
+ if (!this.injectedContext) {
56
+ this.findAndSetParentContext();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @internal
62
+ * Walk up the DOM to find the parent sng-otp-input and get its context.
63
+ * This is needed for content projection scenarios (like Storybook) where DI doesn't work.
64
+ */
65
+ private findAndSetParentContext(): void {
66
+ let el: HTMLElement | null = this.elementRef.nativeElement.parentElement;
67
+ while (el) {
68
+ if (el.tagName.toLowerCase() === 'sng-otp-input') {
69
+ // Get the context stored on the element by SngOtpInput
70
+ const context = (el as unknown as Record<string, SngOtpInputContext | undefined>)[OTP_CONTEXT_KEY];
71
+ if (context) {
72
+ this._manualContext.set(context);
73
+ }
74
+ break;
75
+ }
76
+ el = el.parentElement;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Zero-based index of this slot within the OTP input.
82
+ */
83
+ index = input.required<number>();
84
+
85
+ /** Custom CSS classes. */
86
+ class = input<string>('');
87
+
88
+ /**
89
+ * @internal
90
+ * Called by parent SngOtpInput to provide context when DI doesn't work (content projection).
91
+ */
92
+ _setContext(context: SngOtpInputContext): void {
93
+ this._manualContext.set(context);
94
+ }
95
+
96
+ private slotState = computed(() => {
97
+ const ctx = this.context();
98
+ if (!ctx) return { char: '', isActive: false, hasFakeCaret: false };
99
+ const slots = ctx.slots();
100
+ const idx = this.index();
101
+ return slots[idx] || { char: '', isActive: false, hasFakeCaret: false };
102
+ });
103
+
104
+ char = computed(() => this.slotState().char);
105
+ isActive = computed(() => this.slotState().isActive);
106
+ hasFakeCaret = computed(() => this.slotState().hasFakeCaret);
107
+
108
+ hostClasses = computed(() =>
109
+ cn(
110
+ 'border-input relative flex h-9 w-9 items-center justify-center',
111
+ 'border-y border-r text-sm shadow-xs transition-colors outline-none',
112
+ 'first:rounded-l-md first:border-l last:rounded-r-md',
113
+ 'dark:bg-input/30',
114
+ // Match standard input focus ring: ring-1 ring-ring
115
+ 'data-[active=true]:border-ring data-[active=true]:ring-1 data-[active=true]:ring-ring data-[active=true]:z-10',
116
+ 'aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive',
117
+ 'data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40',
118
+ this.class()
119
+ )
120
+ );
121
+
122
+ onClick() {
123
+ const ctx = this.context();
124
+ if (ctx) {
125
+ ctx.focus(this.index());
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,20 @@
1
+ import { InjectionToken, Signal } from '@angular/core';
2
+
3
+ /** State for an individual OTP slot. */
4
+ export interface OtpSlotState {
5
+ char: string;
6
+ isActive: boolean;
7
+ hasFakeCaret: boolean;
8
+ }
9
+
10
+ /** OTP context interface for child slots to inject. */
11
+ export interface SngOtpInputContext {
12
+ slots: Signal<OtpSlotState[]>;
13
+ focus: (index: number) => void;
14
+ }
15
+
16
+ /** Injection token for OTP context. */
17
+ export const SNG_OTP_INPUT_CONTEXT = new InjectionToken<SngOtpInputContext>('SNG_OTP_INPUT_CONTEXT');
18
+
19
+ /** @internal Key for storing OTP context on DOM element (fallback for content projection) */
20
+ export const OTP_CONTEXT_KEY = '__sngOtpInputContext__';