@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.
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/cli/sng-ui.js +331 -0
- package/ng-package.json +29 -0
- package/package.json +64 -0
- package/registry.json +72 -0
- package/src/lib/accordion/cn.ts +6 -0
- package/src/lib/accordion/index.ts +18 -0
- package/src/lib/accordion/sng-accordion-content.ts +131 -0
- package/src/lib/accordion/sng-accordion-item.ts +299 -0
- package/src/lib/accordion/sng-accordion-trigger.ts +137 -0
- package/src/lib/accordion/sng-accordion.ts +118 -0
- package/src/lib/accordion/sng-accordion.types.ts +82 -0
- package/src/lib/alert/cn.ts +6 -0
- package/src/lib/alert/index.ts +3 -0
- package/src/lib/alert/sng-alert-description.ts +49 -0
- package/src/lib/alert/sng-alert-title.ts +46 -0
- package/src/lib/alert/sng-alert.ts +48 -0
- package/src/lib/avatar/cn.ts +6 -0
- package/src/lib/avatar/index.ts +3 -0
- package/src/lib/avatar/sng-avatar-fallback.ts +50 -0
- package/src/lib/avatar/sng-avatar-image.ts +73 -0
- package/src/lib/avatar/sng-avatar.ts +60 -0
- package/src/lib/badge/cn.ts +6 -0
- package/src/lib/badge/index.ts +1 -0
- package/src/lib/badge/sng-badge.ts +36 -0
- package/src/lib/breadcrumb/cn.ts +6 -0
- package/src/lib/breadcrumb/index.ts +7 -0
- package/src/lib/breadcrumb/sng-breadcrumb-ellipsis.ts +61 -0
- package/src/lib/breadcrumb/sng-breadcrumb-item.ts +47 -0
- package/src/lib/breadcrumb/sng-breadcrumb-link.ts +43 -0
- package/src/lib/breadcrumb/sng-breadcrumb-list.ts +42 -0
- package/src/lib/breadcrumb/sng-breadcrumb-page.ts +44 -0
- package/src/lib/breadcrumb/sng-breadcrumb-separator.ts +60 -0
- package/src/lib/breadcrumb/sng-breadcrumb.ts +52 -0
- package/src/lib/button/cn.ts +6 -0
- package/src/lib/button/index.ts +2 -0
- package/src/lib/button/sng-button.ts +264 -0
- package/src/lib/calendar/cn.ts +6 -0
- package/src/lib/calendar/index.ts +2 -0
- package/src/lib/calendar/sng-calendar.ts +753 -0
- package/src/lib/card/cn.ts +6 -0
- package/src/lib/card/index.ts +6 -0
- package/src/lib/card/sng-card-content.ts +36 -0
- package/src/lib/card/sng-card-description.ts +38 -0
- package/src/lib/card/sng-card-footer.ts +34 -0
- package/src/lib/card/sng-card-header.ts +34 -0
- package/src/lib/card/sng-card-title.ts +48 -0
- package/src/lib/card/sng-card.ts +43 -0
- package/src/lib/carousel/cn.ts +6 -0
- package/src/lib/carousel/index.ts +18 -0
- package/src/lib/carousel/sng-carousel.ts +526 -0
- package/src/lib/checkbox/cn.ts +6 -0
- package/src/lib/checkbox/index.ts +1 -0
- package/src/lib/checkbox/sng-checkbox.ts +154 -0
- package/src/lib/code-block/cn.ts +6 -0
- package/src/lib/code-block/index.ts +1 -0
- package/src/lib/code-block/sng-code-block.ts +296 -0
- package/src/lib/dialog/cn.ts +6 -0
- package/src/lib/dialog/index.ts +37 -0
- package/src/lib/dialog/sng-dialog-close.ts +76 -0
- package/src/lib/dialog/sng-dialog-content.ts +132 -0
- package/src/lib/dialog/sng-dialog-description.ts +36 -0
- package/src/lib/dialog/sng-dialog-footer.ts +39 -0
- package/src/lib/dialog/sng-dialog-header.ts +39 -0
- package/src/lib/dialog/sng-dialog-title.ts +52 -0
- package/src/lib/dialog/sng-dialog.service.ts +222 -0
- package/src/lib/dialog/sng-dialog.ts +224 -0
- package/src/lib/drawer/cn.ts +6 -0
- package/src/lib/drawer/index.ts +36 -0
- package/src/lib/drawer/sng-drawer-close.ts +28 -0
- package/src/lib/drawer/sng-drawer-content.ts +135 -0
- package/src/lib/drawer/sng-drawer-description.ts +29 -0
- package/src/lib/drawer/sng-drawer-footer.ts +34 -0
- package/src/lib/drawer/sng-drawer-handle.ts +30 -0
- package/src/lib/drawer/sng-drawer-header.ts +30 -0
- package/src/lib/drawer/sng-drawer-title.ts +27 -0
- package/src/lib/drawer/sng-drawer-trigger.ts +21 -0
- package/src/lib/drawer/sng-drawer-wrapper.ts +27 -0
- package/src/lib/drawer/sng-drawer.ts +166 -0
- package/src/lib/file-input/cn.ts +6 -0
- package/src/lib/file-input/index.ts +1 -0
- package/src/lib/file-input/sng-file-input.ts +288 -0
- package/src/lib/hover-card/cn.ts +6 -0
- package/src/lib/hover-card/index.ts +3 -0
- package/src/lib/hover-card/sng-hover-card-content.ts +100 -0
- package/src/lib/hover-card/sng-hover-card-trigger.ts +43 -0
- package/src/lib/hover-card/sng-hover-card.ts +246 -0
- package/src/lib/input/cn.ts +6 -0
- package/src/lib/input/index.ts +1 -0
- package/src/lib/input/sng-input.ts +160 -0
- package/src/lib/layout/cn.ts +6 -0
- package/src/lib/layout/index.ts +98 -0
- package/src/lib/layout/sng-layout-footer.ts +37 -0
- package/src/lib/layout/sng-layout-header.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-content.ts +149 -0
- package/src/lib/layout/sng-layout-sidebar-footer.ts +54 -0
- package/src/lib/layout/sng-layout-sidebar-group-action.ts +67 -0
- package/src/lib/layout/sng-layout-sidebar-group-content.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-group-label.ts +53 -0
- package/src/lib/layout/sng-layout-sidebar-group.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-header.ts +54 -0
- package/src/lib/layout/sng-layout-sidebar-input.ts +112 -0
- package/src/lib/layout/sng-layout-sidebar-inset.ts +45 -0
- package/src/lib/layout/sng-layout-sidebar-menu-action.ts +84 -0
- package/src/lib/layout/sng-layout-sidebar-menu-badge.ts +47 -0
- package/src/lib/layout/sng-layout-sidebar-menu-button.ts +160 -0
- package/src/lib/layout/sng-layout-sidebar-menu-item.ts +40 -0
- package/src/lib/layout/sng-layout-sidebar-menu-skeleton.ts +71 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub-button.ts +142 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub-item.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub.ts +48 -0
- package/src/lib/layout/sng-layout-sidebar-menu.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-provider.ts +189 -0
- package/src/lib/layout/sng-layout-sidebar-rail.ts +60 -0
- package/src/lib/layout/sng-layout-sidebar-separator.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-trigger.ts +97 -0
- package/src/lib/layout/sng-layout-sidebar.ts +254 -0
- package/src/lib/menu/cn.ts +6 -0
- package/src/lib/menu/index.ts +21 -0
- package/src/lib/menu/sng-context-trigger.ts +128 -0
- package/src/lib/menu/sng-menu-checkbox-item.ts +91 -0
- package/src/lib/menu/sng-menu-item.ts +80 -0
- package/src/lib/menu/sng-menu-label.ts +47 -0
- package/src/lib/menu/sng-menu-radio-group.ts +38 -0
- package/src/lib/menu/sng-menu-radio-item.ts +94 -0
- package/src/lib/menu/sng-menu-separator.ts +27 -0
- package/src/lib/menu/sng-menu-shortcut.ts +25 -0
- package/src/lib/menu/sng-menu-sub-content.ts +267 -0
- package/src/lib/menu/sng-menu-sub-trigger.ts +68 -0
- package/src/lib/menu/sng-menu-sub.ts +124 -0
- package/src/lib/menu/sng-menu-tokens.ts +52 -0
- package/src/lib/menu/sng-menu-trigger.ts +266 -0
- package/src/lib/menu/sng-menu.ts +100 -0
- package/src/lib/nav-menu/cn.ts +6 -0
- package/src/lib/nav-menu/index.ts +6 -0
- package/src/lib/nav-menu/sng-nav-menu-content.ts +72 -0
- package/src/lib/nav-menu/sng-nav-menu-item.ts +109 -0
- package/src/lib/nav-menu/sng-nav-menu-link.ts +54 -0
- package/src/lib/nav-menu/sng-nav-menu-list.ts +43 -0
- package/src/lib/nav-menu/sng-nav-menu-trigger.ts +98 -0
- package/src/lib/nav-menu/sng-nav-menu.ts +99 -0
- package/src/lib/otp-input/cn.ts +6 -0
- package/src/lib/otp-input/index.ts +14 -0
- package/src/lib/otp-input/sng-otp-input-group.ts +38 -0
- package/src/lib/otp-input/sng-otp-input-separator.ts +43 -0
- package/src/lib/otp-input/sng-otp-input-slot.ts +128 -0
- package/src/lib/otp-input/sng-otp-input-tokens.ts +20 -0
- package/src/lib/otp-input/sng-otp-input.ts +301 -0
- package/src/lib/popover/cn.ts +6 -0
- package/src/lib/popover/index.ts +3 -0
- package/src/lib/popover/sng-popover-content.ts +66 -0
- package/src/lib/popover/sng-popover-trigger.ts +44 -0
- package/src/lib/popover/sng-popover.ts +218 -0
- package/src/lib/preview-box/cn.ts +6 -0
- package/src/lib/preview-box/index.ts +5 -0
- package/src/lib/preview-box/sng-code-block.ts +80 -0
- package/src/lib/preview-box/sng-html-block.ts +79 -0
- package/src/lib/preview-box/sng-preview-block.ts +47 -0
- package/src/lib/preview-box/sng-preview-box.ts +369 -0
- package/src/lib/preview-box/sng-style-block.ts +80 -0
- package/src/lib/progress/cn.ts +6 -0
- package/src/lib/progress/index.ts +1 -0
- package/src/lib/progress/sng-progress.ts +65 -0
- package/src/lib/radio/cn.ts +6 -0
- package/src/lib/radio/index.ts +5 -0
- package/src/lib/radio/sng-radio-item.ts +100 -0
- package/src/lib/radio/sng-radio.ts +54 -0
- package/src/lib/resizable/cn.ts +6 -0
- package/src/lib/resizable/index.ts +3 -0
- package/src/lib/resizable/sng-resizable-group.ts +188 -0
- package/src/lib/resizable/sng-resizable-handle.ts +236 -0
- package/src/lib/resizable/sng-resizable-panel.ts +71 -0
- package/src/lib/search-input/cn.ts +6 -0
- package/src/lib/search-input/index.ts +16 -0
- package/src/lib/search-input/sng-search-input-context.ts +24 -0
- package/src/lib/search-input/sng-search-input-empty.ts +42 -0
- package/src/lib/search-input/sng-search-input-group.ts +69 -0
- package/src/lib/search-input/sng-search-input-item.ts +164 -0
- package/src/lib/search-input/sng-search-input-list.ts +34 -0
- package/src/lib/search-input/sng-search-input-separator.ts +32 -0
- package/src/lib/search-input/sng-search-input-shortcut.ts +29 -0
- package/src/lib/search-input/sng-search-input.ts +368 -0
- package/src/lib/select/cn.ts +6 -0
- package/src/lib/select/index.ts +7 -0
- package/src/lib/select/sng-select-content.ts +27 -0
- package/src/lib/select/sng-select-empty.ts +48 -0
- package/src/lib/select/sng-select-group.ts +29 -0
- package/src/lib/select/sng-select-item.ts +140 -0
- package/src/lib/select/sng-select-label.ts +29 -0
- package/src/lib/select/sng-select-separator.ts +29 -0
- package/src/lib/select/sng-select.ts +326 -0
- package/src/lib/separator/cn.ts +6 -0
- package/src/lib/separator/index.ts +1 -0
- package/src/lib/separator/sng-separator.ts +40 -0
- package/src/lib/skeleton/cn.ts +6 -0
- package/src/lib/skeleton/index.ts +1 -0
- package/src/lib/skeleton/sng-skeleton.ts +49 -0
- package/src/lib/slider/cn.ts +6 -0
- package/src/lib/slider/index.ts +2 -0
- package/src/lib/slider/sng-slider.ts +137 -0
- package/src/lib/sng-table/cn.ts +6 -0
- package/src/lib/sng-table/flex-render.ts +222 -0
- package/src/lib/sng-table/index.ts +85 -0
- package/src/lib/sng-table/sng-table-body.ts +59 -0
- package/src/lib/sng-table/sng-table-caption.ts +49 -0
- package/src/lib/sng-table/sng-table-cell.ts +62 -0
- package/src/lib/sng-table/sng-table-footer.ts +60 -0
- package/src/lib/sng-table/sng-table-head.ts +66 -0
- package/src/lib/sng-table/sng-table-header.ts +48 -0
- package/src/lib/sng-table/sng-table-pagination.ts +265 -0
- package/src/lib/sng-table/sng-table-row.ts +65 -0
- package/src/lib/sng-table/sng-table.ts +67 -0
- package/src/lib/sng-table-core/core/create-cell.ts +117 -0
- package/src/lib/sng-table-core/core/create-column.ts +266 -0
- package/src/lib/sng-table-core/core/create-header.ts +271 -0
- package/src/lib/sng-table-core/core/create-row.ts +293 -0
- package/src/lib/sng-table-core/core/create-table.ts +534 -0
- package/src/lib/sng-table-core/core/types.ts +1197 -0
- package/src/lib/sng-table-core/core/utils.ts +307 -0
- package/src/lib/sng-table-core/features/column-filtering.ts +376 -0
- package/src/lib/sng-table-core/features/column-ordering.ts +159 -0
- package/src/lib/sng-table-core/features/column-pinning.ts +219 -0
- package/src/lib/sng-table-core/features/column-sizing.ts +268 -0
- package/src/lib/sng-table-core/features/column-visibility.ts +128 -0
- package/src/lib/sng-table-core/features/faceting.ts +279 -0
- package/src/lib/sng-table-core/features/fuzzy-filtering.ts +188 -0
- package/src/lib/sng-table-core/features/global-filtering.ts +128 -0
- package/src/lib/sng-table-core/features/pagination.ts +179 -0
- package/src/lib/sng-table-core/features/row-expanding.ts +181 -0
- package/src/lib/sng-table-core/features/row-grouping.ts +235 -0
- package/src/lib/sng-table-core/features/row-pinning.ts +196 -0
- package/src/lib/sng-table-core/features/row-selection.ts +298 -0
- package/src/lib/sng-table-core/features/sorting.ts +425 -0
- package/src/lib/sng-table-core/features/virtualization.ts +298 -0
- package/src/lib/sng-table-core/index.ts +235 -0
- package/src/lib/sng-table-core/row-models/core-row-model.ts +256 -0
- package/src/lib/sng-table-core/row-models/expanded-row-model.ts +175 -0
- package/src/lib/sng-table-core/row-models/filtered-row-model.ts +307 -0
- package/src/lib/sng-table-core/row-models/grouped-row-model.ts +290 -0
- package/src/lib/sng-table-core/row-models/paginated-row-model.ts +135 -0
- package/src/lib/sng-table-core/row-models/sorted-row-model.ts +197 -0
- package/src/lib/styles/sng-themes.css +164 -0
- package/src/lib/switch/cn.ts +6 -0
- package/src/lib/switch/index.ts +1 -0
- package/src/lib/switch/sng-switch.ts +137 -0
- package/src/lib/tabs/cn.ts +6 -0
- package/src/lib/tabs/index.ts +4 -0
- package/src/lib/tabs/sng-tabs-content.ts +66 -0
- package/src/lib/tabs/sng-tabs-list.ts +55 -0
- package/src/lib/tabs/sng-tabs-trigger.ts +86 -0
- package/src/lib/tabs/sng-tabs.ts +83 -0
- package/src/lib/toast/cn.ts +6 -0
- package/src/lib/toast/index.ts +3 -0
- package/src/lib/toast/sng-toast.service.ts +258 -0
- package/src/lib/toast/sng-toast.ts +101 -0
- package/src/lib/toast/sng-toaster.ts +67 -0
- package/src/lib/toggle/cn.ts +6 -0
- package/src/lib/toggle/index.ts +6 -0
- package/src/lib/toggle/sng-toggle-group-item.ts +89 -0
- package/src/lib/toggle/sng-toggle-group.ts +85 -0
- package/src/lib/toggle/sng-toggle.ts +78 -0
- package/src/lib/toggle-group/index.ts +6 -0
- package/src/lib/tooltip/cn.ts +6 -0
- package/src/lib/tooltip/index.ts +5 -0
- package/src/lib/tooltip/sng-tooltip-content.ts +64 -0
- package/src/lib/tooltip/sng-tooltip.ts +216 -0
- package/src/public-api.ts +207 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +17 -0
- 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 @@
|
|
|
1
|
+
export { SngFileInput } from './sng-file-input';
|