@frame-kit/ui-ng 0.0.7 → 0.1.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/COMPONENTS.md CHANGED
@@ -41,7 +41,6 @@ Organized by **purpose** (not Atomic Design) into six layers with strict depende
41
41
  - [fk-loader](#fk-loader)
42
42
  - [fk-menu-item](#fk-menu-item)
43
43
  - [fk-nav-brand](#fk-nav-brand)
44
- - [fk-nav-group](#fk-nav-group)
45
44
  - [fk-nav-separator](#fk-nav-separator)
46
45
  - [fk-node-tree](#fk-node-tree)
47
46
  - [fk-node-tree-breadcrumb](#fk-node-tree-breadcrumb)
@@ -49,7 +48,6 @@ Organized by **purpose** (not Atomic Design) into six layers with strict depende
49
48
  - [fk-numbered-list](#fk-numbered-list)
50
49
  - [fk-pagination](#fk-pagination)
51
50
  - [fk-progress-bar](#fk-progress-bar)
52
- - [fk-sidenav-link](#fk-sidenav-link)
53
51
  - [fk-tabs](#fk-tabs)
54
52
  - [fk-timeline](#fk-timeline)
55
53
  - [fk-toast](#fk-toast)
@@ -310,13 +308,6 @@ A brand/logo element used within navigation bars.
310
308
 
311
309
  ---
312
310
 
313
- ### fk-nav-group
314
-
315
- An expandable nav group that pairs a clickable trigger row (icon + label, optionally routed) with projected child links revealed when expanded. Designed to sit inside a collapsible shell so collapsed clicks request the outer shell to open.
316
- [README](https://github.com/frame-kit/packages/blob/main/packages/ui-ng/ui/nav-group/README.md)
317
-
318
- ---
319
-
320
311
  ### fk-nav-separator
321
312
 
322
313
  A token-driven separator for visually dividing groups of navigation items, with vertical/horizontal orientation and decorative/semantic accessibility modes.
@@ -366,13 +357,6 @@ A token-driven progress bar for indicating how far along a single task or proces
366
357
 
367
358
  ---
368
359
 
369
- ### fk-sidenav-link
370
-
371
- A token-driven sidebar navigation link that renders an icon and label, automatically hiding the label when the parent app-shell is collapsed.
372
- [README](https://github.com/frame-kit/packages/blob/main/packages/ui-ng/ui/sidenav-link/README.md)
373
-
374
- ---
375
-
376
360
  ### fk-tabs
377
361
 
378
362
  A compound component system for building accessible, keyboard-navigable tabbed interfaces. Supports underline, pill, and contained variants, controlled and uncontrolled modes, lazy rendering, closable tabs, and full WAI-ARIA Tabs pattern compliance.
@@ -34,7 +34,6 @@ export * from '@frame-kit/ui-ng/ui/list-editor';
34
34
  export * from '@frame-kit/ui-ng/ui/loader';
35
35
  export * from '@frame-kit/ui-ng/ui/menu-item';
36
36
  export * from '@frame-kit/ui-ng/ui/nav-brand';
37
- export * from '@frame-kit/ui-ng/ui/nav-group';
38
37
  export * from '@frame-kit/ui-ng/ui/nav-separator';
39
38
  export * from '@frame-kit/ui-ng/ui/node-tree';
40
39
  export * from '@frame-kit/ui-ng/ui/node-tree-breadcrumb';
@@ -42,7 +41,6 @@ export * from '@frame-kit/ui-ng/ui/note';
42
41
  export * from '@frame-kit/ui-ng/ui/numbered-list';
43
42
  export * from '@frame-kit/ui-ng/ui/pagination';
44
43
  export * from '@frame-kit/ui-ng/ui/progress-bar';
45
- export * from '@frame-kit/ui-ng/ui/sidenav-link';
46
44
  export * from '@frame-kit/ui-ng/ui/tabs';
47
45
  export * from '@frame-kit/ui-ng/ui/timeline';
48
46
  export * from '@frame-kit/ui-ng/ui/toast';
@@ -1 +1 @@
1
- {"version":3,"file":"frame-kit-ui-ng.mjs","sources":["../../../../packages/ui-ng/frame-kit-ui-ng.ts"],"sourcesContent":["/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;AAEG"}
1
+ {"version":3,"file":"frame-kit-ui-ng.mjs","sources":["../../../../packages/ui-ng/frame-kit-ui-ng.ts"],"sourcesContent":["/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;AAEG"}
@@ -141,8 +141,8 @@ Sidebar pushes the main content over. Collapses to zero width when closed.
141
141
  </div>
142
142
 
143
143
  <nav appShellSidenav>
144
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
145
- <fk-sidenav-link icon="organization" label="Properties" />
144
+ <a routerLink="/app">Overview</a>
145
+ <a routerLink="/properties">Properties</a>
146
146
  </nav>
147
147
 
148
148
  <div appShellContent>
@@ -162,7 +162,7 @@ Sidebar slides over the content with a backdrop overlay.
162
162
  </div>
163
163
 
164
164
  <nav appShellSidenav>
165
- <fk-sidenav-link icon="email" label="Notifications" />
165
+ <a routerLink="/notifications">Notifications</a>
166
166
  </nav>
167
167
 
168
168
  <div appShellContent>
@@ -187,13 +187,13 @@ Sidebar collapses to a narrow icon rail on desktop. On mobile, it behaves as an
187
187
  </div>
188
188
 
189
189
  <nav class="sidenav-nav" style="flex: 1; overflow-y: auto;">
190
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
191
- <fk-sidenav-link icon="organization" label="Properties" />
192
- <fk-sidenav-link icon="email" label="Notifications" />
190
+ <a routerLink="/app">Overview</a>
191
+ <a routerLink="/properties">Properties</a>
192
+ <a routerLink="/notifications">Notifications</a>
193
193
  </nav>
194
194
 
195
195
  <div class="sidenav-footer">
196
- <fk-sidenav-link icon="info" label="Help & Support" />
196
+ <a routerLink="/help">Help &amp; Support</a>
197
197
  </div>
198
198
  </div>
199
199
 
@@ -214,7 +214,7 @@ The end panel is opt-in and starts closed. The consumer provides their own toggl
214
214
  </div>
215
215
 
216
216
  <nav appShellSidenav>
217
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
217
+ <a routerLink="/app">Overview</a>
218
218
  </nav>
219
219
 
220
220
  <div appShellContent>
@@ -242,7 +242,7 @@ Keep the icon rail visible on mobile instead of hiding the sidebar. Tapping an i
242
242
  <button (click)="shell.openSidenav()">
243
243
  <fk-icon name="menu-outline" />
244
244
  </button>
245
- <fk-sidenav-link icon="home" label="Home" routerLink="/" />
245
+ <a routerLink="/">Home</a>
246
246
  </nav>
247
247
 
248
248
  <div appShellContent>
@@ -321,7 +321,7 @@ Keep the icon rail visible on mobile instead of hiding the sidebar. Tapping an i
321
321
  - Transitions are only enabled after explicit user interaction (toggle, open, close)
322
322
  - `dismissSidenav()` closes the mobile overlay without persisting a closed preference — on the next desktop resize the sidebar will reappear
323
323
  - `closeSidenav()` persists the closed preference across mobile/desktop transitions
324
- - In `icon` mode, when collapsed, the aside shrinks to `collapsedWidth` and child components (like `fk-sidenav-link`) can detect collapse state to hide labels
324
+ - In `icon` mode, when collapsed, the aside shrinks to `collapsedWidth` and gains the `fk-app-shell--collapsed` class; sidenav content can react to it (e.g. hiding labels) via that class or the shell's `isCollapsed()` signal
325
325
  - By default, the sidebar is hidden off-screen on mobile and reachable via a toggle. Set `collapseOnMobile="true"` (icon mode only) to keep the collapsed rail visible on mobile; opening the sidenav still slides it in as a full-width overlay that can be dismissed via the backdrop
326
326
  - The mobile overlay defaults to the full viewport width (`--fk-app-shell-mobile-sidenav-width: 100%`). Override the token for a peek-style drawer. The input `sidenavWidth` only drives the desktop width
327
327
  - The end panel starts closed by default — the consumer must explicitly open it via `openEnd()` or `toggleEnd()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frame-kit/ui-ng",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "description": "Style-agnostic, token-driven Angular UI component library for FrameKit.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -177,10 +177,6 @@
177
177
  "types": "./types/frame-kit-ui-ng-ui-nav-brand.d.ts",
178
178
  "default": "./fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs"
179
179
  },
180
- "./ui/nav-group": {
181
- "types": "./types/frame-kit-ui-ng-ui-nav-group.d.ts",
182
- "default": "./fesm2022/frame-kit-ui-ng-ui-nav-group.mjs"
183
- },
184
180
  "./ui/nav-separator": {
185
181
  "types": "./types/frame-kit-ui-ng-ui-nav-separator.d.ts",
186
182
  "default": "./fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs"
@@ -209,10 +205,6 @@
209
205
  "types": "./types/frame-kit-ui-ng-ui-progress-bar.d.ts",
210
206
  "default": "./fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs"
211
207
  },
212
- "./ui/sidenav-link": {
213
- "types": "./types/frame-kit-ui-ng-ui-sidenav-link.d.ts",
214
- "default": "./fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs"
215
- },
216
208
  "./ui/tabs": {
217
209
  "types": "./types/frame-kit-ui-ng-ui-tabs.d.ts",
218
210
  "default": "./fesm2022/frame-kit-ui-ng-ui-tabs.mjs"
@@ -34,7 +34,6 @@ export * from '@frame-kit/ui-ng/ui/list-editor';
34
34
  export * from '@frame-kit/ui-ng/ui/loader';
35
35
  export * from '@frame-kit/ui-ng/ui/menu-item';
36
36
  export * from '@frame-kit/ui-ng/ui/nav-brand';
37
- export * from '@frame-kit/ui-ng/ui/nav-group';
38
37
  export * from '@frame-kit/ui-ng/ui/nav-separator';
39
38
  export * from '@frame-kit/ui-ng/ui/node-tree';
40
39
  export * from '@frame-kit/ui-ng/ui/node-tree-breadcrumb';
@@ -42,7 +41,6 @@ export * from '@frame-kit/ui-ng/ui/note';
42
41
  export * from '@frame-kit/ui-ng/ui/numbered-list';
43
42
  export * from '@frame-kit/ui-ng/ui/pagination';
44
43
  export * from '@frame-kit/ui-ng/ui/progress-bar';
45
- export * from '@frame-kit/ui-ng/ui/sidenav-link';
46
44
  export * from '@frame-kit/ui-ng/ui/tabs';
47
45
  export * from '@frame-kit/ui-ng/ui/timeline';
48
46
  export * from '@frame-kit/ui-ng/ui/toast';
@@ -1,117 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { input, model, output, computed, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import * as i1 from '@angular/router';
4
- import { RouterModule } from '@angular/router';
5
- import { SidenavLinkComponent } from '@frame-kit/ui-ng/ui/sidenav-link';
6
-
7
- /**
8
- * Expandable nav group — renders a trigger row (icon + label, optionally
9
- * linked) and reveals projected children when `expanded` is true. Designed
10
- * to sit inside a collapsible sidenav: when the outer shell is collapsed,
11
- * clicking the row emits `requestOpen` so the host can pop the sidenav
12
- * open, and the group auto-expands once it's visible.
13
- */
14
- class NavGroupComponent {
15
- // ===== INPUTS =====
16
- /** Primary label text shown in the group trigger row. */
17
- label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
18
- /** Optional icon name shown to the leading side of the label. */
19
- icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
20
- /** Size of the leading icon. */
21
- iconSize = input('sm', ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
22
- /** Optional router path; when set, the trigger navigates on click in expanded sidenav mode. */
23
- routerLink = input(null, ...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
24
- /**
25
- * True when the containing sidenav is in its collapsed rail state.
26
- * In that mode, clicking the trigger BOTH navigates via
27
- * `routerLink` AND emits `requestOpen` so the host can expand the
28
- * outer shell — the user lands on the group's route with the
29
- * sidenav already open. The trigger never expands its own children
30
- * inside the rail (no horizontal room); the rail-to-expanded
31
- * transition is what reveals them.
32
- */
33
- collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
34
- /**
35
- * When true and the sidenav is in its expanded state, clicking the
36
- * trigger row does NOT fold the group — children stay visible.
37
- * Useful when the group's children are permanent navigation rather
38
- * than a collapsible disclosure (e.g. a parent nav item that should
39
- * always show its sub-pages while the sidenav is open). Clicks on
40
- * the trigger still navigate via `routerLink`.
41
- *
42
- * Has no effect in collapsed (rail) mode — clicks there still
43
- * navigate via `routerLink` and emit `requestOpen`, and `expanded`
44
- * stays untouched regardless of this input.
45
- */
46
- lockedOpen = input(false, ...(ngDevMode ? [{ debugName: "lockedOpen" }] : /* istanbul ignore next */ []));
47
- /**
48
- * When true, renders a chevron at the trailing edge of the trigger row
49
- * that points right while collapsed and rotates to point down while
50
- * expanded. Decorative (`aria-hidden`); hidden in rail mode. Off by
51
- * default so existing consumers are unaffected.
52
- */
53
- showChevron = input(false, ...(ngDevMode ? [{ debugName: "showChevron" }] : /* istanbul ignore next */ []));
54
- // ===== MODELS =====
55
- /** Whether the group is currently expanded, showing projected child links. */
56
- expanded = model(false, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
57
- // ===== OUTPUTS =====
58
- /**
59
- * Emitted when the group is clicked while the outer sidenav is
60
- * collapsed. Hosts typically listen to open their collapsible shell
61
- * so the projected children become visible.
62
- */
63
- requestOpen = output();
64
- // ===== BASE PROPS =====
65
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
66
- // ===== COMPUTED =====
67
- classes = computed(() => {
68
- return [
69
- 'fk-nav-group',
70
- this.expanded() ? 'fk-nav-group--expanded' : '',
71
- this.collapsed() ? 'fk-nav-group--collapsed' : '',
72
- this.className(),
73
- ]
74
- .filter(Boolean)
75
- .join(' ');
76
- }, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
77
- get hostClass() {
78
- return this.classes();
79
- }
80
- toggle() {
81
- if (this.collapsed()) {
82
- // In rail mode the children must not render inside the rail —
83
- // there's no horizontal room and the visual hierarchy collapses.
84
- // Just ask the host to expand the sidenav; once it does and
85
- // `collapsed` flips to false, the consumer's `[expanded]`
86
- // binding can push true and the children render in the now-open
87
- // sidenav. We DO NOT set `expanded` here, so a click in rail
88
- // mode never reveals children inside the rail itself.
89
- this.requestOpen.emit();
90
- return;
91
- }
92
- // Locked-open: in expanded sidenav mode, the trigger no longer
93
- // folds the group on click. Navigation via `routerLink` still
94
- // fires from the inner sidenav-link; this branch just suppresses
95
- // the toggle behavior so children stay visible.
96
- if (this.lockedOpen()) {
97
- return;
98
- }
99
- this.expanded.update((v) => !v);
100
- }
101
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NavGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
102
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: NavGroupComponent, isStandalone: true, selector: "fk-nav-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconSize: { classPropertyName: "iconSize", publicName: "iconSize", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, lockedOpen: { classPropertyName: "lockedOpen", publicName: "lockedOpen", isSignal: true, isRequired: false, transformFunction: null }, showChevron: { classPropertyName: "showChevron", publicName: "showChevron", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange", requestOpen: "requestOpen" }, host: { properties: { "class": "this.hostClass" } }, ngImport: i0, template: "<div class=\"fk-nav-group__row\" role=\"presentation\" (click)=\"toggle()\">\n <fk-sidenav-link\n class=\"fk-nav-group__link\"\n [icon]=\"icon() ?? ''\"\n [iconSize]=\"iconSize()\"\n [label]=\"label()\"\n [routerLink]=\"routerLink()\"\n />\n\n @if (showChevron()) {\n <span\n class=\"fk-nav-group__chevron\"\n [class.fk-nav-group__chevron--open]=\"expanded()\"\n aria-hidden=\"true\"\n >\n <svg\n class=\"fk-nav-group__chevron-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n >\n <path\n d=\"M4.5 6.75L9 11.25L13.5 6.75\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n }\n</div>\n\n@if (expanded()) {\n <!--\n Stop child clicks from bubbling to the fk-nav-group host. When a\n consumer binds [routerLink] on <fk-nav-group>, Angular attaches the\n RouterLink directive to the host element in addition to our input \u2014\n so an un-stopped child click would re-navigate to the parent URL\n after the child's own RouterLink fires. This is an event absorber,\n not an interactive element; keyboard activation fires click events\n that bubble through the same path, so the a11y rules don't apply.\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-nav-group__children\" (click)=\"$event.stopPropagation()\">\n <ng-content />\n </div>\n}\n", styles: [":host{display:block}.fk-nav-group__row{display:flex;align-items:center;gap:var(--fk-nav-group-row-gap, var(--fk-rhythm-1, .25rem))}.fk-nav-group__link{flex:1;min-width:0}.fk-nav-group__chevron{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--fk-nav-group-chevron-color, var(--fk-color-muted, #64748b));transform:rotate(-90deg);transition:transform .15s ease}.fk-nav-group__chevron--open{transform:rotate(0)}.fk-nav-group__chevron-svg{display:block}:host(.fk-nav-group--collapsed) .fk-nav-group__chevron{display:none}.fk-nav-group__children{position:relative;display:flex;flex-direction:column;gap:var(--fk-nav-group-children-gap, var(--fk-rhythm-1, .25rem));padding-left:var(--fk-nav-group-children-indent, var(--fk-rhythm-6, 1.5rem));margin-top:var(--fk-nav-group-children-offset, var(--fk-rhythm-1, .25rem))}.fk-nav-group__children:before{content:\"\";position:absolute;top:0;bottom:0;left:var(--fk-nav-group-children-border-offset, var(--fk-sidenav-link-padding-inline, var(--fk-rhythm-3, .75rem)));width:var(--fk-nav-group-children-border-width, var(--fk-border-width, 1px));background:var(--fk-nav-group-children-border-color, var(--fk-color-border, #d9e2ee))}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: SidenavLinkComponent, selector: "fk-sidenav-link", inputs: ["icon", "iconSize", "label", "routerLink", "exact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
103
- }
104
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NavGroupComponent, decorators: [{
105
- type: Component,
106
- args: [{ selector: 'fk-nav-group', standalone: true, imports: [RouterModule, SidenavLinkComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fk-nav-group__row\" role=\"presentation\" (click)=\"toggle()\">\n <fk-sidenav-link\n class=\"fk-nav-group__link\"\n [icon]=\"icon() ?? ''\"\n [iconSize]=\"iconSize()\"\n [label]=\"label()\"\n [routerLink]=\"routerLink()\"\n />\n\n @if (showChevron()) {\n <span\n class=\"fk-nav-group__chevron\"\n [class.fk-nav-group__chevron--open]=\"expanded()\"\n aria-hidden=\"true\"\n >\n <svg\n class=\"fk-nav-group__chevron-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n >\n <path\n d=\"M4.5 6.75L9 11.25L13.5 6.75\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n }\n</div>\n\n@if (expanded()) {\n <!--\n Stop child clicks from bubbling to the fk-nav-group host. When a\n consumer binds [routerLink] on <fk-nav-group>, Angular attaches the\n RouterLink directive to the host element in addition to our input \u2014\n so an un-stopped child click would re-navigate to the parent URL\n after the child's own RouterLink fires. This is an event absorber,\n not an interactive element; keyboard activation fires click events\n that bubble through the same path, so the a11y rules don't apply.\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-nav-group__children\" (click)=\"$event.stopPropagation()\">\n <ng-content />\n </div>\n}\n", styles: [":host{display:block}.fk-nav-group__row{display:flex;align-items:center;gap:var(--fk-nav-group-row-gap, var(--fk-rhythm-1, .25rem))}.fk-nav-group__link{flex:1;min-width:0}.fk-nav-group__chevron{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--fk-nav-group-chevron-color, var(--fk-color-muted, #64748b));transform:rotate(-90deg);transition:transform .15s ease}.fk-nav-group__chevron--open{transform:rotate(0)}.fk-nav-group__chevron-svg{display:block}:host(.fk-nav-group--collapsed) .fk-nav-group__chevron{display:none}.fk-nav-group__children{position:relative;display:flex;flex-direction:column;gap:var(--fk-nav-group-children-gap, var(--fk-rhythm-1, .25rem));padding-left:var(--fk-nav-group-children-indent, var(--fk-rhythm-6, 1.5rem));margin-top:var(--fk-nav-group-children-offset, var(--fk-rhythm-1, .25rem))}.fk-nav-group__children:before{content:\"\";position:absolute;top:0;bottom:0;left:var(--fk-nav-group-children-border-offset, var(--fk-sidenav-link-padding-inline, var(--fk-rhythm-3, .75rem)));width:var(--fk-nav-group-children-border-width, var(--fk-border-width, 1px));background:var(--fk-nav-group-children-border-color, var(--fk-color-border, #d9e2ee))}\n"] }]
107
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconSize", required: false }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], lockedOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedOpen", required: false }] }], showChevron: [{ type: i0.Input, args: [{ isSignal: true, alias: "showChevron", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], requestOpen: [{ type: i0.Output, args: ["requestOpen"] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hostClass: [{
108
- type: HostBinding,
109
- args: ['class']
110
- }] } });
111
-
112
- /**
113
- * Generated bundle index. Do not edit.
114
- */
115
-
116
- export { NavGroupComponent };
117
- //# sourceMappingURL=frame-kit-ui-ng-ui-nav-group.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"frame-kit-ui-ng-ui-nav-group.mjs","sources":["../../../../packages/ui-ng/ui/nav-group/nav-group.component.ts","../../../../packages/ui-ng/ui/nav-group/nav-group.component.html","../../../../packages/ui-ng/ui/nav-group/frame-kit-ui-ng-ui-nav-group.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n model,\n output,\n} from '@angular/core';\nimport { RouterModule } from '@angular/router';\n\nimport type { IconSize } from '@frame-kit/ui-ng/core/icon';\nimport { SidenavLinkComponent } from '@frame-kit/ui-ng/ui/sidenav-link';\n\n/**\n * Expandable nav group — renders a trigger row (icon + label, optionally\n * linked) and reveals projected children when `expanded` is true. Designed\n * to sit inside a collapsible sidenav: when the outer shell is collapsed,\n * clicking the row emits `requestOpen` so the host can pop the sidenav\n * open, and the group auto-expands once it's visible.\n */\n@Component({\n selector: 'fk-nav-group',\n standalone: true,\n imports: [RouterModule, SidenavLinkComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './nav-group.component.html',\n styleUrl: './nav-group.component.scss',\n})\nexport class NavGroupComponent {\n // ===== INPUTS =====\n /** Primary label text shown in the group trigger row. */\n readonly label = input.required<string>();\n /** Optional icon name shown to the leading side of the label. */\n readonly icon = input<string | null>(null);\n /** Size of the leading icon. */\n readonly iconSize = input<IconSize>('sm');\n /** Optional router path; when set, the trigger navigates on click in expanded sidenav mode. */\n readonly routerLink = input<string | string[] | null>(null);\n\n /**\n * True when the containing sidenav is in its collapsed rail state.\n * In that mode, clicking the trigger BOTH navigates via\n * `routerLink` AND emits `requestOpen` so the host can expand the\n * outer shell — the user lands on the group's route with the\n * sidenav already open. The trigger never expands its own children\n * inside the rail (no horizontal room); the rail-to-expanded\n * transition is what reveals them.\n */\n readonly collapsed = input<boolean>(false);\n\n /**\n * When true and the sidenav is in its expanded state, clicking the\n * trigger row does NOT fold the group — children stay visible.\n * Useful when the group's children are permanent navigation rather\n * than a collapsible disclosure (e.g. a parent nav item that should\n * always show its sub-pages while the sidenav is open). Clicks on\n * the trigger still navigate via `routerLink`.\n *\n * Has no effect in collapsed (rail) mode — clicks there still\n * navigate via `routerLink` and emit `requestOpen`, and `expanded`\n * stays untouched regardless of this input.\n */\n readonly lockedOpen = input<boolean>(false);\n\n /**\n * When true, renders a chevron at the trailing edge of the trigger row\n * that points right while collapsed and rotates to point down while\n * expanded. Decorative (`aria-hidden`); hidden in rail mode. Off by\n * default so existing consumers are unaffected.\n */\n readonly showChevron = input<boolean>(false);\n\n // ===== MODELS =====\n /** Whether the group is currently expanded, showing projected child links. */\n readonly expanded = model<boolean>(false);\n\n // ===== OUTPUTS =====\n /**\n * Emitted when the group is clicked while the outer sidenav is\n * collapsed. Hosts typically listen to open their collapsible shell\n * so the projected children become visible.\n */\n readonly requestOpen = output<void>();\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n\n // ===== COMPUTED =====\n readonly classes = computed(() => {\n return [\n 'fk-nav-group',\n this.expanded() ? 'fk-nav-group--expanded' : '',\n this.collapsed() ? 'fk-nav-group--collapsed' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n @HostBinding('class')\n get hostClass() {\n return this.classes();\n }\n\n protected toggle(): void {\n if (this.collapsed()) {\n // In rail mode the children must not render inside the rail —\n // there's no horizontal room and the visual hierarchy collapses.\n // Just ask the host to expand the sidenav; once it does and\n // `collapsed` flips to false, the consumer's `[expanded]`\n // binding can push true and the children render in the now-open\n // sidenav. We DO NOT set `expanded` here, so a click in rail\n // mode never reveals children inside the rail itself.\n this.requestOpen.emit();\n\n return;\n }\n\n // Locked-open: in expanded sidenav mode, the trigger no longer\n // folds the group on click. Navigation via `routerLink` still\n // fires from the inner sidenav-link; this branch just suppresses\n // the toggle behavior so children stay visible.\n if (this.lockedOpen()) {\n return;\n }\n\n this.expanded.update((v) => !v);\n }\n}\n","<div class=\"fk-nav-group__row\" role=\"presentation\" (click)=\"toggle()\">\n <fk-sidenav-link\n class=\"fk-nav-group__link\"\n [icon]=\"icon() ?? ''\"\n [iconSize]=\"iconSize()\"\n [label]=\"label()\"\n [routerLink]=\"routerLink()\"\n />\n\n @if (showChevron()) {\n <span\n class=\"fk-nav-group__chevron\"\n [class.fk-nav-group__chevron--open]=\"expanded()\"\n aria-hidden=\"true\"\n >\n <svg\n class=\"fk-nav-group__chevron-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n >\n <path\n d=\"M4.5 6.75L9 11.25L13.5 6.75\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n }\n</div>\n\n@if (expanded()) {\n <!--\n Stop child clicks from bubbling to the fk-nav-group host. When a\n consumer binds [routerLink] on <fk-nav-group>, Angular attaches the\n RouterLink directive to the host element in addition to our input —\n so an un-stopped child click would re-navigate to the parent URL\n after the child's own RouterLink fires. This is an event absorber,\n not an interactive element; keyboard activation fires click events\n that bubble through the same path, so the a11y rules don't apply.\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-nav-group__children\" (click)=\"$event.stopPropagation()\">\n <ng-content />\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAcA;;;;;;AAMG;MASU,iBAAiB,CAAA;;;AAGnB,IAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,2EAAU;;AAEhC,IAAA,IAAI,GAAG,KAAK,CAAgB,IAAI,2EAAC;;AAEjC,IAAA,QAAQ,GAAG,KAAK,CAAW,IAAI,+EAAC;;AAEhC,IAAA,UAAU,GAAG,KAAK,CAA2B,IAAI,iFAAC;AAE3D;;;;;;;;AAQG;AACM,IAAA,SAAS,GAAG,KAAK,CAAU,KAAK,gFAAC;AAE1C;;;;;;;;;;;AAWG;AACM,IAAA,UAAU,GAAG,KAAK,CAAU,KAAK,iFAAC;AAE3C;;;;;AAKG;AACM,IAAA,WAAW,GAAG,KAAK,CAAU,KAAK,kFAAC;;;AAInC,IAAA,QAAQ,GAAG,KAAK,CAAU,KAAK,+EAAC;;AAGzC;;;;AAIG;IACM,WAAW,GAAG,MAAM,EAAQ;;AAG5B,IAAA,SAAS,GAAG,KAAK,CAAS,EAAE,gFAAC;;AAG7B,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;QAC/B,OAAO;YACL,cAAc;YACd,IAAI,CAAC,QAAQ,EAAE,GAAG,wBAAwB,GAAG,EAAE;YAC/C,IAAI,CAAC,SAAS,EAAE,GAAG,yBAAyB,GAAG,EAAE;YACjD,IAAI,CAAC,SAAS,EAAE;AACjB;aACE,MAAM,CAAC,OAAO;aACd,IAAI,CAAC,GAAG,CAAC;AACd,IAAA,CAAC,8EAAC;AAEF,IAAA,IACI,SAAS,GAAA;AACX,QAAA,OAAO,IAAI,CAAC,OAAO,EAAE;IACvB;IAEU,MAAM,GAAA;AACd,QAAA,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE;;;;;;;;AAQpB,YAAA,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;YAEvB;QACF;;;;;AAMA,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE;YACrB;QACF;AAEA,QAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACjC;uGAnGW,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAjB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,iBAAiB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,WAAA,EAAA,aAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,gBAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC7B9B,8pDAkDA,EAAA,MAAA,EAAA,CAAA,wrCAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,ED1BY,YAAY,gRAAE,oBAAoB,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,OAAA,EAAA,YAAA,EAAA,OAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAKjC,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAR7B,SAAS;+BACE,cAAc,EAAA,UAAA,EACZ,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,oBAAoB,CAAC,EAAA,eAAA,EAC5B,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,8pDAAA,EAAA,MAAA,EAAA,CAAA,wrCAAA,CAAA,EAAA;;sBA2E9C,WAAW;uBAAC,OAAO;;;AEpGtB;;AAEG;;;;"}
@@ -1,42 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { input, inject, computed, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { RouterLink, RouterLinkActive } from '@angular/router';
4
- import { IconComponent } from '@frame-kit/ui-ng/core/icon';
5
- import { TooltipDirective } from '@frame-kit/ui-ng/directives/tooltip';
6
- import { AppShellComponent } from '@frame-kit/ui-ng/layouts/app-shell';
7
-
8
- class SidenavLinkComponent {
9
- /** Optional icon name shown to the leading side of the link label. */
10
- icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
11
- /** Size of the leading icon. */
12
- iconSize = input('sm', ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
13
- /** Link text displayed next to the icon and used as a tooltip when the sidenav is collapsed. */
14
- label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
15
- /** Router path the link navigates to; when null the element renders as a non-navigable button. */
16
- routerLink = input(null, ...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
17
- /** When true, the active class is applied only on an exact route match. */
18
- exact = input(false, ...(ngDevMode ? [{ debugName: "exact" }] : /* istanbul ignore next */ []));
19
- shell = inject(AppShellComponent, { optional: true });
20
- isCollapsed = computed(() => this.shell?.isCollapsed() ?? false, ...(ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []));
21
- hostClass = 'fk-sidenav-link';
22
- /** Dismisses the parent sidenav on mobile when the link is activated. */
23
- dismiss() {
24
- this.shell?.dismissSidenav();
25
- }
26
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidenavLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
27
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SidenavLinkComponent, isStandalone: true, selector: "fk-sidenav-link", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconSize: { classPropertyName: "iconSize", publicName: "iconSize", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, exact: { classPropertyName: "exact", publicName: "exact", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, ngImport: i0, template: "@if (routerLink()) {\n <a\n class=\"fk-sidenav-link__anchor\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"fk-sidenav-link__anchor--active\"\n [routerLinkActiveOptions]=\"{ exact: exact() }\"\n [attr.aria-label]=\"isCollapsed() ? label() : null\"\n [fkTooltip]=\"label()\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"dismiss()\"\n >\n @if (icon()) {\n <fk-icon [name]=\"icon()!\" [size]=\"iconSize()\" />\n }\n @if (!isCollapsed()) {\n <span class=\"fk-sidenav-link__label\">{{ label() }}</span>\n }\n </a>\n} @else {\n <button\n class=\"fk-sidenav-link__anchor\"\n type=\"button\"\n [attr.aria-label]=\"isCollapsed() ? label() : null\"\n [fkTooltip]=\"label()\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"dismiss()\"\n >\n @if (icon()) {\n <fk-icon [name]=\"icon()!\" [size]=\"iconSize()\" />\n }\n @if (!isCollapsed()) {\n <span class=\"fk-sidenav-link__label\">{{ label() }}</span>\n }\n </button>\n}\n", styles: [".fk-sidenav-link{display:block}.fk-sidenav-link__anchor{display:flex;align-items:center;gap:var(--fk-sidenav-link-gap, var(--fk-rhythm-2, .5rem));width:100%;padding:var(--fk-sidenav-link-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidenav-link-padding-inline, var(--fk-rhythm-3, .75rem));border:none;border-radius:var(--fk-sidenav-link-radius, var(--fk-radius-md, .5rem));background:var(--fk-sidenav-link-bg, transparent);font-family:var(--fk-sidenav-link-font-family, var(--fk-font-family-base));font-size:var(--fk-sidenav-link-font-size, .9375rem);color:var(--fk-sidenav-link-color, var(--fk-color-text, #1f2d3d));text-align:left;text-decoration:none;white-space:nowrap;overflow:hidden;cursor:pointer;transition:background-color .15s ease}.fk-sidenav-link__anchor:hover{background-color:var(--fk-sidenav-link-bg-hover, var(--fk-color-surface-muted, #f7f9fb));color:var(--fk-sidenav-link-color-hover, var(--fk-color-primary, #0a84ff))}.fk-sidenav-link__anchor:focus-visible{outline:none;box-shadow:var(--fk-sidenav-link-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-sidenav-link__anchor--active,.fk-sidenav-link__anchor--active:hover{font-weight:var(--fk-sidenav-link-font-weight-active, var(--fk-font-weight-semibold, 600));color:var(--fk-sidenav-link-color-active, var(--fk-color-primary, #0a84ff));background-color:var(--fk-sidenav-link-bg-active, var(--fk-color-surface-muted, #f7f9fb))}.fk-app-shell--collapsed .fk-sidenav-link__anchor{justify-content:center;padding:var(--fk-sidenav-link-collapsed-padding, var(--fk-rhythm-2, .5rem))}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: IconComponent, selector: "fk-icon", inputs: ["name", "size", "color", "className", "id", "ariaLabel", "ariaHidden"] }, { kind: "directive", type: TooltipDirective, selector: "[fkTooltip]", inputs: ["fkTooltip", "fkTooltipPosition", "fkTooltipOffset", "fkTooltipShowDelay", "fkTooltipHideDelay", "fkTooltipDisabled", "fkTooltipClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
28
- }
29
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidenavLinkComponent, decorators: [{
30
- type: Component,
31
- args: [{ selector: 'fk-sidenav-link', standalone: true, imports: [RouterLink, RouterLinkActive, IconComponent, TooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (routerLink()) {\n <a\n class=\"fk-sidenav-link__anchor\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"fk-sidenav-link__anchor--active\"\n [routerLinkActiveOptions]=\"{ exact: exact() }\"\n [attr.aria-label]=\"isCollapsed() ? label() : null\"\n [fkTooltip]=\"label()\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"dismiss()\"\n >\n @if (icon()) {\n <fk-icon [name]=\"icon()!\" [size]=\"iconSize()\" />\n }\n @if (!isCollapsed()) {\n <span class=\"fk-sidenav-link__label\">{{ label() }}</span>\n }\n </a>\n} @else {\n <button\n class=\"fk-sidenav-link__anchor\"\n type=\"button\"\n [attr.aria-label]=\"isCollapsed() ? label() : null\"\n [fkTooltip]=\"label()\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"dismiss()\"\n >\n @if (icon()) {\n <fk-icon [name]=\"icon()!\" [size]=\"iconSize()\" />\n }\n @if (!isCollapsed()) {\n <span class=\"fk-sidenav-link__label\">{{ label() }}</span>\n }\n </button>\n}\n", styles: [".fk-sidenav-link{display:block}.fk-sidenav-link__anchor{display:flex;align-items:center;gap:var(--fk-sidenav-link-gap, var(--fk-rhythm-2, .5rem));width:100%;padding:var(--fk-sidenav-link-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidenav-link-padding-inline, var(--fk-rhythm-3, .75rem));border:none;border-radius:var(--fk-sidenav-link-radius, var(--fk-radius-md, .5rem));background:var(--fk-sidenav-link-bg, transparent);font-family:var(--fk-sidenav-link-font-family, var(--fk-font-family-base));font-size:var(--fk-sidenav-link-font-size, .9375rem);color:var(--fk-sidenav-link-color, var(--fk-color-text, #1f2d3d));text-align:left;text-decoration:none;white-space:nowrap;overflow:hidden;cursor:pointer;transition:background-color .15s ease}.fk-sidenav-link__anchor:hover{background-color:var(--fk-sidenav-link-bg-hover, var(--fk-color-surface-muted, #f7f9fb));color:var(--fk-sidenav-link-color-hover, var(--fk-color-primary, #0a84ff))}.fk-sidenav-link__anchor:focus-visible{outline:none;box-shadow:var(--fk-sidenav-link-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-sidenav-link__anchor--active,.fk-sidenav-link__anchor--active:hover{font-weight:var(--fk-sidenav-link-font-weight-active, var(--fk-font-weight-semibold, 600));color:var(--fk-sidenav-link-color-active, var(--fk-color-primary, #0a84ff));background-color:var(--fk-sidenav-link-bg-active, var(--fk-color-surface-muted, #f7f9fb))}.fk-app-shell--collapsed .fk-sidenav-link__anchor{justify-content:center;padding:var(--fk-sidenav-link-collapsed-padding, var(--fk-rhythm-2, .5rem))}\n"] }]
32
- }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconSize", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], exact: [{ type: i0.Input, args: [{ isSignal: true, alias: "exact", required: false }] }], hostClass: [{
33
- type: HostBinding,
34
- args: ['class']
35
- }] } });
36
-
37
- /**
38
- * Generated bundle index. Do not edit.
39
- */
40
-
41
- export { SidenavLinkComponent };
42
- //# sourceMappingURL=frame-kit-ui-ng-ui-sidenav-link.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"frame-kit-ui-ng-ui-sidenav-link.mjs","sources":["../../../../packages/ui-ng/ui/sidenav-link/sidenav-link.component.ts","../../../../packages/ui-ng/ui/sidenav-link/sidenav-link.component.html","../../../../packages/ui-ng/ui/sidenav-link/frame-kit-ui-ng-ui-sidenav-link.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n inject,\n input,\n} from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { IconComponent } from '@frame-kit/ui-ng/core/icon';\nimport type { IconSize } from '@frame-kit/ui-ng/core/icon';\nimport { TooltipDirective } from '@frame-kit/ui-ng/directives/tooltip';\nimport { AppShellComponent } from '@frame-kit/ui-ng/layouts/app-shell';\n\n@Component({\n selector: 'fk-sidenav-link',\n standalone: true,\n imports: [RouterLink, RouterLinkActive, IconComponent, TooltipDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './sidenav-link.component.html',\n styleUrl: './sidenav-link.component.scss',\n})\nexport class SidenavLinkComponent {\n /** Optional icon name shown to the leading side of the link label. */\n readonly icon = input<string | null>(null);\n /** Size of the leading icon. */\n readonly iconSize = input<IconSize>('sm');\n /** Link text displayed next to the icon and used as a tooltip when the sidenav is collapsed. */\n readonly label = input.required<string>();\n /** Router path the link navigates to; when null the element renders as a non-navigable button. */\n readonly routerLink = input<string | string[] | null>(null);\n /** When true, the active class is applied only on an exact route match. */\n readonly exact = input<boolean>(false);\n\n private readonly shell = inject(AppShellComponent, { optional: true });\n\n readonly isCollapsed = computed(() => this.shell?.isCollapsed() ?? false);\n\n @HostBinding('class')\n readonly hostClass = 'fk-sidenav-link';\n\n /** Dismisses the parent sidenav on mobile when the link is activated. */\n dismiss(): void {\n this.shell?.dismissSidenav();\n }\n}\n","@if (routerLink()) {\n <a\n class=\"fk-sidenav-link__anchor\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"fk-sidenav-link__anchor--active\"\n [routerLinkActiveOptions]=\"{ exact: exact() }\"\n [attr.aria-label]=\"isCollapsed() ? label() : null\"\n [fkTooltip]=\"label()\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"dismiss()\"\n >\n @if (icon()) {\n <fk-icon [name]=\"icon()!\" [size]=\"iconSize()\" />\n }\n @if (!isCollapsed()) {\n <span class=\"fk-sidenav-link__label\">{{ label() }}</span>\n }\n </a>\n} @else {\n <button\n class=\"fk-sidenav-link__anchor\"\n type=\"button\"\n [attr.aria-label]=\"isCollapsed() ? label() : null\"\n [fkTooltip]=\"label()\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"dismiss()\"\n >\n @if (icon()) {\n <fk-icon [name]=\"icon()!\" [size]=\"iconSize()\" />\n }\n @if (!isCollapsed()) {\n <span class=\"fk-sidenav-link__label\">{{ label() }}</span>\n }\n </button>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,67 +0,0 @@
1
- import * as _angular_core from '@angular/core';
2
- import { IconSize } from '@frame-kit/ui-ng/core/icon';
3
- export { IconSize as NavGroupIconSize } from '@frame-kit/ui-ng/core/icon';
4
-
5
- /**
6
- * Expandable nav group — renders a trigger row (icon + label, optionally
7
- * linked) and reveals projected children when `expanded` is true. Designed
8
- * to sit inside a collapsible sidenav: when the outer shell is collapsed,
9
- * clicking the row emits `requestOpen` so the host can pop the sidenav
10
- * open, and the group auto-expands once it's visible.
11
- */
12
- declare class NavGroupComponent {
13
- /** Primary label text shown in the group trigger row. */
14
- readonly label: _angular_core.InputSignal<string>;
15
- /** Optional icon name shown to the leading side of the label. */
16
- readonly icon: _angular_core.InputSignal<string | null>;
17
- /** Size of the leading icon. */
18
- readonly iconSize: _angular_core.InputSignal<IconSize>;
19
- /** Optional router path; when set, the trigger navigates on click in expanded sidenav mode. */
20
- readonly routerLink: _angular_core.InputSignal<string | string[] | null>;
21
- /**
22
- * True when the containing sidenav is in its collapsed rail state.
23
- * In that mode, clicking the trigger BOTH navigates via
24
- * `routerLink` AND emits `requestOpen` so the host can expand the
25
- * outer shell — the user lands on the group's route with the
26
- * sidenav already open. The trigger never expands its own children
27
- * inside the rail (no horizontal room); the rail-to-expanded
28
- * transition is what reveals them.
29
- */
30
- readonly collapsed: _angular_core.InputSignal<boolean>;
31
- /**
32
- * When true and the sidenav is in its expanded state, clicking the
33
- * trigger row does NOT fold the group — children stay visible.
34
- * Useful when the group's children are permanent navigation rather
35
- * than a collapsible disclosure (e.g. a parent nav item that should
36
- * always show its sub-pages while the sidenav is open). Clicks on
37
- * the trigger still navigate via `routerLink`.
38
- *
39
- * Has no effect in collapsed (rail) mode — clicks there still
40
- * navigate via `routerLink` and emit `requestOpen`, and `expanded`
41
- * stays untouched regardless of this input.
42
- */
43
- readonly lockedOpen: _angular_core.InputSignal<boolean>;
44
- /**
45
- * When true, renders a chevron at the trailing edge of the trigger row
46
- * that points right while collapsed and rotates to point down while
47
- * expanded. Decorative (`aria-hidden`); hidden in rail mode. Off by
48
- * default so existing consumers are unaffected.
49
- */
50
- readonly showChevron: _angular_core.InputSignal<boolean>;
51
- /** Whether the group is currently expanded, showing projected child links. */
52
- readonly expanded: _angular_core.ModelSignal<boolean>;
53
- /**
54
- * Emitted when the group is clicked while the outer sidenav is
55
- * collapsed. Hosts typically listen to open their collapsible shell
56
- * so the projected children become visible.
57
- */
58
- readonly requestOpen: _angular_core.OutputEmitterRef<void>;
59
- readonly className: _angular_core.InputSignal<string>;
60
- readonly classes: _angular_core.Signal<string>;
61
- get hostClass(): string;
62
- protected toggle(): void;
63
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<NavGroupComponent, never>;
64
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<NavGroupComponent, "fk-nav-group", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "iconSize": { "alias": "iconSize"; "required": false; "isSignal": true; }; "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "lockedOpen": { "alias": "lockedOpen"; "required": false; "isSignal": true; }; "showChevron": { "alias": "showChevron"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; "requestOpen": "requestOpen"; }, never, ["*"], true, never>;
65
- }
66
-
67
- export { NavGroupComponent };
@@ -1,24 +0,0 @@
1
- import * as _angular_core from '@angular/core';
2
- import { IconSize } from '@frame-kit/ui-ng/core/icon';
3
-
4
- declare class SidenavLinkComponent {
5
- /** Optional icon name shown to the leading side of the link label. */
6
- readonly icon: _angular_core.InputSignal<string | null>;
7
- /** Size of the leading icon. */
8
- readonly iconSize: _angular_core.InputSignal<IconSize>;
9
- /** Link text displayed next to the icon and used as a tooltip when the sidenav is collapsed. */
10
- readonly label: _angular_core.InputSignal<string>;
11
- /** Router path the link navigates to; when null the element renders as a non-navigable button. */
12
- readonly routerLink: _angular_core.InputSignal<string | string[] | null>;
13
- /** When true, the active class is applied only on an exact route match. */
14
- readonly exact: _angular_core.InputSignal<boolean>;
15
- private readonly shell;
16
- readonly isCollapsed: _angular_core.Signal<boolean>;
17
- readonly hostClass = "fk-sidenav-link";
18
- /** Dismisses the parent sidenav on mobile when the link is activated. */
19
- dismiss(): void;
20
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidenavLinkComponent, never>;
21
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidenavLinkComponent, "fk-sidenav-link", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "iconSize": { "alias": "iconSize"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "exact": { "alias": "exact"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
22
- }
23
-
24
- export { SidenavLinkComponent };
@@ -1,170 +0,0 @@
1
- # fk-nav-group
2
-
3
- An expandable sidenav group — renders a clickable trigger row (icon + label, optionally routed) and reveals projected child nav items when expanded. Designed to sit inside a collapsible shell so that clicks on a collapsed rail pop the shell open and auto-expand the group.
4
-
5
- ---
6
-
7
- ## API
8
-
9
- ### Inputs
10
-
11
- | Input | Type | Default | Description |
12
- | ------------ | ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
13
- | `label` | `string` | — | **Required.** Text rendered on the trigger row. |
14
- | `icon` | `string \| null` | `null` | Icon name rendered before the label. |
15
- | `iconSize` | `IconSize` | `"sm"` | Size passed through to the trigger's `fk-sidenav-link`. |
16
- | `routerLink` | `string \| string[] \| null` | `null` | Destination for the trigger's anchor. Active in both expanded and collapsed states. |
17
- | `collapsed` | `boolean` | `false` | Set this to `true` when the outer sidenav is in its collapsed rail state — clicks then both navigate AND emit `requestOpen`. |
18
- | `lockedOpen` | `boolean` | `false` | When `true`, clicking an expanded group's trigger no longer folds it — children stay visible (navigation via `routerLink` still fires). No effect in collapsed rail mode. |
19
- | `showChevron`| `boolean` | `false` | Renders a trailing disclosure chevron that points right while collapsed and rotates to point down while expanded. Decorative; hidden in rail mode. |
20
- | `className` | `string` | `''` | Additional CSS classes merged onto the host element. |
21
-
22
- ### Models
23
-
24
- | Model | Type | Default | Description |
25
- | ---------- | --------- | ------- | --------------------------------------------------------------------------- |
26
- | `expanded` | `boolean` | `false` | Two-way bound expansion state. Flipped by the built-in toggle on row click. |
27
-
28
- ### Outputs
29
-
30
- | Output | Type | Description |
31
- | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
- | `requestOpen` | `void` | Emitted only when the trigger is clicked while `collapsed` is `true`. Hosts typically listen to open their collapsible outer shell. The trigger also navigates via `routerLink` on the same click, so the user lands on the group's route with the shell already opening. |
33
-
34
- ### Content
35
-
36
- `fk-nav-group` projects its child nav items into the expanded body:
37
-
38
- ```html
39
- <fk-nav-group label="Integrations" icon="integrations">
40
- <fk-sidenav-link icon="integrations" label="API Keys" routerLink="/integrations/api-keys" />
41
- <fk-sidenav-link icon="integrations" label="Webhooks" routerLink="/integrations/webhooks" />
42
- </fk-nav-group>
43
- ```
44
-
45
- ---
46
-
47
- ## Features
48
-
49
- - Click-to-toggle expansion with two-way `expanded` model (suppressed by `lockedOpen`)
50
- - Router integration via `routerLink` — navigates in both expanded and collapsed states
51
- - `requestOpen` output bridges to a collapsible outer shell (`fk-app-shell` and friends) — fires alongside navigation when clicked in rail mode
52
- - Content projection for arbitrary child rows — `fk-sidenav-link`, section titles, custom markers
53
- - Token-driven spacing (`--fk-nav-group-row-gap`, `--fk-nav-group-children-gap`, `--fk-nav-group-children-indent`, `--fk-nav-group-children-offset`)
54
- - Section rule — a vertical line brackets the expanded children, flush-left under the icon/label, tokenized via `--fk-nav-group-children-border-{width,color,offset}` (removable)
55
- - Optional disclosure chevron (`showChevron`) — points right collapsed, rotates down when expanded; colour via `--fk-nav-group-chevron-color`
56
-
57
- ---
58
-
59
- ## Quick Start
60
-
61
- ```html
62
- <fk-nav-group label="Integrations" icon="integrations" iconSize="md" [routerLink]="['/integrations']" [collapsed]="sidenavCollapsed()" [(expanded)]="integrationsExpanded" (requestOpen)="openSidenav()">
63
- <fk-sidenav-link icon="integrations" label="API Keys" [routerLink]="['/integrations/api-keys']" />
64
- <fk-sidenav-link icon="integrations" label="Applications" [routerLink]="['/integrations/applications']" />
65
- </fk-nav-group>
66
- ```
67
-
68
- ---
69
-
70
- ## Import
71
-
72
- ```ts
73
- import { NavGroupComponent } from '@frame-kit/ui-ng';
74
- ```
75
-
76
- ```ts
77
- @Component({
78
- selector: 'app-example',
79
- imports: [NavGroupComponent],
80
- templateUrl: './example.component.html',
81
- })
82
- export class ExampleComponent {}
83
- ```
84
-
85
- ---
86
-
87
- ## Selector
88
-
89
- ```html
90
- <fk-nav-group></fk-nav-group>
91
- ```
92
-
93
- ---
94
-
95
- ## Examples
96
-
97
- ### Flat list with auto-expansion
98
-
99
- ```html
100
- <fk-nav-group label="Core Concepts" icon="book-outline" [(expanded)]="expanded">
101
- <fk-sidenav-link icon="book-outline" label="Authentication" routerLink="/docs/authentication" />
102
- <fk-sidenav-link icon="book-outline" label="Roles" routerLink="/docs/roles" />
103
- <fk-sidenav-link icon="book-outline" label="Permissions" routerLink="/docs/permissions" />
104
- </fk-nav-group>
105
- ```
106
-
107
- ### Inside a collapsible sidenav
108
-
109
- ```html
110
- <fk-nav-group label="Docs" icon="book-outline" [collapsed]="shellCollapsed()" [(expanded)]="docsExpanded" (requestOpen)="shellCollapsed.set(false)">
111
- <fk-sidenav-link label="Getting started" routerLink="/docs" />
112
- </fk-nav-group>
113
- ```
114
-
115
- ---
116
-
117
- ## Accessibility
118
-
119
- - Trigger row is a `role="presentation"` wrapper containing a keyboard-navigable `fk-sidenav-link` — the sidenav-link handles focus and keyboard activation
120
- - Expansion state is reflected through the host class `fk-nav-group--expanded`; consumers can add further ARIA attributes via `className` if needed
121
- - Keyboard activation (Enter on the focused trigger) follows the same path as a mouse click — navigates via `routerLink` and, when `collapsed` is `true`, also emits `requestOpen` so screen-reader users land on the destination with the rail expanding behind them
122
-
123
- ---
124
-
125
- ## Design Tokens
126
-
127
- ```scss
128
- --fk-nav-group-row-gap;
129
- --fk-nav-group-children-gap;
130
- --fk-nav-group-children-indent;
131
- --fk-nav-group-children-offset;
132
- --fk-nav-group-children-border-width; // section rule width (default --fk-border-width / 1px)
133
- --fk-nav-group-children-border-color; // section rule colour (default --fk-color-border)
134
- --fk-nav-group-children-border-offset; // section rule x-position (see below)
135
- --fk-nav-group-chevron-color; // disclosure chevron colour (default --fk-color-muted)
136
- ```
137
-
138
- The expanded children column carries a quiet vertical **section rule** by
139
- default — a line that brackets the nested items, aligned flush-left with the
140
- trigger row's content-left edge (the icon when present, the label text
141
- otherwise — both start at the link's inline padding, so a single offset
142
- covers both, independent of icon size). The colour defaults to the semantic
143
- `--fk-color-border`, so the rule adapts to dark mode through the fallback
144
- chain with no dedicated dark token.
145
-
146
- Override `--fk-nav-group-children-border-offset` to pin the line elsewhere, or
147
- remove the rule by zeroing the width / making the colour transparent.
148
-
149
- Override in your app stylesheet:
150
-
151
- ```scss
152
- :root {
153
- --fk-nav-group-children-indent: 1rem;
154
- --fk-nav-group-children-gap: var(--fk-rhythm-2);
155
- }
156
-
157
- // Remove the section rule for a particular scope:
158
- .flat-nav {
159
- --fk-nav-group-children-border-width: 0;
160
- }
161
- ```
162
-
163
- ---
164
-
165
- ## Behavior Notes
166
-
167
- - The built-in `toggle()` decides what happens to `expanded` on click: in rail mode it leaves `expanded` alone and only emits `requestOpen` (children must not render inside the rail); in expanded mode it flips `expanded` unless `lockedOpen` is `true`. Navigation via `routerLink` happens independently on the inner sidenav-link's anchor in both states. If you want custom behavior, listen to `(requestOpen)` and drive `[(expanded)]` yourself.
168
- - The trigger row composes `fk-sidenav-link` — you get the same active-route styling and keyboard semantics for free.
169
- - The section rule is an absolutely-positioned `::before` on the children column (not a `border-left`), so its x-position can anchor to the icon or text. It spans the full height of the children and sits behind them; child rows render normally on top. It only shows while the group is expanded (the children column isn't rendered otherwise).
170
- - No opinionated max-width — the trigger fills the row. Cap the width at the consumer level if you need it (e.g., docs shells clamp their top-level items to 18rem).
@@ -1,214 +0,0 @@
1
- # fk-sidenav-link
2
-
3
- A token-driven sidebar navigation link that renders an icon and label, automatically hiding the label when the parent `fk-app-shell` is collapsed.
4
-
5
- ---
6
-
7
- ## API
8
-
9
- ### Inputs
10
-
11
- | Input | Type | Default | Description |
12
- | ---------- | -------------------------------- | ------- | ------------------------------------------------------------------------------ |
13
- | icon | `string` | — | **Required.** Icon name from the icon registry |
14
- | iconSize | `IconSize` | `"sm"` | Size of the icon (`"xs"`, `"sm"`, `"md"`, `"lg"`, `"xl"`, or custom CSS value) |
15
- | label | `string` | — | **Required.** Visible text label |
16
- | routerLink | `string` \| `string[]` \| `null` | `null` | Angular Router link — renders `<a>` when set |
17
- | exact | `boolean` | `false` | Use exact match for `routerLinkActive` highlighting |
18
-
19
- ### Methods
20
-
21
- | Method | Description |
22
- | --------- | ------------------------------------------------------------------------ |
23
- | dismiss() | Calls `dismissSidenav()` on the parent app-shell (closes mobile overlay) |
24
-
25
- ### Computed (read-only)
26
-
27
- | Property | Type | Description |
28
- | ----------- | ----------------- | -------------------------------------------------- |
29
- | isCollapsed | `Signal<boolean>` | Whether the parent app-shell is in collapsed state |
30
-
31
- ---
32
-
33
- ## Features
34
-
35
- - Renders `<a>` with `routerLink` when a route is provided, `<button>` otherwise
36
- - `routerLinkActive` class applied automatically for active route highlighting
37
- - Label hides when parent `fk-app-shell` is collapsed (icon-only rail)
38
- - Auto-dismisses mobile sidebar overlay on click
39
- - Works standalone (outside an app-shell) — collapse detection gracefully defaults to `false`
40
- - Token-driven styling with full override capability
41
-
42
- ---
43
-
44
- ## Quick Start
45
-
46
- ```html
47
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
48
- ```
49
-
50
- ---
51
-
52
- ## Import
53
-
54
- ```ts
55
- import { SidenavLinkComponent } from '@frame-kit/ui-ng';
56
- ```
57
-
58
- ```ts
59
- @Component({
60
- selector: 'app-sidebar',
61
- imports: [SidenavLinkComponent],
62
- templateUrl: './sidebar.component.html',
63
- })
64
- export class SidebarComponent {}
65
- ```
66
-
67
- ---
68
-
69
- ## Selector
70
-
71
- ```html
72
- <fk-sidenav-link />
73
- ```
74
-
75
- ---
76
-
77
- ## Examples
78
-
79
- ### With Router Link
80
-
81
- ```html
82
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
83
- ```
84
-
85
- ### Button Mode (no route)
86
-
87
- Renders a `<button>` instead of `<a>`. Useful for actions like opening a dialog.
88
-
89
- ```html
90
- <fk-sidenav-link icon="email" label="Notifications" />
91
- ```
92
-
93
- ### Nav List
94
-
95
- ```html
96
- <nav style="display: flex; flex-direction: column; gap: 0.25rem;">
97
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
98
- <fk-sidenav-link icon="organization" label="Properties" />
99
- <fk-sidenav-link icon="email" label="Notifications" />
100
- <fk-sidenav-link icon="lock" label="Compliance" />
101
- <fk-sidenav-link icon="shield-alert" label="Insurance" />
102
- </nav>
103
- ```
104
-
105
- ### Inside App Shell
106
-
107
- ```html
108
- <fk-app-shell mode="icon">
109
- <nav appShellSidenav>
110
- <fk-sidenav-link icon="chart-activity-fill" label="Overview" routerLink="/app" [exact]="true" />
111
- <fk-sidenav-link icon="organization" label="Properties" />
112
- <fk-sidenav-link icon="email" label="Notifications" />
113
- </nav>
114
-
115
- <div appShellSidenavFooter>
116
- <fk-sidenav-link icon="info" label="Help & Support" />
117
- </div>
118
-
119
- <div appShellContent>
120
- <router-outlet />
121
- </div>
122
- </fk-app-shell>
123
- ```
124
-
125
- When the shell is collapsed, only icons are shown. When expanded, icons and labels are both visible.
126
-
127
- ---
128
-
129
- ## Accessibility
130
-
131
- - Renders a native `<a>` or `<button>` element — keyboard accessible by default
132
- - Supports Enter and Space through native element behavior
133
- - `focus-visible` ring applied via CSS token
134
- - `routerLinkActive` provides visual indication of the current route
135
- - When collapsed, the icon remains visible and the label is removed from the DOM (not just hidden)
136
- - When collapsed, the host carries `aria-label="<label>"` so screen readers still get a name for the link, and a tooltip appears to the right on hover or keyboard focus (via the `[fkTooltip]` directive). When expanded, the visible label text serves both purposes — `aria-label` is omitted and the tooltip is disabled.
137
-
138
- ### Recommended
139
-
140
- Always register the icon before use:
141
-
142
- ```ts
143
- provideIcons({
144
- 'chart-activity-fill': chartActivityFill,
145
- organization,
146
- email,
147
- });
148
- ```
149
-
150
- ---
151
-
152
- ## Design Tokens
153
-
154
- ### Component Tokens
155
-
156
- ```
157
- --fk-sidenav-link-gap
158
- --fk-sidenav-link-padding-block
159
- --fk-sidenav-link-padding-inline
160
- --fk-sidenav-link-radius
161
- --fk-sidenav-link-bg
162
- --fk-sidenav-link-bg-hover
163
- --fk-sidenav-link-bg-active
164
- --fk-sidenav-link-font-family
165
- --fk-sidenav-link-font-size
166
- --fk-sidenav-link-font-weight-active
167
- --fk-sidenav-link-color
168
- --fk-sidenav-link-color-hover
169
- --fk-sidenav-link-color-active
170
- --fk-sidenav-link-focus-ring
171
- ```
172
-
173
- Each component token falls back to a semantic token, then a raw value. For example:
174
-
175
- ```
176
- --fk-sidenav-link-color → --fk-color-text → #1f2d3d
177
- --fk-sidenav-link-color-hover → --fk-color-primary → #0a84ff
178
- --fk-sidenav-link-color-active → --fk-color-primary → #0a84ff
179
- --fk-sidenav-link-radius → --fk-radius-md → 0.5rem
180
- --fk-sidenav-link-bg-hover → --fk-color-surface-dim → #edf1f5
181
- ```
182
-
183
- ### Customizing Tokens
184
-
185
- Override tokens at any scope:
186
-
187
- ```css
188
- :root {
189
- --fk-sidenav-link-color: #4338ca;
190
- --fk-sidenav-link-bg-hover: #eef2ff;
191
- --fk-sidenav-link-bg-active: #e0e7ff;
192
- --fk-sidenav-link-font-weight-active: 700;
193
- --fk-sidenav-link-radius: 0.25rem;
194
- }
195
- ```
196
-
197
- Or scope to a specific context:
198
-
199
- ```css
200
- .dark-theme fk-sidenav-link {
201
- --fk-sidenav-link-color: #e2e8f0;
202
- --fk-sidenav-link-bg-hover: #1e293b;
203
- --fk-sidenav-link-bg-active: #334155;
204
- }
205
- ```
206
-
207
- ---
208
-
209
- ## Behavior Notes
210
-
211
- - When used inside `fk-app-shell`, the component injects the parent via `inject(AppShellComponent, { optional: true })` to read collapse state
212
- - When used outside an app-shell, `isCollapsed` is always `false` and labels are always visible
213
- - Clicking any link calls `dismissSidenav()` on the parent shell, which closes the mobile overlay
214
- - In collapsed state, the anchor centers its icon with `justify-content: center` and removes inline padding via the `.fk-app-shell--collapsed .fk-sidenav-link__anchor` rule