@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,135 @@
1
+ import {
2
+ Component,
3
+ input,
4
+ computed,
5
+ inject,
6
+ ChangeDetectionStrategy,
7
+ ViewEncapsulation,
8
+ AfterViewInit,
9
+ } from '@angular/core';
10
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
11
+ import { SNG_DRAWER_INSTANCE, SNG_DRAWER_CLOSE, SngDrawerSide } from './sng-drawer';
12
+ import { cn } from './cn';
13
+
14
+ /**
15
+ * Container for the drawer panel content.
16
+ * Uses CDK focus trap for accessibility and supports slide animations.
17
+ *
18
+ * @example
19
+ * ```html
20
+ * <ng-template #content>
21
+ * <sng-drawer-content>
22
+ * <sng-drawer-handle></sng-drawer-handle>
23
+ * <sng-drawer-header>
24
+ * <sng-drawer-title>Settings</sng-drawer-title>
25
+ * </sng-drawer-header>
26
+ * <div class="p-4">Drawer content</div>
27
+ * </sng-drawer-content>
28
+ * </ng-template>
29
+ * ```
30
+ */
31
+ @Component({
32
+ selector: 'sng-drawer-content',
33
+ standalone: true,
34
+ imports: [],
35
+ changeDetection: ChangeDetectionStrategy.OnPush,
36
+ encapsulation: ViewEncapsulation.None,
37
+ hostDirectives: [CdkTrapFocus],
38
+ host: {
39
+ role: 'dialog',
40
+ 'aria-modal': 'true',
41
+ },
42
+ styles: [`
43
+ sng-drawer-content {
44
+ display: contents;
45
+ }
46
+ /* Overlay fade */
47
+ .sng-drawer-overlay[data-state=open] { animation: sng-drawer-fade-in var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
48
+ .sng-drawer-overlay[data-state=closed] { animation: sng-drawer-fade-out var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
49
+ @keyframes sng-drawer-fade-in { from { opacity: 0; } }
50
+ @keyframes sng-drawer-fade-out { to { opacity: 0; } }
51
+ /* Content slide per side */
52
+ .sng-drawer-panel[data-state=open][data-side=top] { animation: sng-drawer-in-top var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
53
+ .sng-drawer-panel[data-state=closed][data-side=top] { animation: sng-drawer-out-top var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
54
+ .sng-drawer-panel[data-state=open][data-side=bottom] { animation: sng-drawer-in-bottom var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
55
+ .sng-drawer-panel[data-state=closed][data-side=bottom] { animation: sng-drawer-out-bottom var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
56
+ .sng-drawer-panel[data-state=open][data-side=left] { animation: sng-drawer-in-left var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
57
+ .sng-drawer-panel[data-state=closed][data-side=left] { animation: sng-drawer-out-left var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
58
+ .sng-drawer-panel[data-state=open][data-side=right] { animation: sng-drawer-in-right var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
59
+ .sng-drawer-panel[data-state=closed][data-side=right] { animation: sng-drawer-out-right var(--sng-drawer-duration, 300ms) var(--sng-drawer-ease, ease) both; }
60
+ @keyframes sng-drawer-in-top { from { transform: translateY(-100%); } }
61
+ @keyframes sng-drawer-out-top { to { transform: translateY(-100%); } }
62
+ @keyframes sng-drawer-in-bottom { from { transform: translateY(100%); } }
63
+ @keyframes sng-drawer-out-bottom { to { transform: translateY(100%); } }
64
+ @keyframes sng-drawer-in-left { from { transform: translateX(-100%); } }
65
+ @keyframes sng-drawer-out-left { to { transform: translateX(-100%); } }
66
+ @keyframes sng-drawer-in-right { from { transform: translateX(100%); } }
67
+ @keyframes sng-drawer-out-right { to { transform: translateX(100%); } }
68
+ `],
69
+ template: `
70
+ <!-- Backdrop overlay (decorative) -->
71
+ <div
72
+ aria-hidden="true"
73
+ [class]="overlayClasses()"
74
+ [attr.data-state]="state()"
75
+ (click)="onOverlayClick()"
76
+ ></div>
77
+
78
+ <!-- Drawer content panel -->
79
+ <div
80
+ [class]="contentClasses()"
81
+ [attr.data-state]="state()"
82
+ [attr.data-side]="side()"
83
+ role="document"
84
+ >
85
+ <ng-content />
86
+ </div>
87
+ `,
88
+ })
89
+ export class SngDrawerContent implements AfterViewInit {
90
+ private drawer = inject(SNG_DRAWER_INSTANCE, { optional: true });
91
+ private closeFn = inject(SNG_DRAWER_CLOSE, { optional: true });
92
+ private focusTrap = inject(CdkTrapFocus);
93
+
94
+ /** Custom CSS classes. */
95
+ class = input<string>('');
96
+
97
+ ngAfterViewInit(): void {
98
+ // Auto-focus the first tabbable element
99
+ this.focusTrap.focusTrap.focusInitialElementWhenReady();
100
+ }
101
+
102
+ /** @internal Side for data-side attribute binding */
103
+ side = computed(() => this.drawer?.side() ?? 'bottom');
104
+ private isModal = computed(() => this.drawer?.modal() ?? false);
105
+ state = computed(() => this.drawer?.isOpen() ? 'open' : 'closed');
106
+
107
+ onOverlayClick(): void {
108
+ this.closeFn?.();
109
+ }
110
+
111
+ overlayClasses = computed(() =>
112
+ cn(
113
+ 'fixed inset-0 z-50 sng-drawer-overlay',
114
+ // Modal mode uses darker backdrop like Sheet, regular drawer uses light backdrop
115
+ this.isModal() ? 'bg-black/50' : 'bg-black/8',
116
+ )
117
+ );
118
+
119
+ // Side-specific positioning (layout only, animations handled by CSS keyframes)
120
+ // Note: No max-w constraint so user can override with custom width via class input
121
+ private sideClasses: Record<SngDrawerSide, string> = {
122
+ top: 'inset-x-0 top-0 max-h-[80vh] rounded-b-lg border-b',
123
+ bottom: 'inset-x-0 bottom-0 max-h-[80vh] rounded-t-lg border-t',
124
+ left: 'inset-y-0 left-0 h-full w-full sm:w-3/4 border-r',
125
+ right: 'inset-y-0 right-0 h-full w-full sm:w-3/4 border-l',
126
+ };
127
+
128
+ contentClasses = computed(() =>
129
+ cn(
130
+ 'fixed z-50 flex flex-col bg-background shadow-lg sng-drawer-panel',
131
+ this.sideClasses[this.side()],
132
+ this.class()
133
+ )
134
+ );
135
+ }
@@ -0,0 +1,29 @@
1
+ import { Directive, input, computed } from '@angular/core';
2
+ import { cn } from './cn';
3
+
4
+ /**
5
+ * Description text for the drawer header.
6
+ * Renders as muted, smaller text below the title.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <sng-drawer-description>
11
+ * Set your daily activity goal to track your progress.
12
+ * </sng-drawer-description>
13
+ * ```
14
+ */
15
+ @Directive({
16
+ selector: 'sng-drawer-description',
17
+ standalone: true,
18
+ host: {
19
+ '[class]': 'hostClasses()',
20
+ },
21
+ })
22
+ export class SngDrawerDescription {
23
+ /** Custom CSS classes. */
24
+ class = input<string>('');
25
+
26
+ hostClasses = computed(() =>
27
+ cn('text-sm text-muted-foreground', this.class())
28
+ );
29
+ }
@@ -0,0 +1,34 @@
1
+ import { Directive, input, computed } from '@angular/core';
2
+ import { cn } from './cn';
3
+
4
+ /**
5
+ * Footer section for the drawer, typically containing action buttons.
6
+ * Positioned at the bottom with auto margin.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <sng-drawer-footer>
11
+ * <sng-drawer-close>
12
+ * <sng-button class="border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground">Cancel</sng-button>
13
+ * </sng-drawer-close>
14
+ * <sng-drawer-close>
15
+ * <sng-button class="bg-primary text-primary-foreground shadow-xs hover:bg-primary/90">Submit</sng-button>
16
+ * </sng-drawer-close>
17
+ * </sng-drawer-footer>
18
+ * ```
19
+ */
20
+ @Directive({
21
+ selector: 'sng-drawer-footer',
22
+ standalone: true,
23
+ host: {
24
+ '[class]': 'hostClasses()',
25
+ },
26
+ })
27
+ export class SngDrawerFooter {
28
+ /** Custom CSS classes. */
29
+ class = input<string>('');
30
+
31
+ hostClasses = computed(() =>
32
+ cn('mt-auto flex flex-col-reverse gap-2 p-4 sm:flex-row sm:justify-center', this.class())
33
+ );
34
+ }
@@ -0,0 +1,30 @@
1
+ import { Directive, input, computed } from '@angular/core';
2
+ import { cn } from './cn';
3
+
4
+ /**
5
+ * Visual drag handle indicator for the drawer.
6
+ * Typically placed at the top of drawer content to indicate drag capability.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <sng-drawer-content>
11
+ * <sng-drawer-handle></sng-drawer-handle>
12
+ * <sng-drawer-header>...</sng-drawer-header>
13
+ * </sng-drawer-content>
14
+ * ```
15
+ */
16
+ @Directive({
17
+ selector: 'sng-drawer-handle',
18
+ standalone: true,
19
+ host: {
20
+ '[class]': 'hostClasses()',
21
+ },
22
+ })
23
+ export class SngDrawerHandle {
24
+ /** Custom CSS classes. */
25
+ class = input<string>('');
26
+
27
+ hostClasses = computed(() =>
28
+ cn('bg-muted mx-auto mt-4 h-2 w-[100px] shrink-0 rounded-full', this.class())
29
+ );
30
+ }
@@ -0,0 +1,30 @@
1
+ import { Directive, input, computed } from '@angular/core';
2
+ import { cn } from './cn';
3
+
4
+ /**
5
+ * Header section for the drawer containing title and description.
6
+ * Centers text on mobile and aligns left on larger screens.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <sng-drawer-header>
11
+ * <sng-drawer-title>Move Goal</sng-drawer-title>
12
+ * <sng-drawer-description>Set your daily activity goal.</sng-drawer-description>
13
+ * </sng-drawer-header>
14
+ * ```
15
+ */
16
+ @Directive({
17
+ selector: 'sng-drawer-header',
18
+ standalone: true,
19
+ host: {
20
+ '[class]': 'hostClasses()',
21
+ },
22
+ })
23
+ export class SngDrawerHeader {
24
+ /** Custom CSS classes. */
25
+ class = input<string>('');
26
+
27
+ hostClasses = computed(() =>
28
+ cn('flex flex-col gap-1.5 p-4 text-center sm:text-left', this.class())
29
+ );
30
+ }
@@ -0,0 +1,27 @@
1
+ import { Directive, input, computed } from '@angular/core';
2
+ import { cn } from './cn';
3
+
4
+ /**
5
+ * Title element for the drawer header.
6
+ * Renders as a semibold text element.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <sng-drawer-title>Settings</sng-drawer-title>
11
+ * ```
12
+ */
13
+ @Directive({
14
+ selector: 'sng-drawer-title',
15
+ standalone: true,
16
+ host: {
17
+ '[class]': 'hostClasses()',
18
+ },
19
+ })
20
+ export class SngDrawerTitle {
21
+ /** Custom CSS classes. */
22
+ class = input<string>('');
23
+
24
+ hostClasses = computed(() =>
25
+ cn('text-foreground font-semibold', this.class())
26
+ );
27
+ }
@@ -0,0 +1,21 @@
1
+ import { Directive } from '@angular/core';
2
+
3
+ /**
4
+ * Marker directive for drawer trigger elements.
5
+ * Apply to buttons or other interactive elements that open the drawer.
6
+ *
7
+ * @example
8
+ * ```html
9
+ * <sng-drawer-trigger (click)="drawer.open(content)">
10
+ * Open Drawer
11
+ * </sng-drawer-trigger>
12
+ * ```
13
+ */
14
+ @Directive({
15
+ selector: 'sng-drawer-trigger',
16
+ standalone: true,
17
+ host: {
18
+ 'class': 'contents',
19
+ },
20
+ })
21
+ export class SngDrawerTrigger {}
@@ -0,0 +1,27 @@
1
+ import { Directive, ElementRef, inject } from '@angular/core';
2
+
3
+ /**
4
+ * Wrapper directive that enables background scaling animation.
5
+ * Apply to the main content container that should scale when drawer opens.
6
+ *
7
+ * @example
8
+ * ```html
9
+ * <sng-drawer-wrapper class="min-h-screen">
10
+ * <app-header />
11
+ * <main>
12
+ * <sng-drawer>...</sng-drawer>
13
+ * </main>
14
+ * </sng-drawer-wrapper>
15
+ * ```
16
+ */
17
+ @Directive({
18
+ selector: 'sng-drawer-wrapper',
19
+ standalone: true,
20
+ host: {
21
+ 'class': 'block bg-background',
22
+ },
23
+ })
24
+ export class SngDrawerWrapper {
25
+ /** Reference to the host element for applying scale transforms */
26
+ elementRef = inject(ElementRef<HTMLElement>);
27
+ }
@@ -0,0 +1,166 @@
1
+ import {
2
+ Component,
3
+ signal,
4
+ input,
5
+ booleanAttribute,
6
+ ChangeDetectionStrategy,
7
+ TemplateRef,
8
+ ViewContainerRef,
9
+ inject,
10
+ Injector,
11
+ InjectionToken,
12
+ OnDestroy,
13
+ Renderer2,
14
+ contentChild,
15
+ } from '@angular/core';
16
+ import { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';
17
+ import { TemplatePortal } from '@angular/cdk/portal';
18
+ import { Subscription } from 'rxjs';
19
+ import { DOCUMENT } from '@angular/common';
20
+ import { SngDrawerWrapper } from './sng-drawer-wrapper';
21
+
22
+ /** Side from which the drawer slides in */
23
+ export type SngDrawerSide = 'top' | 'bottom' | 'left' | 'right';
24
+
25
+ /** Injection token providing a function to close the drawer */
26
+ export const SNG_DRAWER_CLOSE = new InjectionToken<() => void>('SNG_DRAWER_CLOSE');
27
+
28
+ /** Injection token providing access to the parent SngDrawer instance */
29
+ export const SNG_DRAWER_INSTANCE = new InjectionToken<SngDrawer>('SNG_DRAWER_INSTANCE');
30
+
31
+ /** Touch-friendly modal dialog that slides in from screen edges via CDK Overlay. */
32
+ @Component({
33
+ selector: 'sng-drawer',
34
+ standalone: true,
35
+ changeDetection: ChangeDetectionStrategy.OnPush,
36
+ host: {
37
+ 'class': 'contents',
38
+ },
39
+ template: `<ng-content />`,
40
+ })
41
+ export class SngDrawer implements OnDestroy {
42
+ /** Side from which the drawer appears. */
43
+ side = input<SngDrawerSide>('bottom');
44
+
45
+ /** Whether to scale and round the background content when drawer opens. */
46
+ shouldScaleBackground = input(true, { transform: booleanAttribute });
47
+
48
+ /** When true, uses a simple modal backdrop without background scaling. */
49
+ modal = input(false, { transform: booleanAttribute });
50
+
51
+ private overlay = inject(Overlay);
52
+ private viewContainerRef = inject(ViewContainerRef);
53
+ private renderer = inject(Renderer2);
54
+ private document = inject(DOCUMENT);
55
+ private overlayRef: OverlayRef | null = null;
56
+ private subscriptions = new Subscription();
57
+ private _closing = false;
58
+
59
+ /** @internal */
60
+ private wrapperDirective = contentChild(SngDrawerWrapper);
61
+
62
+ isOpen = signal(false);
63
+
64
+ /** Opens the drawer with the given template. Animation lifecycle only -- no business logic here. */
65
+ open(template: TemplateRef<unknown>) {
66
+ if (this.overlayRef || this._closing) return;
67
+
68
+ const config = new OverlayConfig({
69
+ hasBackdrop: false, // We handle backdrop in sng-drawer-content template
70
+ panelClass: 'sng-drawer-panel',
71
+ positionStrategy: this.overlay.position().global(),
72
+ scrollStrategy: this.overlay.scrollStrategies.block(),
73
+ });
74
+
75
+ this.overlayRef = this.overlay.create(config);
76
+ const injector = Injector.create({
77
+ providers: [
78
+ { provide: SNG_DRAWER_CLOSE, useValue: () => this.close() },
79
+ { provide: SNG_DRAWER_INSTANCE, useValue: this },
80
+ ],
81
+ parent: this.viewContainerRef.injector,
82
+ });
83
+ const portal = new TemplatePortal(template, this.viewContainerRef, null, injector);
84
+ this.overlayRef.attach(portal);
85
+ this.isOpen.set(true);
86
+
87
+ // Only scale background if not in modal mode and shouldScaleBackground is true
88
+ if (!this.modal() && this.shouldScaleBackground()) {
89
+ this.scaleBackground();
90
+ }
91
+
92
+ this.subscriptions.add(
93
+ this.overlayRef.backdropClick().subscribe(() => this.close())
94
+ );
95
+ }
96
+
97
+ private scaleBackground() {
98
+ const wrapper = this.getWrapper();
99
+ if (!wrapper) return;
100
+
101
+ this.renderer.setStyle(wrapper, 'transition', 'transform 0.5s cubic-bezier(0.32, 0.72, 0, 1), border-radius 0.5s cubic-bezier(0.32, 0.72, 0, 1)');
102
+ this.renderer.setStyle(wrapper, 'transform-origin', 'center top');
103
+ this.renderer.setStyle(wrapper, 'transform', 'scale(0.95) translateY(10px)');
104
+ this.renderer.setStyle(wrapper, 'border-radius', '10px');
105
+ this.renderer.setStyle(this.document.body, 'background-color', 'black');
106
+ }
107
+
108
+ private resetBackground() {
109
+ const wrapper = this.getWrapper();
110
+ if (!wrapper) return;
111
+
112
+ this.renderer.removeStyle(wrapper, 'transition');
113
+ this.renderer.removeStyle(wrapper, 'transform-origin');
114
+ this.renderer.removeStyle(wrapper, 'transform');
115
+ this.renderer.removeStyle(wrapper, 'border-radius');
116
+ this.renderer.removeStyle(this.document.body, 'background-color');
117
+ }
118
+
119
+ private getWrapper(): HTMLElement | null {
120
+ return this.wrapperDirective()?.elementRef.nativeElement ||
121
+ this.document.querySelector('sng-drawer-wrapper') as HTMLElement;
122
+ }
123
+
124
+ /** Closes with exit animation, then disposes. Animation lifecycle only -- no business logic here. */
125
+ close() {
126
+ if (!this.overlayRef || this._closing) return;
127
+ this._closing = true;
128
+
129
+ this.isOpen.set(false);
130
+
131
+ if (!this.modal() && this.shouldScaleBackground()) {
132
+ this.resetBackground();
133
+ }
134
+
135
+ // Set data-state="closed" directly on DOM to trigger CSS exit animations
136
+ // immediately (Angular's signal binding will also set this on next CD — same value).
137
+ const panel = this.overlayRef.overlayElement;
138
+ panel.querySelectorAll('[data-state]').forEach(el =>
139
+ el.setAttribute('data-state', 'closed')
140
+ );
141
+
142
+ // getAnimations() flushes styles, so newly triggered animations are returned.
143
+ const animations = panel.getAnimations({ subtree: true });
144
+ if (animations.length > 0) {
145
+ Promise.allSettled(animations.map(animation => animation.finished)).finally(() => this.dispose());
146
+ } else {
147
+ this.dispose();
148
+ }
149
+ }
150
+
151
+ /** @internal Immediately removes the overlay from DOM. Called after exit animation completes. */
152
+ private dispose() {
153
+ this._closing = false;
154
+ this.subscriptions.unsubscribe();
155
+ this.subscriptions = new Subscription();
156
+ if (this.overlayRef) {
157
+ this.overlayRef.dispose();
158
+ this.overlayRef = null;
159
+ }
160
+ this.isOpen.set(false);
161
+ }
162
+
163
+ ngOnDestroy() {
164
+ this.dispose();
165
+ }
166
+ }
@@ -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 @@
1
+ export { SngFileInput } from './sng-file-input';