@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,160 @@
1
+ import {
2
+ Component,
3
+ computed,
4
+ input,
5
+ model,
6
+ booleanAttribute,
7
+ ChangeDetectionStrategy,
8
+ ViewEncapsulation,
9
+ viewChild,
10
+ ElementRef,
11
+ afterNextRender,
12
+ effect,
13
+ } from '@angular/core';
14
+ import { cn } from './cn';
15
+
16
+ /** Standard input types. */
17
+ export type SngInputType =
18
+ | 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'
19
+ | 'date' | 'time' | 'datetime-local';
20
+
21
+ /**
22
+ * Standard input component for text, email, password, number, and other basic input types.
23
+ *
24
+ * @example
25
+ * ```html
26
+ * <sng-input placeholder="Enter your email" type="email" />
27
+ * <sng-input type="password" [(value)]="password" />
28
+ * ```
29
+ */
30
+ @Component({
31
+ selector: 'sng-input',
32
+ standalone: true,
33
+ changeDetection: ChangeDetectionStrategy.OnPush,
34
+ encapsulation: ViewEncapsulation.None,
35
+ host: {
36
+ 'class': 'contents',
37
+ '[attr.id]': 'null',
38
+ '[attr.name]': 'null',
39
+ },
40
+ template: `
41
+ <input
42
+ #inputRef
43
+ [type]="type()"
44
+ [attr.id]="id()"
45
+ [attr.name]="name()"
46
+ [placeholder]="placeholder()"
47
+ [disabled]="disabled()"
48
+ [readonly]="readonly()"
49
+ [required]="required()"
50
+ [attr.min]="min()"
51
+ [attr.max]="max()"
52
+ [attr.minlength]="minlength()"
53
+ [attr.maxlength]="maxlength()"
54
+ [attr.pattern]="pattern()"
55
+ [attr.step]="step()"
56
+ [attr.autocomplete]="autocomplete()"
57
+ [value]="value()"
58
+ [class]="inputClasses()"
59
+ (input)="onInput($event)"
60
+ />
61
+ `,
62
+ })
63
+ export class SngInput {
64
+ private inputRef = viewChild<ElementRef<HTMLInputElement>>('inputRef');
65
+ private hasAppliedAutofocus = false;
66
+
67
+ /** Input type. */
68
+ type = input<SngInputType>('text');
69
+
70
+ /** Input id attribute. */
71
+ id = input<string>();
72
+
73
+ /** Input name attribute. */
74
+ name = input<string>();
75
+
76
+ /** Placeholder text. */
77
+ placeholder = input<string>('');
78
+
79
+ /** Whether the input is disabled. */
80
+ disabled = input(false, { transform: booleanAttribute });
81
+
82
+ /** Whether the input is readonly. */
83
+ readonly = input(false, { transform: booleanAttribute });
84
+
85
+ /** Whether the input is required. */
86
+ required = input(false, { transform: booleanAttribute });
87
+
88
+ /** Custom CSS classes. */
89
+ class = input<string>('');
90
+
91
+ /** Current value. Supports two-way binding via [(value)]. */
92
+ value = model<string | number>('');
93
+
94
+ /** Minimum value (for number/date inputs). */
95
+ min = input<string | number>();
96
+
97
+ /** Maximum value (for number/date inputs). */
98
+ max = input<string | number>();
99
+
100
+ /** Minimum length. */
101
+ minlength = input<number>();
102
+
103
+ /** Maximum length. */
104
+ maxlength = input<number>();
105
+
106
+ /** Pattern for validation. */
107
+ pattern = input<string>();
108
+
109
+ /** Step value (for number inputs). */
110
+ step = input<string | number>();
111
+
112
+ /** Autocomplete attribute. */
113
+ autocomplete = input<string>();
114
+
115
+ /** Whether to autofocus. */
116
+ autofocus = input(false, { transform: booleanAttribute });
117
+
118
+ constructor() {
119
+ effect(() => {
120
+ if (!this.autofocus() || this.hasAppliedAutofocus) {
121
+ return;
122
+ }
123
+ afterNextRender(() => {
124
+ this.inputRef()?.nativeElement.focus();
125
+ this.hasAppliedAutofocus = true;
126
+ });
127
+ });
128
+ }
129
+
130
+ /** @internal */
131
+ inputClasses = computed(() =>
132
+ cn(
133
+ 'flex w-full min-w-0 rounded-md border border-input bg-transparent',
134
+ 'h-9 text-sm px-3 py-1',
135
+ 'shadow-xs transition-[color,box-shadow] outline-none',
136
+ 'placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground',
137
+ 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
138
+ 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
139
+ 'dark:bg-input/30',
140
+ 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
141
+ this.class()
142
+ )
143
+ );
144
+
145
+ /** @internal */
146
+ onInput(event: Event): void {
147
+ const value = (event.target as HTMLInputElement).value;
148
+ this.value.set(value);
149
+ }
150
+
151
+ /** Focus the input element. */
152
+ focus(): void {
153
+ this.inputRef()?.nativeElement.focus();
154
+ }
155
+
156
+ /** Blur the input element. */
157
+ blur(): void {
158
+ this.inputRef()?.nativeElement.blur();
159
+ }
160
+ }
@@ -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,98 @@
1
+ /**
2
+ * @fileoverview sng-layout components
3
+ *
4
+ * Layout components for building page structures with header, footer, and sidebar.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import {
9
+ * SngLayoutHeader,
10
+ * SngLayoutFooter,
11
+ * SngLayoutSidebarProvider,
12
+ * SngLayoutSidebar,
13
+ * SngLayoutSidebarContent,
14
+ * SngLayoutSidebarMenu,
15
+ * SngLayoutSidebarMenuItem,
16
+ * SngLayoutSidebarMenuButton,
17
+ * SngLayoutSidebarInset,
18
+ * } from 'sng-ui';
19
+ *
20
+ * @Component({
21
+ * imports: [
22
+ * SngLayoutHeader,
23
+ * SngLayoutFooter,
24
+ * SngLayoutSidebarProvider,
25
+ * SngLayoutSidebar,
26
+ * SngLayoutSidebarContent,
27
+ * SngLayoutSidebarMenu,
28
+ * SngLayoutSidebarMenuItem,
29
+ * SngLayoutSidebarMenuButton,
30
+ * SngLayoutSidebarInset,
31
+ * ],
32
+ * template: `
33
+ * <sng-layout-sidebar-provider>
34
+ * <sng-layout-sidebar>
35
+ * <sng-layout-sidebar-content>
36
+ * <sng-layout-sidebar-menu>
37
+ * <sng-layout-sidebar-menu-item>
38
+ * <sng-layout-sidebar-menu-button routerLink="/home">Home</sng-layout-sidebar-menu-button>
39
+ * </sng-layout-sidebar-menu-item>
40
+ * </sng-layout-sidebar-menu>
41
+ * </sng-layout-sidebar-content>
42
+ * </sng-layout-sidebar>
43
+ * <sng-layout-sidebar-inset>
44
+ * <sng-layout-header>Header content</sng-layout-header>
45
+ * <main>Main content</main>
46
+ * <sng-layout-footer>Footer content</sng-layout-footer>
47
+ * </sng-layout-sidebar-inset>
48
+ * </sng-layout-sidebar-provider>
49
+ * `
50
+ * })
51
+ * export class MyLayoutComponent { }
52
+ * ```
53
+ */
54
+
55
+ // Header & Footer (can be used standalone)
56
+ export { SngLayoutHeader } from './sng-layout-header';
57
+ export { SngLayoutFooter } from './sng-layout-footer';
58
+
59
+ // Sidebar Provider & Context
60
+ export {
61
+ SngLayoutSidebarProvider,
62
+ SNG_LAYOUT_SIDEBAR_CONTEXT,
63
+ type LayoutSidebarContext,
64
+ type LayoutSidebarDirection,
65
+ } from './sng-layout-sidebar-provider';
66
+
67
+ // Sidebar Main Components
68
+ export {
69
+ SngLayoutSidebar,
70
+ type LayoutSidebarSide,
71
+ type LayoutSidebarCollapsible,
72
+ type LayoutSidebarLayout,
73
+ } from './sng-layout-sidebar';
74
+ export { SngLayoutSidebarHeader } from './sng-layout-sidebar-header';
75
+ export { SngLayoutSidebarContent } from './sng-layout-sidebar-content';
76
+ export { SngLayoutSidebarFooter } from './sng-layout-sidebar-footer';
77
+ export { SngLayoutSidebarInset } from './sng-layout-sidebar-inset';
78
+ export { SngLayoutSidebarTrigger } from './sng-layout-sidebar-trigger';
79
+ export { SngLayoutSidebarRail } from './sng-layout-sidebar-rail';
80
+ export { SngLayoutSidebarSeparator } from './sng-layout-sidebar-separator';
81
+ export { SngLayoutSidebarInput } from './sng-layout-sidebar-input';
82
+
83
+ // Sidebar Group Components
84
+ export { SngLayoutSidebarGroup } from './sng-layout-sidebar-group';
85
+ export { SngLayoutSidebarGroupLabel } from './sng-layout-sidebar-group-label';
86
+ export { SngLayoutSidebarGroupContent } from './sng-layout-sidebar-group-content';
87
+ export { SngLayoutSidebarGroupAction } from './sng-layout-sidebar-group-action';
88
+
89
+ // Sidebar Menu Components
90
+ export { SngLayoutSidebarMenu } from './sng-layout-sidebar-menu';
91
+ export { SngLayoutSidebarMenuItem } from './sng-layout-sidebar-menu-item';
92
+ export { SngLayoutSidebarMenuButton, type LayoutSidebarMenuButtonSize } from './sng-layout-sidebar-menu-button';
93
+ export { SngLayoutSidebarMenuSub } from './sng-layout-sidebar-menu-sub';
94
+ export { SngLayoutSidebarMenuSubItem } from './sng-layout-sidebar-menu-sub-item';
95
+ export { SngLayoutSidebarMenuSubButton } from './sng-layout-sidebar-menu-sub-button';
96
+ export { SngLayoutSidebarMenuAction } from './sng-layout-sidebar-menu-action';
97
+ export { SngLayoutSidebarMenuBadge } from './sng-layout-sidebar-menu-badge';
98
+ export { SngLayoutSidebarMenuSkeleton } from './sng-layout-sidebar-menu-skeleton';
@@ -0,0 +1,37 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ input,
5
+ computed,
6
+ } from '@angular/core';
7
+ import { cn } from './cn';
8
+
9
+ /**
10
+ * A flexible footer component for page layouts.
11
+ * Can be used standalone or within a full layout structure.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <sng-layout-footer>
16
+ * <span>&copy; 2024 Company</span>
17
+ * <nav>...</nav>
18
+ * </sng-layout-footer>
19
+ * ```
20
+ */
21
+ @Component({
22
+ selector: 'sng-layout-footer',
23
+ standalone: true,
24
+ changeDetection: ChangeDetectionStrategy.OnPush,
25
+ template: `<ng-content />`,
26
+ host: {
27
+ '[class]': 'hostClasses()',
28
+ },
29
+ })
30
+ export class SngLayoutFooter {
31
+ /** Custom CSS classes. */
32
+ class = input<string>('');
33
+
34
+ hostClasses = computed(() =>
35
+ cn('flex w-full items-center h-8 px-4 border-t border-border bg-background shrink-0', this.class())
36
+ );
37
+ }
@@ -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
+ * A flexible header component for page layouts.
11
+ * Can be used standalone or within a full layout structure.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <sng-layout-header>
16
+ * <img src="logo.svg" alt="Logo" />
17
+ * <nav>...</nav>
18
+ * <button>Login</button>
19
+ * </sng-layout-header>
20
+ * ```
21
+ */
22
+ @Component({
23
+ selector: 'sng-layout-header',
24
+ standalone: true,
25
+ changeDetection: ChangeDetectionStrategy.OnPush,
26
+ template: `<ng-content />`,
27
+ host: {
28
+ '[class]': 'hostClasses()',
29
+ },
30
+ })
31
+ export class SngLayoutHeader {
32
+ /** Custom CSS classes. */
33
+ class = input<string>('');
34
+
35
+ hostClasses = computed(() =>
36
+ cn('flex w-full items-center h-14 px-4 border-b border-border bg-background shrink-0', this.class())
37
+ );
38
+ }
@@ -0,0 +1,149 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ input,
5
+ computed,
6
+ signal,
7
+ OnDestroy,
8
+ } from '@angular/core';
9
+ import { Subscription, timer } from 'rxjs';
10
+ import { cn } from './cn';
11
+
12
+ /**
13
+ * Scrollable content area of the sidebar containing navigation groups and menus.
14
+ * Features an auto-hiding scrollbar for a cleaner appearance.
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <sng-layout-sidebar>
19
+ * <sng-layout-sidebar-header>...</sng-layout-sidebar-header>
20
+ * <sng-layout-sidebar-content [autoHideScrollbar]="2000">
21
+ * <sng-layout-sidebar-group>
22
+ * <sng-layout-sidebar-menu>...</sng-layout-sidebar-menu>
23
+ * </sng-layout-sidebar-group>
24
+ * </sng-layout-sidebar-content>
25
+ * <sng-layout-sidebar-footer>...</sng-layout-sidebar-footer>
26
+ * </sng-layout-sidebar>
27
+ * ```
28
+ */
29
+ @Component({
30
+ selector: 'sng-layout-sidebar-content',
31
+ standalone: true,
32
+ changeDetection: ChangeDetectionStrategy.OnPush,
33
+ template: `<ng-content />`,
34
+ host: {
35
+ '[class]': 'hostClasses()',
36
+ '[attr.data-scrollbar-visible]': 'scrollbarVisible()',
37
+ '(scroll)': 'onScroll()',
38
+ '(mouseenter)': 'onMouseEnter()',
39
+ '(mouseleave)': 'onMouseLeave()',
40
+ },
41
+ styles: `
42
+ :host {
43
+ /* Thin scrollbar for webkit browsers */
44
+ &::-webkit-scrollbar {
45
+ width: 6px;
46
+ }
47
+ &::-webkit-scrollbar-track {
48
+ background: transparent;
49
+ }
50
+ &::-webkit-scrollbar-thumb {
51
+ background-color: transparent;
52
+ border-radius: 3px;
53
+ transition: background-color 0.3s ease;
54
+ }
55
+
56
+ /* Firefox thin scrollbar - hidden by default */
57
+ scrollbar-width: thin;
58
+ scrollbar-color: transparent transparent;
59
+ transition: scrollbar-color 0.3s ease;
60
+
61
+ /* Show scrollbar when visible */
62
+ &[data-scrollbar-visible="true"]::-webkit-scrollbar-thumb {
63
+ background-color: var(--sidebar-border, var(--border));
64
+ }
65
+ &[data-scrollbar-visible="true"]::-webkit-scrollbar-thumb:hover {
66
+ background-color: var(--muted-foreground);
67
+ }
68
+ &[data-scrollbar-visible="true"] {
69
+ scrollbar-color: var(--sidebar-border, var(--border)) transparent;
70
+ }
71
+ }
72
+ `,
73
+ })
74
+ export class SngLayoutSidebarContent implements OnDestroy {
75
+ /** Custom CSS classes. */
76
+ class = input<string>('');
77
+
78
+ /**
79
+ * Auto-hide scrollbar after timeout (in milliseconds). Set to 0 to disable auto-hide.
80
+ */
81
+ autoHideScrollbar = input<number>(1500);
82
+
83
+ // Scrollbar visibility - computed based on autoHideScrollbar and manual state
84
+ private manualScrollbarVisible = signal(false);
85
+ scrollbarVisible = computed(() => {
86
+ // If auto-hide is disabled (timeout = 0), always show scrollbar
87
+ if (this.autoHideScrollbar() === 0) {
88
+ return true;
89
+ }
90
+ return this.manualScrollbarVisible();
91
+ });
92
+ private hideTimerSubscription: Subscription | null = null;
93
+ private isHovering = signal(false);
94
+
95
+ hostClasses = computed(() =>
96
+ cn(
97
+ 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
98
+ this.class()
99
+ )
100
+ );
101
+
102
+ onScroll() {
103
+ if (this.autoHideScrollbar() > 0) {
104
+ this.showScrollbar();
105
+ this.startHideTimer();
106
+ }
107
+ }
108
+
109
+ onMouseEnter() {
110
+ this.isHovering.set(true);
111
+ // Don't show scrollbar on hover - only show when scrolling
112
+ if (this.autoHideScrollbar() > 0) {
113
+ this.clearHideTimer();
114
+ }
115
+ }
116
+
117
+ onMouseLeave() {
118
+ this.isHovering.set(false);
119
+ if (this.autoHideScrollbar() > 0 && this.scrollbarVisible()) {
120
+ this.startHideTimer();
121
+ }
122
+ }
123
+
124
+ private showScrollbar() {
125
+ this.manualScrollbarVisible.set(true);
126
+ }
127
+
128
+ private startHideTimer() {
129
+ this.clearHideTimer();
130
+ const timeout = this.autoHideScrollbar();
131
+ if (timeout > 0) {
132
+ this.hideTimerSubscription = timer(timeout).subscribe(() => {
133
+ if (!this.isHovering()) {
134
+ this.manualScrollbarVisible.set(false);
135
+ }
136
+ this.hideTimerSubscription = null;
137
+ });
138
+ }
139
+ }
140
+
141
+ private clearHideTimer() {
142
+ this.hideTimerSubscription?.unsubscribe();
143
+ this.hideTimerSubscription = null;
144
+ }
145
+
146
+ ngOnDestroy() {
147
+ this.clearHideTimer();
148
+ }
149
+ }
@@ -0,0 +1,54 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ input,
5
+ computed,
6
+ ViewEncapsulation,
7
+ } from '@angular/core';
8
+ import { cn } from './cn';
9
+
10
+ /**
11
+ * Footer section of the sidebar, typically containing user info or actions.
12
+ * Positioned at the bottom of the sidebar.
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <sng-layout-sidebar>
17
+ * <sng-layout-sidebar-content>...</sng-layout-sidebar-content>
18
+ * <sng-layout-sidebar-footer>
19
+ * <img src="avatar.png" alt="User" />
20
+ * <span>John Doe</span>
21
+ * </sng-layout-sidebar-footer>
22
+ * </sng-layout-sidebar>
23
+ * ```
24
+ */
25
+ @Component({
26
+ selector: 'sng-layout-sidebar-footer',
27
+ standalone: true,
28
+ changeDetection: ChangeDetectionStrategy.OnPush,
29
+ encapsulation: ViewEncapsulation.None,
30
+ styles: [`
31
+ .group[data-collapsible='icon'] sng-layout-sidebar-footer > button {
32
+ width: 2rem !important;
33
+ height: 2rem !important;
34
+ padding: 0 !important;
35
+ }
36
+ `],
37
+ template: `<ng-content />`,
38
+ host: {
39
+ '[class]': 'hostClasses()',
40
+ },
41
+ })
42
+ export class SngLayoutSidebarFooter {
43
+ /** Custom CSS classes. */
44
+ class = input<string>('');
45
+
46
+ hostClasses = computed(() =>
47
+ cn(
48
+ 'flex flex-col gap-2 p-2 overflow-hidden',
49
+ // Icon mode: center content and auto-size buttons
50
+ 'group-data-[collapsible=icon]:p-2 group-data-[collapsible=icon]:items-center',
51
+ this.class()
52
+ )
53
+ );
54
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ ViewEncapsulation,
5
+ input,
6
+ computed,
7
+ booleanAttribute,
8
+ } from '@angular/core';
9
+ import { cn } from './cn';
10
+
11
+ /**
12
+ * Action button positioned in the top-right corner of a sidebar group.
13
+ * Typically used for adding new items or triggering group-level actions.
14
+ *
15
+ * @example
16
+ * ```html
17
+ * <sng-layout-sidebar-group>
18
+ * <sng-layout-sidebar-group-label>Projects</sng-layout-sidebar-group-label>
19
+ * <sng-layout-sidebar-group-action (click)="addProject()">
20
+ * <lucide-icon name="plus" />
21
+ * </sng-layout-sidebar-group-action>
22
+ * <sng-layout-sidebar-group-content>...</sng-layout-sidebar-group-content>
23
+ * </sng-layout-sidebar-group>
24
+ * ```
25
+ */
26
+ @Component({
27
+ selector: 'sng-layout-sidebar-group-action',
28
+ standalone: true,
29
+ changeDetection: ChangeDetectionStrategy.OnPush,
30
+ encapsulation: ViewEncapsulation.None,
31
+ styles: [`
32
+ sng-layout-sidebar-group-action > button > svg {
33
+ width: 1rem;
34
+ height: 1rem;
35
+ flex-shrink: 0;
36
+ }
37
+ `],
38
+ host: {
39
+ 'class': 'contents',
40
+ },
41
+ template: `
42
+ <button
43
+ type="button"
44
+ [class]="buttonClasses()"
45
+ [disabled]="disabled()"
46
+ >
47
+ <ng-content />
48
+ </button>
49
+ `,
50
+ })
51
+ export class SngLayoutSidebarGroupAction {
52
+ /** Custom CSS classes. */
53
+ class = input<string>('');
54
+
55
+ /** Whether the button is disabled. */
56
+ disabled = input(false, { transform: booleanAttribute });
57
+
58
+ buttonClasses = computed(() =>
59
+ cn(
60
+ 'cursor-pointer text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2',
61
+ // Increases the hit area of the button on mobile
62
+ 'after:absolute after:-inset-2 md:after:hidden',
63
+ 'group-data-[collapsible=icon]:hidden',
64
+ this.class()
65
+ )
66
+ );
67
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ input,
5
+ computed,
6
+ } from '@angular/core';
7
+ import { cn } from './cn';
8
+
9
+ /**
10
+ * Container for the menu items within a sidebar group.
11
+ * Wraps the menu and applies appropriate spacing.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <sng-layout-sidebar-group>
16
+ * <sng-layout-sidebar-group-label>Navigation</sng-layout-sidebar-group-label>
17
+ * <sng-layout-sidebar-group-content>
18
+ * <sng-layout-sidebar-menu>
19
+ * <sng-layout-sidebar-menu-item>...</sng-layout-sidebar-menu-item>
20
+ * </sng-layout-sidebar-menu>
21
+ * </sng-layout-sidebar-group-content>
22
+ * </sng-layout-sidebar-group>
23
+ * ```
24
+ */
25
+ @Component({
26
+ selector: 'sng-layout-sidebar-group-content',
27
+ standalone: true,
28
+ changeDetection: ChangeDetectionStrategy.OnPush,
29
+ template: `<ng-content />`,
30
+ host: {
31
+ '[class]': 'hostClasses()',
32
+ },
33
+ })
34
+ export class SngLayoutSidebarGroupContent {
35
+ /** Custom CSS classes. */
36
+ class = input<string>('');
37
+
38
+ hostClasses = computed(() =>
39
+ cn('w-full text-sm', this.class())
40
+ );
41
+ }