@frame-kit/ui-ng-patterns 0.0.2
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 +65 -0
- package/fesm2022/frame-kit-ui-ng-patterns-dashboard.mjs +279 -0
- package/fesm2022/frame-kit-ui-ng-patterns-dashboard.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-patterns-docs.mjs +96 -0
- package/fesm2022/frame-kit-ui-ng-patterns-docs.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-patterns.mjs +13 -0
- package/fesm2022/frame-kit-ui-ng-patterns.mjs.map +1 -0
- package/package.json +50 -0
- package/types/frame-kit-ui-ng-patterns-dashboard.d.ts +173 -0
- package/types/frame-kit-ui-ng-patterns-docs.d.ts +68 -0
- package/types/frame-kit-ui-ng-patterns.d.ts +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FrameKit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @frame-kit/ui-ng-patterns
|
|
2
|
+
|
|
3
|
+
Opinionated, **dashboard-ready Angular components** composed from
|
|
4
|
+
[`@frame-kit/ui-ng`](https://www.npmjs.com/package/@frame-kit/ui-ng) primitives. Where `ui-ng` is
|
|
5
|
+
deliberately unstyled (structure, accessibility, behavior), this layer adds the **composition, layout,
|
|
6
|
+
and sensible defaults** you'd otherwise rebuild on every dashboard — while staying fully themeable
|
|
7
|
+
through the same `--fk-*` token contract.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
@frame-kit/tokens design variables (--fk-*)
|
|
11
|
+
↓
|
|
12
|
+
@frame-kit/ui-ng unstyled primitives (button, card, icon, …)
|
|
13
|
+
↓
|
|
14
|
+
@frame-kit/ui-ng-patterns ← you are here: composed, styled patterns
|
|
15
|
+
↓
|
|
16
|
+
your app domain-specific glue only
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i @frame-kit/ui-ng-patterns @frame-kit/ui-ng @frame-kit/tokens
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@frame-kit/ui-ng` and the Angular packages are peer dependencies. Load the `@frame-kit/tokens`
|
|
26
|
+
stylesheets (and any brand overrides) as described in the
|
|
27
|
+
[theming guide](https://github.com/frame-kit/packages/blob/main/packages/ui-ng/THEMING.md).
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Patterns use the **`fk-`** selector prefix, the same as `ui-ng` primitives. Each layer ships as its
|
|
32
|
+
own entry point for tree-shaking:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { PageHeaderComponent } from '@frame-kit/ui-ng-patterns/dashboard';
|
|
36
|
+
import { EndpointLinkComponent } from '@frame-kit/ui-ng-patterns/docs';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## What belongs here
|
|
40
|
+
|
|
41
|
+
A component is promoted into this package when it:
|
|
42
|
+
|
|
43
|
+
- Composes **two or more** `ui-ng` primitives/layouts,
|
|
44
|
+
- Would be reused across **two or more** apps/dashboards,
|
|
45
|
+
- Carries **no domain data** (no app APIs, enums, or business concepts),
|
|
46
|
+
- Is **token-driven** (`--fk-*` plus component-scoped `--fk-<component>-*` with two-tier fallbacks).
|
|
47
|
+
|
|
48
|
+
Domain-specific UI (auth banners, business cards, feature-flagged screens) stays in the consuming app.
|
|
49
|
+
|
|
50
|
+
## Building components
|
|
51
|
+
|
|
52
|
+
See the [Component Development Guide](./DEVELOPMENT_GUIDE.md) for this package's
|
|
53
|
+
conventions. It inherits the [ui-ng guide](../ui-ng/DEVELOPMENT_GUIDE.md) and
|
|
54
|
+
documents the package-specific differences (layers, one entry point per layer,
|
|
55
|
+
selector prefix, story/token rules) plus the ui-ng rules that do **not** apply here.
|
|
56
|
+
|
|
57
|
+
## Status
|
|
58
|
+
|
|
59
|
+
Early — components are being added one at a time. See the
|
|
60
|
+
[patterns catalog](./COMPONENTS.md) and the
|
|
61
|
+
[ui-ng primitive catalog](https://github.com/frame-kit/packages/blob/main/packages/ui-ng/COMPONENTS.md).
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, ChangeDetectionStrategy, ViewEncapsulation, Component, computed, HostBinding } from '@angular/core';
|
|
3
|
+
import { HeadlineComponent } from '@frame-kit/ui-ng/core/headline';
|
|
4
|
+
import { TextComponent } from '@frame-kit/ui-ng/core/text';
|
|
5
|
+
import { IconComponent } from '@frame-kit/ui-ng/core/icon';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Page-level dashboard header: title + optional description on the left, up to
|
|
9
|
+
* ~3 actions on the right.
|
|
10
|
+
*
|
|
11
|
+
* When the header's own width gets tight, the actions drop below the text and
|
|
12
|
+
* go full-width (mobile-style). The break is driven by a CSS container query on
|
|
13
|
+
* the host, so it responds to the content region's width — not the viewport,
|
|
14
|
+
* which means it behaves correctly inside a narrow panel.
|
|
15
|
+
*
|
|
16
|
+
* Project actions by tagging each button with `pageHeaderActions`:
|
|
17
|
+
*
|
|
18
|
+
* <fk-page-header title="Users" description="...">
|
|
19
|
+
* <fk-button pageHeaderActions variant="outline" size="sm">Import</fk-button>
|
|
20
|
+
* <fk-button pageHeaderActions variant="primary" size="sm">Create user</fk-button>
|
|
21
|
+
* </fk-page-header>
|
|
22
|
+
*
|
|
23
|
+
* Tagging each button (rather than wrapping them in a div) makes every button a
|
|
24
|
+
* direct flex child, so full-width stacking falls out of `align-items: stretch`
|
|
25
|
+
* with no `::ng-deep` reach-in.
|
|
26
|
+
*
|
|
27
|
+
* The title row has two projected slots — `[pageHeaderLeading]` before the
|
|
28
|
+
* title and `[pageHeaderTrailing]` after it. Project a plain `fk-icon` for a
|
|
29
|
+
* decorative mark, or a `button` for a clickable one (the consumer owns the
|
|
30
|
+
* interaction, focus ring, and aria-label):
|
|
31
|
+
*
|
|
32
|
+
* <fk-page-header title="Permissions">
|
|
33
|
+
* <fk-icon pageHeaderLeading name="key-outline" />
|
|
34
|
+
* <button pageHeaderTrailing type="button" aria-label="Docs" (click)="openDocs()">
|
|
35
|
+
* <fk-icon name="view-api" />
|
|
36
|
+
* </button>
|
|
37
|
+
* </fk-page-header>
|
|
38
|
+
*/
|
|
39
|
+
class PageHeaderComponent {
|
|
40
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
41
|
+
description = input(null, ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
|
|
42
|
+
headingLevel = input(1, ...(ngDevMode ? [{ debugName: "headingLevel" }] : /* istanbul ignore next */ []));
|
|
43
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
44
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: PageHeaderComponent, isStandalone: true, selector: "fk-page-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, headingLevel: { classPropertyName: "headingLevel", publicName: "headingLevel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<header class=\"fk-page-header\">\n <div class=\"fk-page-header__text\">\n <div class=\"fk-page-header__title-row\">\n <ng-content select=\"[pageHeaderLeading]\" />\n\n <fk-headline [level]=\"headingLevel()\">{{ title() }}</fk-headline>\n\n <ng-content select=\"[pageHeaderTrailing]\" />\n </div>\n\n @if (description()) {\n <fk-text class=\"fk-page-header__description\" tone=\"muted\">{{\n description()\n }}</fk-text>\n }\n </div>\n\n <div class=\"fk-page-header__actions\">\n <ng-content select=\"[pageHeaderActions]\" />\n </div>\n</header>\n", styles: [":host{display:block;container-type:inline-size}.fk-page-header{display:flex;flex-direction:column;gap:var(--fk-rhythm-4, 1rem)}.fk-page-header__text{min-width:0}.fk-page-header__title-row{display:flex;align-items:center;gap:var(--fk-rhythm-2, .5rem)}.fk-page-header__title-row fk-headline{margin-block:0}.fk-page-header__description{display:block;margin-top:var(--fk-rhythm-2, .5rem);max-width:60ch;line-height:1.5}.fk-page-header__actions{display:flex;flex-direction:column;gap:var(--fk-rhythm-3, .75rem)}@container (min-width: 40rem){.fk-page-header{flex-direction:row;align-items:flex-start;justify-content:space-between}.fk-page-header__actions{flex-direction:row;flex-shrink:0;align-items:center}}\n"], dependencies: [{ kind: "component", type: HeadlineComponent, selector: "fk-headline", inputs: ["ariaLabel", "className", "fontSize", "id", "level", "variant", "visuallyHidden"] }, { kind: "component", type: TextComponent, selector: "fk-text", inputs: ["as", "variant", "tone", "align", "weight", "truncate", "maxLines", "gutterBottom", "italic", "visuallyHidden", "className", "id", "ariaLabel", "ariaDescribedBy", "role", "htmlFor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
45
|
+
}
|
|
46
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PageHeaderComponent, decorators: [{
|
|
47
|
+
type: Component,
|
|
48
|
+
args: [{ selector: 'fk-page-header', standalone: true, imports: [HeadlineComponent, TextComponent], encapsulation: ViewEncapsulation.Emulated, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fk-page-header\">\n <div class=\"fk-page-header__text\">\n <div class=\"fk-page-header__title-row\">\n <ng-content select=\"[pageHeaderLeading]\" />\n\n <fk-headline [level]=\"headingLevel()\">{{ title() }}</fk-headline>\n\n <ng-content select=\"[pageHeaderTrailing]\" />\n </div>\n\n @if (description()) {\n <fk-text class=\"fk-page-header__description\" tone=\"muted\">{{\n description()\n }}</fk-text>\n }\n </div>\n\n <div class=\"fk-page-header__actions\">\n <ng-content select=\"[pageHeaderActions]\" />\n </div>\n</header>\n", styles: [":host{display:block;container-type:inline-size}.fk-page-header{display:flex;flex-direction:column;gap:var(--fk-rhythm-4, 1rem)}.fk-page-header__text{min-width:0}.fk-page-header__title-row{display:flex;align-items:center;gap:var(--fk-rhythm-2, .5rem)}.fk-page-header__title-row fk-headline{margin-block:0}.fk-page-header__description{display:block;margin-top:var(--fk-rhythm-2, .5rem);max-width:60ch;line-height:1.5}.fk-page-header__actions{display:flex;flex-direction:column;gap:var(--fk-rhythm-3, .75rem)}@container (min-width: 40rem){.fk-page-header{flex-direction:row;align-items:flex-start;justify-content:space-between}.fk-page-header__actions{flex-direction:row;flex-shrink:0;align-items:center}}\n"] }]
|
|
49
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], headingLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingLevel", required: false }] }] } });
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Empty-state panel for dashboard tables and lists: a centered icon, a title,
|
|
53
|
+
* an optional description, and a projected actions slot (typically one primary
|
|
54
|
+
* fk-button). Bordered-card styling, meant to stand in for a table body when
|
|
55
|
+
* there are no rows.
|
|
56
|
+
*
|
|
57
|
+
* The action row flips from a horizontal row to a centered vertical stack on
|
|
58
|
+
* narrow widths, driven by a container query on the host — so it reacts to the
|
|
59
|
+
* empty-state's own width (e.g. inside a split column) rather than the viewport.
|
|
60
|
+
*
|
|
61
|
+
* <fk-table-empty-state
|
|
62
|
+
* icon="globe"
|
|
63
|
+
* title="No webhooks configured"
|
|
64
|
+
* description="Add a webhook to receive events."
|
|
65
|
+
* >
|
|
66
|
+
* <fk-button variant="primary" size="sm">Add webhook</fk-button>
|
|
67
|
+
* </fk-table-empty-state>
|
|
68
|
+
*
|
|
69
|
+
* The actions slot is an open `ng-content`, so project as many actions as the
|
|
70
|
+
* design needs (one primary is the norm; two reads cleanly).
|
|
71
|
+
*/
|
|
72
|
+
class TableEmptyStateComponent {
|
|
73
|
+
// ===== INPUTS =====
|
|
74
|
+
icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
75
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
76
|
+
description = input(null, ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
|
|
77
|
+
// ===== BASE PROPS =====
|
|
78
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
79
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
80
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
81
|
+
// ===== COMPUTED =====
|
|
82
|
+
classes = computed(() => ['fk-table-empty-state', this.className()].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
83
|
+
get hostClass() {
|
|
84
|
+
return this.classes();
|
|
85
|
+
}
|
|
86
|
+
get hostId() {
|
|
87
|
+
return this.id();
|
|
88
|
+
}
|
|
89
|
+
get hostAriaLabel() {
|
|
90
|
+
return this.ariaLabel();
|
|
91
|
+
}
|
|
92
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TableEmptyStateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
93
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: TableEmptyStateComponent, isStandalone: true, selector: "fk-table-empty-state", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass", "attr.id": "this.hostId", "attr.aria-label": "this.hostAriaLabel" } }, ngImport: i0, template: "@if (icon()) {\n <div class=\"fk-table-empty-state__icon\">\n <fk-icon [name]=\"icon()!\" size=\"lg\" />\n </div>\n}\n\n<h3 class=\"fk-table-empty-state__title\">{{ title() }}</h3>\n\n@if (description()) {\n <p class=\"fk-table-empty-state__description\">{{ description() }}</p>\n}\n\n<div class=\"fk-table-empty-state__actions\">\n <ng-content />\n</div>\n", styles: [":host{container-type:inline-size;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:var(--fk-table-empty-state-gap, var(--fk-rhythm-3, .75rem));padding:var(--fk-table-empty-state-padding-block, var(--fk-rhythm-8, 2rem)) var(--fk-table-empty-state-padding-inline, var(--fk-rhythm-4, 1rem));border:1px solid var(--fk-table-empty-state-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-table-empty-state-radius, var(--fk-radius-lg, .75rem));background:var(--fk-table-empty-state-bg, var(--fk-color-surface, #ffffff))}.fk-table-empty-state__icon{color:var(--fk-table-empty-state-icon-color, var(--fk-color-muted, #8a98a8))}.fk-table-empty-state__title{margin:0;font-size:var(--fk-table-empty-state-title-font-size, var(--fk-typography-body-font-size, .9375rem));font-weight:var(--fk-table-empty-state-title-font-weight, var(--fk-font-weight-semibold, 600));color:var(--fk-table-empty-state-title-color, var(--fk-color-text-strong, #0b1420))}.fk-table-empty-state__description{margin:0;font-size:var(--fk-table-empty-state-description-font-size, var(--fk-typography-small-font-size, .8125rem));color:var(--fk-table-empty-state-description-color, var(--fk-color-muted, #8a98a8));max-width:var(--fk-table-empty-state-description-max-width, 24rem)}.fk-table-empty-state__actions{display:flex;gap:var(--fk-table-empty-state-actions-gap, var(--fk-rhythm-2, .5rem));margin-top:var(--fk-table-empty-state-actions-margin-top, var(--fk-rhythm-1, .25rem))}.fk-table-empty-state__actions:empty{display:none}@container (max-width: 28rem){.fk-table-empty-state__actions{flex-direction:column;align-self:stretch;align-items:stretch}}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fk-icon", inputs: ["name", "size", "color", "className", "id", "ariaLabel", "ariaHidden"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
94
|
+
}
|
|
95
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TableEmptyStateComponent, decorators: [{
|
|
96
|
+
type: Component,
|
|
97
|
+
args: [{ selector: 'fk-table-empty-state', standalone: true, imports: [IconComponent], encapsulation: ViewEncapsulation.Emulated, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (icon()) {\n <div class=\"fk-table-empty-state__icon\">\n <fk-icon [name]=\"icon()!\" size=\"lg\" />\n </div>\n}\n\n<h3 class=\"fk-table-empty-state__title\">{{ title() }}</h3>\n\n@if (description()) {\n <p class=\"fk-table-empty-state__description\">{{ description() }}</p>\n}\n\n<div class=\"fk-table-empty-state__actions\">\n <ng-content />\n</div>\n", styles: [":host{container-type:inline-size;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:var(--fk-table-empty-state-gap, var(--fk-rhythm-3, .75rem));padding:var(--fk-table-empty-state-padding-block, var(--fk-rhythm-8, 2rem)) var(--fk-table-empty-state-padding-inline, var(--fk-rhythm-4, 1rem));border:1px solid var(--fk-table-empty-state-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-table-empty-state-radius, var(--fk-radius-lg, .75rem));background:var(--fk-table-empty-state-bg, var(--fk-color-surface, #ffffff))}.fk-table-empty-state__icon{color:var(--fk-table-empty-state-icon-color, var(--fk-color-muted, #8a98a8))}.fk-table-empty-state__title{margin:0;font-size:var(--fk-table-empty-state-title-font-size, var(--fk-typography-body-font-size, .9375rem));font-weight:var(--fk-table-empty-state-title-font-weight, var(--fk-font-weight-semibold, 600));color:var(--fk-table-empty-state-title-color, var(--fk-color-text-strong, #0b1420))}.fk-table-empty-state__description{margin:0;font-size:var(--fk-table-empty-state-description-font-size, var(--fk-typography-small-font-size, .8125rem));color:var(--fk-table-empty-state-description-color, var(--fk-color-muted, #8a98a8));max-width:var(--fk-table-empty-state-description-max-width, 24rem)}.fk-table-empty-state__actions{display:flex;gap:var(--fk-table-empty-state-actions-gap, var(--fk-rhythm-2, .5rem));margin-top:var(--fk-table-empty-state-actions-margin-top, var(--fk-rhythm-1, .25rem))}.fk-table-empty-state__actions:empty{display:none}@container (max-width: 28rem){.fk-table-empty-state__actions{flex-direction:column;align-self:stretch;align-items:stretch}}\n"] }]
|
|
98
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hostClass: [{
|
|
99
|
+
type: HostBinding,
|
|
100
|
+
args: ['class']
|
|
101
|
+
}], hostId: [{
|
|
102
|
+
type: HostBinding,
|
|
103
|
+
args: ['attr.id']
|
|
104
|
+
}], hostAriaLabel: [{
|
|
105
|
+
type: HostBinding,
|
|
106
|
+
args: ['attr.aria-label']
|
|
107
|
+
}] } });
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Two-column split layout with container-query responsive stacking.
|
|
111
|
+
*
|
|
112
|
+
* Projects content into `[start]` (grows to fill) and `[end]` (content-sized or
|
|
113
|
+
* fixed width). Below 700px container width, columns stack vertically.
|
|
114
|
+
*
|
|
115
|
+
* A standalone layout primitive for custom dashboard rows.
|
|
116
|
+
*/
|
|
117
|
+
class ContentSplitLayoutComponent {
|
|
118
|
+
endWidth = input(null, ...(ngDevMode ? [{ debugName: "endWidth" }] : /* istanbul ignore next */ []));
|
|
119
|
+
gap = input('var(--fk-rhythm-4, 1rem)', ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
|
|
120
|
+
hostClass = 'fk-content-split-layout';
|
|
121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ContentSplitLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
122
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: ContentSplitLayoutComponent, isStandalone: true, selector: "fk-content-split-layout", inputs: { endWidth: { classPropertyName: "endWidth", publicName: "endWidth", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, ngImport: i0, template: "<div\n class=\"fk-content-split-layout__row\"\n [style.--fk-content-split-gap]=\"gap()\"\n>\n <div class=\"fk-content-split-layout__start\">\n <ng-content select=\"[start]\" />\n </div>\n\n <div\n class=\"fk-content-split-layout__end\"\n [class.fk-content-split-layout__end--fixed]=\"endWidth()\"\n [style.--fk-content-split-end-width]=\"endWidth()\"\n >\n <ng-content select=\"[end]\" />\n </div>\n</div>\n", styles: [":host{display:block;container-type:inline-size}.fk-content-split-layout__row{display:flex;flex-direction:column;gap:var(--fk-content-split-gap, var(--fk-rhythm-4, 1rem))}@container (min-width: 700px){.fk-content-split-layout__row{flex-direction:row;align-items:flex-start}}.fk-content-split-layout__start{display:flex;flex-direction:column;padding-top:var(--fk-rhythm-4, 1rem);width:100%}.fk-content-split-layout__start>*+*{margin-top:var(--fk-content-split-gap, var(--fk-rhythm-4, 1rem))}@container (min-width: 700px){.fk-content-split-layout__start{flex:1;min-width:0}}.fk-content-split-layout__end{width:100%}@container (min-width: 700px){.fk-content-split-layout__end{flex:0 0 auto}}@container (min-width: 700px){.fk-content-split-layout__end--fixed{flex:0 0 var(--fk-content-split-end-width)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
123
|
+
}
|
|
124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ContentSplitLayoutComponent, decorators: [{
|
|
125
|
+
type: Component,
|
|
126
|
+
args: [{ selector: 'fk-content-split-layout', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"fk-content-split-layout__row\"\n [style.--fk-content-split-gap]=\"gap()\"\n>\n <div class=\"fk-content-split-layout__start\">\n <ng-content select=\"[start]\" />\n </div>\n\n <div\n class=\"fk-content-split-layout__end\"\n [class.fk-content-split-layout__end--fixed]=\"endWidth()\"\n [style.--fk-content-split-end-width]=\"endWidth()\"\n >\n <ng-content select=\"[end]\" />\n </div>\n</div>\n", styles: [":host{display:block;container-type:inline-size}.fk-content-split-layout__row{display:flex;flex-direction:column;gap:var(--fk-content-split-gap, var(--fk-rhythm-4, 1rem))}@container (min-width: 700px){.fk-content-split-layout__row{flex-direction:row;align-items:flex-start}}.fk-content-split-layout__start{display:flex;flex-direction:column;padding-top:var(--fk-rhythm-4, 1rem);width:100%}.fk-content-split-layout__start>*+*{margin-top:var(--fk-content-split-gap, var(--fk-rhythm-4, 1rem))}@container (min-width: 700px){.fk-content-split-layout__start{flex:1;min-width:0}}.fk-content-split-layout__end{width:100%}@container (min-width: 700px){.fk-content-split-layout__end{flex:0 0 auto}}@container (min-width: 700px){.fk-content-split-layout__end--fixed{flex:0 0 var(--fk-content-split-end-width)}}\n"] }]
|
|
127
|
+
}], propDecorators: { endWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "endWidth", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], hostClass: [{
|
|
128
|
+
type: HostBinding,
|
|
129
|
+
args: ['class']
|
|
130
|
+
}] } });
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Responsive column layout for dashboard content. Lays projected children out
|
|
134
|
+
* in equal-width columns that wrap — and ultimately collapse to a single
|
|
135
|
+
* stacked column — based on the layout's *own* available width, never the
|
|
136
|
+
* viewport. So it reacts correctly when the dashboard content region is
|
|
137
|
+
* squeezed (sidenav open, a drawer pushed in, a split pane) while the viewport
|
|
138
|
+
* still reports "desktop".
|
|
139
|
+
*
|
|
140
|
+
* Implemented with an intrinsic CSS grid — `repeat(auto-fit, minmax(min, 1fr))`
|
|
141
|
+
* — so there is no container query, no JS, and no viewport breakpoint to keep
|
|
142
|
+
* in sync. `minColumnWidth` is the smallest a column may shrink to before the
|
|
143
|
+
* grid drops a column; empty tracks collapse, so N children never produce more
|
|
144
|
+
* than N columns. Spacing on both axes is a single `gutter` gap, so wrapped
|
|
145
|
+
* rows and side-by-side columns separate consistently with no edge artifacts.
|
|
146
|
+
*
|
|
147
|
+
* <fk-columns minColumnWidth="18rem">
|
|
148
|
+
* <fk-field-group>…</fk-field-group>
|
|
149
|
+
* <fk-field-group>…</fk-field-group>
|
|
150
|
+
* </fk-columns>
|
|
151
|
+
*/
|
|
152
|
+
class ColumnsComponent {
|
|
153
|
+
// ===== INPUTS =====
|
|
154
|
+
/**
|
|
155
|
+
* The smallest width a column may shrink to before the grid drops to fewer
|
|
156
|
+
* columns. This is the single knob that controls when columns wrap/stack.
|
|
157
|
+
*/
|
|
158
|
+
minColumnWidth = input('16rem', ...(ngDevMode ? [{ debugName: "minColumnWidth" }] : /* istanbul ignore next */ []));
|
|
159
|
+
/** Gap between side-by-side columns and between wrapped rows. */
|
|
160
|
+
gutter = input('var(--fk-rhythm-4, 1rem)', ...(ngDevMode ? [{ debugName: "gutter" }] : /* istanbul ignore next */ []));
|
|
161
|
+
// ===== BASE PROPS =====
|
|
162
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
163
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
164
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
165
|
+
// ===== COMPUTED =====
|
|
166
|
+
classes = computed(() => ['fk-columns', this.className()].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
167
|
+
get hostClass() {
|
|
168
|
+
return this.classes();
|
|
169
|
+
}
|
|
170
|
+
get hostId() {
|
|
171
|
+
return this.id();
|
|
172
|
+
}
|
|
173
|
+
get hostAriaLabel() {
|
|
174
|
+
return this.ariaLabel();
|
|
175
|
+
}
|
|
176
|
+
get hostMin() {
|
|
177
|
+
return this.minColumnWidth();
|
|
178
|
+
}
|
|
179
|
+
get hostGutter() {
|
|
180
|
+
return this.gutter();
|
|
181
|
+
}
|
|
182
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ColumnsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
183
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: ColumnsComponent, isStandalone: true, selector: "fk-columns", inputs: { minColumnWidth: { classPropertyName: "minColumnWidth", publicName: "minColumnWidth", isSignal: true, isRequired: false, transformFunction: null }, gutter: { classPropertyName: "gutter", publicName: "gutter", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass", "attr.id": "this.hostId", "attr.aria-label": "this.hostAriaLabel", "style.--fk-columns-min": "this.hostMin", "style.--fk-columns-gutter": "this.hostGutter" } }, ngImport: i0, template: "<ng-content />\n", styles: [":host{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--fk-columns-min, 16rem),100%),1fr));gap:var(--fk-columns-gutter, var(--fk-rhythm-4, 1rem));align-items:start}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
184
|
+
}
|
|
185
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ColumnsComponent, decorators: [{
|
|
186
|
+
type: Component,
|
|
187
|
+
args: [{ selector: 'fk-columns', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content />\n", styles: [":host{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--fk-columns-min, 16rem),100%),1fr));gap:var(--fk-columns-gutter, var(--fk-rhythm-4, 1rem));align-items:start}\n"] }]
|
|
188
|
+
}], propDecorators: { minColumnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minColumnWidth", required: false }] }], gutter: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutter", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hostClass: [{
|
|
189
|
+
type: HostBinding,
|
|
190
|
+
args: ['class']
|
|
191
|
+
}], hostId: [{
|
|
192
|
+
type: HostBinding,
|
|
193
|
+
args: ['attr.id']
|
|
194
|
+
}], hostAriaLabel: [{
|
|
195
|
+
type: HostBinding,
|
|
196
|
+
args: ['attr.aria-label']
|
|
197
|
+
}], hostMin: [{
|
|
198
|
+
type: HostBinding,
|
|
199
|
+
args: ['style.--fk-columns-min']
|
|
200
|
+
}], hostGutter: [{
|
|
201
|
+
type: HostBinding,
|
|
202
|
+
args: ['style.--fk-columns-gutter']
|
|
203
|
+
}] } });
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Padded container for an expanded dashboard detail region — typically the body
|
|
207
|
+
* of a data-table row expansion, holding an `fk-columns` of form controls.
|
|
208
|
+
*
|
|
209
|
+
* The `disabled` input greys the panel and makes its entire subtree `inert`
|
|
210
|
+
* (non-interactive, unfocusable, hidden from assistive technology) — for
|
|
211
|
+
* controls gated behind a feature toggle that must not be editable until the
|
|
212
|
+
* feature is enabled.
|
|
213
|
+
*
|
|
214
|
+
* <fk-detail-panel [disabled]="!feature.enabled">
|
|
215
|
+
* <fk-columns>…</fk-columns>
|
|
216
|
+
* </fk-detail-panel>
|
|
217
|
+
*/
|
|
218
|
+
class DetailPanelComponent {
|
|
219
|
+
// ===== INPUTS =====
|
|
220
|
+
/**
|
|
221
|
+
* Greys the panel and makes its content inert (locked) — for feature-gated
|
|
222
|
+
* controls that should not be editable until the feature is enabled.
|
|
223
|
+
*/
|
|
224
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
225
|
+
// ===== BASE PROPS =====
|
|
226
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
227
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
228
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
229
|
+
// ===== COMPUTED =====
|
|
230
|
+
classes = computed(() => [
|
|
231
|
+
'fk-detail-panel',
|
|
232
|
+
this.disabled() ? 'fk-detail-panel--disabled' : '',
|
|
233
|
+
this.className(),
|
|
234
|
+
]
|
|
235
|
+
.filter(Boolean)
|
|
236
|
+
.join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
237
|
+
get hostClass() {
|
|
238
|
+
return this.classes();
|
|
239
|
+
}
|
|
240
|
+
get hostId() {
|
|
241
|
+
return this.id();
|
|
242
|
+
}
|
|
243
|
+
get hostAriaLabel() {
|
|
244
|
+
return this.ariaLabel();
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Lock the whole subtree when disabled. `inert` blocks pointer interaction,
|
|
248
|
+
* focus, and assistive-tech access in one attribute — stronger and more
|
|
249
|
+
* correct than `pointer-events: none`, which only blocks the mouse.
|
|
250
|
+
*/
|
|
251
|
+
get hostInert() {
|
|
252
|
+
return this.disabled() ? '' : null;
|
|
253
|
+
}
|
|
254
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DetailPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
255
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: DetailPanelComponent, isStandalone: true, selector: "fk-detail-panel", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass", "attr.id": "this.hostId", "attr.aria-label": "this.hostAriaLabel", "attr.inert": "this.hostInert" } }, ngImport: i0, template: "<ng-content />\n", styles: [":host{display:block;padding:var(--fk-detail-panel-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-detail-panel-padding-inline, var(--fk-rhythm-1, .25rem))}:host.fk-detail-panel--disabled{opacity:var(--fk-detail-panel-disabled-opacity, .55)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
256
|
+
}
|
|
257
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DetailPanelComponent, decorators: [{
|
|
258
|
+
type: Component,
|
|
259
|
+
args: [{ selector: 'fk-detail-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content />\n", styles: [":host{display:block;padding:var(--fk-detail-panel-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-detail-panel-padding-inline, var(--fk-rhythm-1, .25rem))}:host.fk-detail-panel--disabled{opacity:var(--fk-detail-panel-disabled-opacity, .55)}\n"] }]
|
|
260
|
+
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hostClass: [{
|
|
261
|
+
type: HostBinding,
|
|
262
|
+
args: ['class']
|
|
263
|
+
}], hostId: [{
|
|
264
|
+
type: HostBinding,
|
|
265
|
+
args: ['attr.id']
|
|
266
|
+
}], hostAriaLabel: [{
|
|
267
|
+
type: HostBinding,
|
|
268
|
+
args: ['attr.aria-label']
|
|
269
|
+
}], hostInert: [{
|
|
270
|
+
type: HostBinding,
|
|
271
|
+
args: ['attr.inert']
|
|
272
|
+
}] } });
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Generated bundle index. Do not edit.
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
export { ColumnsComponent, ContentSplitLayoutComponent, DetailPanelComponent, PageHeaderComponent, TableEmptyStateComponent };
|
|
279
|
+
//# sourceMappingURL=frame-kit-ui-ng-patterns-dashboard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-kit-ui-ng-patterns-dashboard.mjs","sources":["../../../../packages/ui-ng-patterns/dashboard/page-header/page-header.component.ts","../../../../packages/ui-ng-patterns/dashboard/page-header/page-header.component.html","../../../../packages/ui-ng-patterns/dashboard/table-empty-state/table-empty-state.component.ts","../../../../packages/ui-ng-patterns/dashboard/table-empty-state/table-empty-state.component.html","../../../../packages/ui-ng-patterns/dashboard/content-split-layout/content-split-layout.component.ts","../../../../packages/ui-ng-patterns/dashboard/content-split-layout/content-split-layout.component.html","../../../../packages/ui-ng-patterns/dashboard/columns/columns.component.ts","../../../../packages/ui-ng-patterns/dashboard/columns/columns.component.html","../../../../packages/ui-ng-patterns/dashboard/detail-panel/detail-panel.component.ts","../../../../packages/ui-ng-patterns/dashboard/detail-panel/detail-panel.component.html","../../../../packages/ui-ng-patterns/dashboard/frame-kit-ui-ng-patterns-dashboard.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport type { HeadlineLevel } from '@frame-kit/ui-ng/core/headline';\nimport { HeadlineComponent } from '@frame-kit/ui-ng/core/headline';\nimport { TextComponent } from '@frame-kit/ui-ng/core/text';\n\n/**\n * Page-level dashboard header: title + optional description on the left, up to\n * ~3 actions on the right.\n *\n * When the header's own width gets tight, the actions drop below the text and\n * go full-width (mobile-style). The break is driven by a CSS container query on\n * the host, so it responds to the content region's width — not the viewport,\n * which means it behaves correctly inside a narrow panel.\n *\n * Project actions by tagging each button with `pageHeaderActions`:\n *\n * <fk-page-header title=\"Users\" description=\"...\">\n * <fk-button pageHeaderActions variant=\"outline\" size=\"sm\">Import</fk-button>\n * <fk-button pageHeaderActions variant=\"primary\" size=\"sm\">Create user</fk-button>\n * </fk-page-header>\n *\n * Tagging each button (rather than wrapping them in a div) makes every button a\n * direct flex child, so full-width stacking falls out of `align-items: stretch`\n * with no `::ng-deep` reach-in.\n *\n * The title row has two projected slots — `[pageHeaderLeading]` before the\n * title and `[pageHeaderTrailing]` after it. Project a plain `fk-icon` for a\n * decorative mark, or a `button` for a clickable one (the consumer owns the\n * interaction, focus ring, and aria-label):\n *\n * <fk-page-header title=\"Permissions\">\n * <fk-icon pageHeaderLeading name=\"key-outline\" />\n * <button pageHeaderTrailing type=\"button\" aria-label=\"Docs\" (click)=\"openDocs()\">\n * <fk-icon name=\"view-api\" />\n * </button>\n * </fk-page-header>\n */\n@Component({\n selector: 'fk-page-header',\n standalone: true,\n imports: [HeadlineComponent, TextComponent],\n // Emulated (not None) so `:host` works — `:host { display: block }` is what\n // keeps the custom element from defaulting to `display: inline` and\n // collapsing to min-content under the container query below.\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './page-header.component.html',\n styleUrl: './page-header.component.scss',\n})\nexport class PageHeaderComponent {\n readonly title = input.required<string>();\n readonly description = input<string | null>(null);\n readonly headingLevel = input<HeadlineLevel>(1);\n}\n","<header class=\"fk-page-header\">\n <div class=\"fk-page-header__text\">\n <div class=\"fk-page-header__title-row\">\n <ng-content select=\"[pageHeaderLeading]\" />\n\n <fk-headline [level]=\"headingLevel()\">{{ title() }}</fk-headline>\n\n <ng-content select=\"[pageHeaderTrailing]\" />\n </div>\n\n @if (description()) {\n <fk-text class=\"fk-page-header__description\" tone=\"muted\">{{\n description()\n }}</fk-text>\n }\n </div>\n\n <div class=\"fk-page-header__actions\">\n <ng-content select=\"[pageHeaderActions]\" />\n </div>\n</header>\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { IconComponent } from '@frame-kit/ui-ng/core/icon';\n\n/**\n * Empty-state panel for dashboard tables and lists: a centered icon, a title,\n * an optional description, and a projected actions slot (typically one primary\n * fk-button). Bordered-card styling, meant to stand in for a table body when\n * there are no rows.\n *\n * The action row flips from a horizontal row to a centered vertical stack on\n * narrow widths, driven by a container query on the host — so it reacts to the\n * empty-state's own width (e.g. inside a split column) rather than the viewport.\n *\n * <fk-table-empty-state\n * icon=\"globe\"\n * title=\"No webhooks configured\"\n * description=\"Add a webhook to receive events.\"\n * >\n * <fk-button variant=\"primary\" size=\"sm\">Add webhook</fk-button>\n * </fk-table-empty-state>\n *\n * The actions slot is an open `ng-content`, so project as many actions as the\n * design needs (one primary is the norm; two reads cleanly).\n */\n@Component({\n selector: 'fk-table-empty-state',\n standalone: true,\n imports: [IconComponent],\n // Emulated (default) so `:host` works — the host is the bordered flex card\n // and establishes the container-query context the actions break against.\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './table-empty-state.component.html',\n styleUrl: './table-empty-state.component.scss',\n})\nexport class TableEmptyStateComponent {\n // ===== INPUTS =====\n readonly icon = input<string | null>(null);\n readonly title = input.required<string>();\n readonly description = input<string | null>(null);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-table-empty-state', this.className()].filter(Boolean).join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n}\n","@if (icon()) {\n <div class=\"fk-table-empty-state__icon\">\n <fk-icon [name]=\"icon()!\" size=\"lg\" />\n </div>\n}\n\n<h3 class=\"fk-table-empty-state__title\">{{ title() }}</h3>\n\n@if (description()) {\n <p class=\"fk-table-empty-state__description\">{{ description() }}</p>\n}\n\n<div class=\"fk-table-empty-state__actions\">\n <ng-content />\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Two-column split layout with container-query responsive stacking.\n *\n * Projects content into `[start]` (grows to fill) and `[end]` (content-sized or\n * fixed width). Below 700px container width, columns stack vertically.\n *\n * A standalone layout primitive for custom dashboard rows.\n */\n@Component({\n selector: 'fk-content-split-layout',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './content-split-layout.component.html',\n styleUrl: './content-split-layout.component.scss',\n})\nexport class ContentSplitLayoutComponent {\n readonly endWidth = input<string | null>(null);\n readonly gap = input<string>('var(--fk-rhythm-4, 1rem)');\n\n @HostBinding('class')\n readonly hostClass = 'fk-content-split-layout';\n}\n","<div\n class=\"fk-content-split-layout__row\"\n [style.--fk-content-split-gap]=\"gap()\"\n>\n <div class=\"fk-content-split-layout__start\">\n <ng-content select=\"[start]\" />\n </div>\n\n <div\n class=\"fk-content-split-layout__end\"\n [class.fk-content-split-layout__end--fixed]=\"endWidth()\"\n [style.--fk-content-split-end-width]=\"endWidth()\"\n >\n <ng-content select=\"[end]\" />\n </div>\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Responsive column layout for dashboard content. Lays projected children out\n * in equal-width columns that wrap — and ultimately collapse to a single\n * stacked column — based on the layout's *own* available width, never the\n * viewport. So it reacts correctly when the dashboard content region is\n * squeezed (sidenav open, a drawer pushed in, a split pane) while the viewport\n * still reports \"desktop\".\n *\n * Implemented with an intrinsic CSS grid — `repeat(auto-fit, minmax(min, 1fr))`\n * — so there is no container query, no JS, and no viewport breakpoint to keep\n * in sync. `minColumnWidth` is the smallest a column may shrink to before the\n * grid drops a column; empty tracks collapse, so N children never produce more\n * than N columns. Spacing on both axes is a single `gutter` gap, so wrapped\n * rows and side-by-side columns separate consistently with no edge artifacts.\n *\n * <fk-columns minColumnWidth=\"18rem\">\n * <fk-field-group>…</fk-field-group>\n * <fk-field-group>…</fk-field-group>\n * </fk-columns>\n */\n@Component({\n selector: 'fk-columns',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './columns.component.html',\n styleUrl: './columns.component.scss',\n})\nexport class ColumnsComponent {\n // ===== INPUTS =====\n /**\n * The smallest width a column may shrink to before the grid drops to fewer\n * columns. This is the single knob that controls when columns wrap/stack.\n */\n readonly minColumnWidth = input<string>('16rem');\n /** Gap between side-by-side columns and between wrapped rows. */\n readonly gutter = input<string>('var(--fk-rhythm-4, 1rem)');\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-columns', this.className()].filter(Boolean).join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n\n @HostBinding('style.--fk-columns-min')\n get hostMin(): string {\n return this.minColumnWidth();\n }\n\n @HostBinding('style.--fk-columns-gutter')\n get hostGutter(): string {\n return this.gutter();\n }\n}\n","<ng-content />\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Padded container for an expanded dashboard detail region — typically the body\n * of a data-table row expansion, holding an `fk-columns` of form controls.\n *\n * The `disabled` input greys the panel and makes its entire subtree `inert`\n * (non-interactive, unfocusable, hidden from assistive technology) — for\n * controls gated behind a feature toggle that must not be editable until the\n * feature is enabled.\n *\n * <fk-detail-panel [disabled]=\"!feature.enabled\">\n * <fk-columns>…</fk-columns>\n * </fk-detail-panel>\n */\n@Component({\n selector: 'fk-detail-panel',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './detail-panel.component.html',\n styleUrl: './detail-panel.component.scss',\n})\nexport class DetailPanelComponent {\n // ===== INPUTS =====\n /**\n * Greys the panel and makes its content inert (locked) — for feature-gated\n * controls that should not be editable until the feature is enabled.\n */\n readonly disabled = input<boolean>(false);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n [\n 'fk-detail-panel',\n this.disabled() ? 'fk-detail-panel--disabled' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n\n /**\n * Lock the whole subtree when disabled. `inert` blocks pointer interaction,\n * focus, and assistive-tech access in one attribute — stronger and more\n * correct than `pointer-events: none`, which only blocks the mouse.\n */\n @HostBinding('attr.inert')\n get hostInert(): '' | null {\n return this.disabled() ? '' : null;\n }\n}\n","<ng-content />\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, HostBinding, ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
|
3
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
4
|
+
import { AppShellComponent } from '@frame-kit/ui-ng/layouts/app-shell';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Small rectangular badge showing an HTTP method label (e.g. `GET`, `POST`).
|
|
8
|
+
*
|
|
9
|
+
* Each method has its own hue so consumers can identify the verb in a dense
|
|
10
|
+
* list. Background colors are exposed as CSS variables
|
|
11
|
+
* (`--fk-method-badge-bg-get`, etc.) with defaults baked in so the component
|
|
12
|
+
* renders correctly without a theme.
|
|
13
|
+
*/
|
|
14
|
+
class MethodBadgeComponent {
|
|
15
|
+
// ===== INPUTS =====
|
|
16
|
+
method = input.required(...(ngDevMode ? [{ debugName: "method" }] : /* istanbul ignore next */ []));
|
|
17
|
+
// ===== BASE PROPS =====
|
|
18
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
19
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
20
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
21
|
+
// ===== COMPUTED =====
|
|
22
|
+
classes = computed(() => ['fk-method-badge', `fk-method-badge--${this.method()}`, this.className()]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
25
|
+
label = computed(() => this.method().toUpperCase(), ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
26
|
+
get hostClass() {
|
|
27
|
+
return this.classes();
|
|
28
|
+
}
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MethodBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: MethodBadgeComponent, isStandalone: true, selector: "fk-method-badge", inputs: { method: { classPropertyName: "method", publicName: "method", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, ngImport: i0, template: "<span\n class=\"fk-method-badge__label\"\n [id]=\"id() ?? undefined\"\n [attr.aria-label]=\"ariaLabel()\"\n >{{ label() }}</span\n>\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fk-method-badge-min-width, 3rem);padding:var(--fk-method-badge-padding-block, 2px) var(--fk-method-badge-padding-inline, var(--fk-rhythm-2, .5rem));border-radius:var(--fk-method-badge-radius, var(--fk-radius-sm, .25rem));background-color:var(--fk-method-badge-bg, var(--fk-method-badge-bg-neutral, #d6d6d6));color:var(--fk-method-badge-color, var(--fk-color-text, #1f2d3d));font-family:var(--fk-method-badge-font-family, var(--fk-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace));font-size:var(--fk-method-badge-font-size, .6875rem);font-weight:var(--fk-method-badge-font-weight, var(--fk-font-weight-bold, 700));letter-spacing:.05em;line-height:1.4;text-transform:uppercase}:host.fk-method-badge--get{background-color:var(--fk-method-badge-bg-get, #89ecac);color:var(--fk-method-badge-color-get, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--post{background-color:var(--fk-method-badge-bg-post, #94c4fd);color:var(--fk-method-badge-color-post, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--put{background-color:var(--fk-method-badge-bg-put, #fbb972);color:var(--fk-method-badge-color-put, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--patch{background-color:var(--fk-method-badge-bg-patch, #c8a7fb);color:var(--fk-method-badge-color-patch, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--delete{background-color:var(--fk-method-badge-bg-delete, #fb8d8d);color:var(--fk-method-badge-color-delete, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--head{background-color:var(--fk-method-badge-bg-head, var(--fk-method-badge-bg-neutral, #d6d6d6));color:var(--fk-method-badge-color-head, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--options{background-color:var(--fk-method-badge-bg-options, var(--fk-method-badge-bg-neutral, #d6d6d6));color:var(--fk-method-badge-color-options, var(--fk-color-text, #1f2d3d))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
31
|
+
}
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MethodBadgeComponent, decorators: [{
|
|
33
|
+
type: Component,
|
|
34
|
+
args: [{ selector: 'fk-method-badge', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<span\n class=\"fk-method-badge__label\"\n [id]=\"id() ?? undefined\"\n [attr.aria-label]=\"ariaLabel()\"\n >{{ label() }}</span\n>\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fk-method-badge-min-width, 3rem);padding:var(--fk-method-badge-padding-block, 2px) var(--fk-method-badge-padding-inline, var(--fk-rhythm-2, .5rem));border-radius:var(--fk-method-badge-radius, var(--fk-radius-sm, .25rem));background-color:var(--fk-method-badge-bg, var(--fk-method-badge-bg-neutral, #d6d6d6));color:var(--fk-method-badge-color, var(--fk-color-text, #1f2d3d));font-family:var(--fk-method-badge-font-family, var(--fk-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace));font-size:var(--fk-method-badge-font-size, .6875rem);font-weight:var(--fk-method-badge-font-weight, var(--fk-font-weight-bold, 700));letter-spacing:.05em;line-height:1.4;text-transform:uppercase}:host.fk-method-badge--get{background-color:var(--fk-method-badge-bg-get, #89ecac);color:var(--fk-method-badge-color-get, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--post{background-color:var(--fk-method-badge-bg-post, #94c4fd);color:var(--fk-method-badge-color-post, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--put{background-color:var(--fk-method-badge-bg-put, #fbb972);color:var(--fk-method-badge-color-put, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--patch{background-color:var(--fk-method-badge-bg-patch, #c8a7fb);color:var(--fk-method-badge-color-patch, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--delete{background-color:var(--fk-method-badge-bg-delete, #fb8d8d);color:var(--fk-method-badge-color-delete, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--head{background-color:var(--fk-method-badge-bg-head, var(--fk-method-badge-bg-neutral, #d6d6d6));color:var(--fk-method-badge-color-head, var(--fk-color-text, #1f2d3d))}:host.fk-method-badge--options{background-color:var(--fk-method-badge-bg-options, var(--fk-method-badge-bg-neutral, #d6d6d6));color:var(--fk-method-badge-color-options, var(--fk-color-text, #1f2d3d))}\n"] }]
|
|
35
|
+
}], propDecorators: { method: [{ type: i0.Input, args: [{ isSignal: true, alias: "method", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hostClass: [{
|
|
36
|
+
type: HostBinding,
|
|
37
|
+
args: ['class']
|
|
38
|
+
}] } });
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Sidebar/list item for an API endpoint in developer documentation.
|
|
42
|
+
*
|
|
43
|
+
* Renders a leading `fk-method-badge` followed by the endpoint label as a
|
|
44
|
+
* single clickable router link. Used to list operations (e.g. "GET List
|
|
45
|
+
* permissions", "POST Create identity") in a documentation sidebar or any
|
|
46
|
+
* denser endpoint index.
|
|
47
|
+
*
|
|
48
|
+
* Active state is driven by `routerLinkActive`, matching the behavior of
|
|
49
|
+
* `fk-sidenav-link` so every router-aware nav item in the library resolves
|
|
50
|
+
* active state the same way.
|
|
51
|
+
*
|
|
52
|
+
* When rendered inside an `fk-app-shell`, clicks also call `dismissSidenav`
|
|
53
|
+
* on the ancestor shell — this closes the mobile overlay so the user lands
|
|
54
|
+
* directly on the selected endpoint. On desktop it's a no-op.
|
|
55
|
+
*/
|
|
56
|
+
class EndpointLinkComponent {
|
|
57
|
+
shell = inject(AppShellComponent, { optional: true });
|
|
58
|
+
// ===== INPUTS =====
|
|
59
|
+
method = input.required(...(ngDevMode ? [{ debugName: "method" }] : /* istanbul ignore next */ []));
|
|
60
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
routerLink = input.required(...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
|
|
63
|
+
/**
|
|
64
|
+
* Whether to require an exact URL match for the active state. Defaults to
|
|
65
|
+
* false — each endpoint has a unique final path segment so prefix matching
|
|
66
|
+
* resolves to the same thing in practice.
|
|
67
|
+
*/
|
|
68
|
+
exact = input(false, ...(ngDevMode ? [{ debugName: "exact" }] : /* istanbul ignore next */ []));
|
|
69
|
+
// ===== BASE PROPS =====
|
|
70
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
71
|
+
// ===== COMPUTED =====
|
|
72
|
+
classes = computed(() => ['fk-endpoint-link', this.className()].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
73
|
+
activeOptions = computed(() => ({ exact: this.exact() }), ...(ngDevMode ? [{ debugName: "activeOptions" }] : /* istanbul ignore next */ []));
|
|
74
|
+
get hostClass() {
|
|
75
|
+
return this.classes();
|
|
76
|
+
}
|
|
77
|
+
dismiss() {
|
|
78
|
+
this.shell?.dismissSidenav();
|
|
79
|
+
}
|
|
80
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EndpointLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
81
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: EndpointLinkComponent, isStandalone: true, selector: "fk-endpoint-link", inputs: { method: { classPropertyName: "method", publicName: "method", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: true, transformFunction: null }, exact: { classPropertyName: "exact", publicName: "exact", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, ngImport: i0, template: "<a\n class=\"fk-endpoint-link__anchor\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"fk-endpoint-link__anchor--active\"\n [routerLinkActiveOptions]=\"activeOptions()\"\n (click)=\"$event.stopPropagation(); dismiss()\"\n>\n <fk-method-badge [method]=\"method()\" class=\"fk-endpoint-link__badge\" />\n <span class=\"fk-endpoint-link__label\">{{ label() }}</span>\n</a>\n", styles: [".fk-endpoint-link{display:block}.fk-endpoint-link__anchor{display:flex;align-items:center;gap:var(--fk-endpoint-link-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-endpoint-link-padding-block, var(--fk-rhythm-1, .25rem)) var(--fk-endpoint-link-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-endpoint-link-radius, var(--fk-radius-md, .5rem));color:var(--fk-endpoint-link-color, var(--fk-color-text, #1f2d3d));font-size:var(--fk-endpoint-link-font-size, .875rem);line-height:1.4;text-decoration:none;transition:background-color .15s ease}.fk-endpoint-link__anchor:hover{background-color:var(--fk-endpoint-link-bg-hover, var(--fk-color-surface-muted, #f7f9fb))}.fk-endpoint-link__anchor:focus-visible{outline:none;box-shadow:var(--fk-endpoint-link-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-endpoint-link__anchor--active,.fk-endpoint-link__anchor--active:hover{background-color:var(--fk-endpoint-link-bg-active, var(--fk-color-primary-light, #dbeafe));color:var(--fk-endpoint-link-color-active, var(--fk-color-primary, #1e3a8a));font-weight:var(--fk-endpoint-link-font-weight-active, var(--fk-font-weight-semibold, 600))}.fk-endpoint-link__badge{flex-shrink:0}.fk-endpoint-link__label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\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: MethodBadgeComponent, selector: "fk-method-badge", inputs: ["method", "className", "id", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
82
|
+
}
|
|
83
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EndpointLinkComponent, decorators: [{
|
|
84
|
+
type: Component,
|
|
85
|
+
args: [{ selector: 'fk-endpoint-link', standalone: true, imports: [RouterLink, RouterLinkActive, MethodBadgeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<a\n class=\"fk-endpoint-link__anchor\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"fk-endpoint-link__anchor--active\"\n [routerLinkActiveOptions]=\"activeOptions()\"\n (click)=\"$event.stopPropagation(); dismiss()\"\n>\n <fk-method-badge [method]=\"method()\" class=\"fk-endpoint-link__badge\" />\n <span class=\"fk-endpoint-link__label\">{{ label() }}</span>\n</a>\n", styles: [".fk-endpoint-link{display:block}.fk-endpoint-link__anchor{display:flex;align-items:center;gap:var(--fk-endpoint-link-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-endpoint-link-padding-block, var(--fk-rhythm-1, .25rem)) var(--fk-endpoint-link-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-endpoint-link-radius, var(--fk-radius-md, .5rem));color:var(--fk-endpoint-link-color, var(--fk-color-text, #1f2d3d));font-size:var(--fk-endpoint-link-font-size, .875rem);line-height:1.4;text-decoration:none;transition:background-color .15s ease}.fk-endpoint-link__anchor:hover{background-color:var(--fk-endpoint-link-bg-hover, var(--fk-color-surface-muted, #f7f9fb))}.fk-endpoint-link__anchor:focus-visible{outline:none;box-shadow:var(--fk-endpoint-link-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18)))}.fk-endpoint-link__anchor--active,.fk-endpoint-link__anchor--active:hover{background-color:var(--fk-endpoint-link-bg-active, var(--fk-color-primary-light, #dbeafe));color:var(--fk-endpoint-link-color-active, var(--fk-color-primary, #1e3a8a));font-weight:var(--fk-endpoint-link-font-weight-active, var(--fk-font-weight-semibold, 600))}.fk-endpoint-link__badge{flex-shrink:0}.fk-endpoint-link__label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
|
|
86
|
+
}], propDecorators: { method: [{ type: i0.Input, args: [{ isSignal: true, alias: "method", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: true }] }], exact: [{ type: i0.Input, args: [{ isSignal: true, alias: "exact", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hostClass: [{
|
|
87
|
+
type: HostBinding,
|
|
88
|
+
args: ['class']
|
|
89
|
+
}] } });
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generated bundle index. Do not edit.
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
export { EndpointLinkComponent, MethodBadgeComponent };
|
|
96
|
+
//# sourceMappingURL=frame-kit-ui-ng-patterns-docs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-kit-ui-ng-patterns-docs.mjs","sources":["../../../../packages/ui-ng-patterns/docs/method-badge/method-badge.component.ts","../../../../packages/ui-ng-patterns/docs/method-badge/method-badge.component.html","../../../../packages/ui-ng-patterns/docs/endpoint-link/endpoint-link.component.ts","../../../../packages/ui-ng-patterns/docs/endpoint-link/endpoint-link.component.html","../../../../packages/ui-ng-patterns/docs/frame-kit-ui-ng-patterns-docs.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\nimport type { MethodBadgeMethod } from './method-badge.types';\n\n/**\n * Small rectangular badge showing an HTTP method label (e.g. `GET`, `POST`).\n *\n * Each method has its own hue so consumers can identify the verb in a dense\n * list. Background colors are exposed as CSS variables\n * (`--fk-method-badge-bg-get`, etc.) with defaults baked in so the component\n * renders correctly without a theme.\n */\n@Component({\n selector: 'fk-method-badge',\n standalone: true,\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './method-badge.component.html',\n styleUrl: './method-badge.component.scss',\n})\nexport class MethodBadgeComponent {\n // ===== INPUTS =====\n readonly method = input.required<MethodBadgeMethod>();\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-method-badge', `fk-method-badge--${this.method()}`, this.className()]\n .filter(Boolean)\n .join(' '),\n );\n\n readonly label = computed(() => this.method().toUpperCase());\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n}\n","<span\n class=\"fk-method-badge__label\"\n [id]=\"id() ?? undefined\"\n [attr.aria-label]=\"ariaLabel()\"\n >{{ label() }}</span\n>\n","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 { AppShellComponent } from '@frame-kit/ui-ng/layouts/app-shell';\nimport { MethodBadgeComponent } from '../method-badge/method-badge.component';\nimport type { MethodBadgeMethod } from '../method-badge/method-badge.types';\n\n/**\n * Sidebar/list item for an API endpoint in developer documentation.\n *\n * Renders a leading `fk-method-badge` followed by the endpoint label as a\n * single clickable router link. Used to list operations (e.g. \"GET List\n * permissions\", \"POST Create identity\") in a documentation sidebar or any\n * denser endpoint index.\n *\n * Active state is driven by `routerLinkActive`, matching the behavior of\n * `fk-sidenav-link` so every router-aware nav item in the library resolves\n * active state the same way.\n *\n * When rendered inside an `fk-app-shell`, clicks also call `dismissSidenav`\n * on the ancestor shell — this closes the mobile overlay so the user lands\n * directly on the selected endpoint. On desktop it's a no-op.\n */\n@Component({\n selector: 'fk-endpoint-link',\n standalone: true,\n imports: [RouterLink, RouterLinkActive, MethodBadgeComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './endpoint-link.component.html',\n styleUrl: './endpoint-link.component.scss',\n})\nexport class EndpointLinkComponent {\n private readonly shell = inject(AppShellComponent, { optional: true });\n\n // ===== INPUTS =====\n readonly method = input.required<MethodBadgeMethod>();\n readonly label = input.required<string>();\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly routerLink = input.required<string | any[]>();\n\n /**\n * Whether to require an exact URL match for the active state. Defaults to\n * false — each endpoint has a unique final path segment so prefix matching\n * resolves to the same thing in practice.\n */\n readonly exact = input<boolean>(false);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-endpoint-link', this.className()].filter(Boolean).join(' '),\n );\n\n readonly activeOptions = computed(() => ({ exact: this.exact() }));\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n protected dismiss(): void {\n this.shell?.dismissSidenav();\n }\n}\n","<a\n class=\"fk-endpoint-link__anchor\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"fk-endpoint-link__anchor--active\"\n [routerLinkActiveOptions]=\"activeOptions()\"\n (click)=\"$event.stopPropagation(); dismiss()\"\n>\n <fk-method-badge [method]=\"method()\" class=\"fk-endpoint-link__badge\" />\n <span class=\"fk-endpoint-link__label\">{{ label() }}</span>\n</a>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from '@frame-kit/ui-ng-patterns/dashboard';
|
|
2
|
+
export * from '@frame-kit/ui-ng-patterns/docs';
|
|
3
|
+
|
|
4
|
+
// @frame-kit/ui-ng-patterns
|
|
5
|
+
//
|
|
6
|
+
// Opinionated, dashboard-ready components composed from @frame-kit/ui-ng primitives.
|
|
7
|
+
// Most components get their own secondary entry point (packages/ui-ng-patterns/<area>/<name>/).
|
|
8
|
+
// The docs/ and dashboard/ layers are single entry points, like forms/ in ui-ng.
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generated bundle index. Do not edit.
|
|
12
|
+
*/
|
|
13
|
+
//# sourceMappingURL=frame-kit-ui-ng-patterns.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-kit-ui-ng-patterns.mjs","sources":["../../../../packages/ui-ng-patterns/index.ts","../../../../packages/ui-ng-patterns/frame-kit-ui-ng-patterns.ts"],"sourcesContent":["// @frame-kit/ui-ng-patterns\n//\n// Opinionated, dashboard-ready components composed from @frame-kit/ui-ng primitives.\n// Most components get their own secondary entry point (packages/ui-ng-patterns/<area>/<name>/).\n// The docs/ and dashboard/ layers are single entry points, like forms/ in ui-ng.\n\nexport * from '@frame-kit/ui-ng-patterns/dashboard';\nexport * from '@frame-kit/ui-ng-patterns/docs';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;;ACJA;;AAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@frame-kit/ui-ng-patterns",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Opinionated, dashboard-ready Angular component patterns composed from @frame-kit/ui-ng.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/frame-kit/packages.git",
|
|
9
|
+
"directory": "packages/ui-ng-patterns"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/frame-kit/packages/tree/main/packages/ui-ng-patterns#readme",
|
|
12
|
+
"bugs": "https://github.com/frame-kit/packages/issues",
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"@angular/cdk": "^21.2.0",
|
|
15
|
+
"@angular/common": "^21.2.0",
|
|
16
|
+
"@angular/core": "^21.2.0",
|
|
17
|
+
"@angular/forms": "^21.2.0",
|
|
18
|
+
"@angular/platform-browser": "^21.2.0",
|
|
19
|
+
"@angular/router": "^21.2.0",
|
|
20
|
+
"@frame-kit/ui-ng": "^0.0.2",
|
|
21
|
+
"rxjs": "^7.0.0"
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"module": "fesm2022/frame-kit-ui-ng-patterns.mjs",
|
|
28
|
+
"typings": "types/frame-kit-ui-ng-patterns.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
"./package.json": {
|
|
31
|
+
"default": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./types/frame-kit-ui-ng-patterns.d.ts",
|
|
35
|
+
"default": "./fesm2022/frame-kit-ui-ng-patterns.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./dashboard": {
|
|
38
|
+
"types": "./types/frame-kit-ui-ng-patterns-dashboard.d.ts",
|
|
39
|
+
"default": "./fesm2022/frame-kit-ui-ng-patterns-dashboard.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./docs": {
|
|
42
|
+
"types": "./types/frame-kit-ui-ng-patterns-docs.d.ts",
|
|
43
|
+
"default": "./fesm2022/frame-kit-ui-ng-patterns-docs.mjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"type": "module",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"tslib": "^2.3.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { HeadlineLevel } from '@frame-kit/ui-ng/core/headline';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Page-level dashboard header: title + optional description on the left, up to
|
|
6
|
+
* ~3 actions on the right.
|
|
7
|
+
*
|
|
8
|
+
* When the header's own width gets tight, the actions drop below the text and
|
|
9
|
+
* go full-width (mobile-style). The break is driven by a CSS container query on
|
|
10
|
+
* the host, so it responds to the content region's width — not the viewport,
|
|
11
|
+
* which means it behaves correctly inside a narrow panel.
|
|
12
|
+
*
|
|
13
|
+
* Project actions by tagging each button with `pageHeaderActions`:
|
|
14
|
+
*
|
|
15
|
+
* <fk-page-header title="Users" description="...">
|
|
16
|
+
* <fk-button pageHeaderActions variant="outline" size="sm">Import</fk-button>
|
|
17
|
+
* <fk-button pageHeaderActions variant="primary" size="sm">Create user</fk-button>
|
|
18
|
+
* </fk-page-header>
|
|
19
|
+
*
|
|
20
|
+
* Tagging each button (rather than wrapping them in a div) makes every button a
|
|
21
|
+
* direct flex child, so full-width stacking falls out of `align-items: stretch`
|
|
22
|
+
* with no `::ng-deep` reach-in.
|
|
23
|
+
*
|
|
24
|
+
* The title row has two projected slots — `[pageHeaderLeading]` before the
|
|
25
|
+
* title and `[pageHeaderTrailing]` after it. Project a plain `fk-icon` for a
|
|
26
|
+
* decorative mark, or a `button` for a clickable one (the consumer owns the
|
|
27
|
+
* interaction, focus ring, and aria-label):
|
|
28
|
+
*
|
|
29
|
+
* <fk-page-header title="Permissions">
|
|
30
|
+
* <fk-icon pageHeaderLeading name="key-outline" />
|
|
31
|
+
* <button pageHeaderTrailing type="button" aria-label="Docs" (click)="openDocs()">
|
|
32
|
+
* <fk-icon name="view-api" />
|
|
33
|
+
* </button>
|
|
34
|
+
* </fk-page-header>
|
|
35
|
+
*/
|
|
36
|
+
declare class PageHeaderComponent {
|
|
37
|
+
readonly title: _angular_core.InputSignal<string>;
|
|
38
|
+
readonly description: _angular_core.InputSignal<string | null>;
|
|
39
|
+
readonly headingLevel: _angular_core.InputSignal<HeadlineLevel>;
|
|
40
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<PageHeaderComponent, never>;
|
|
41
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<PageHeaderComponent, "fk-page-header", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; }, {}, never, ["[pageHeaderLeading]", "[pageHeaderTrailing]", "[pageHeaderActions]"], true, never>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Empty-state panel for dashboard tables and lists: a centered icon, a title,
|
|
46
|
+
* an optional description, and a projected actions slot (typically one primary
|
|
47
|
+
* fk-button). Bordered-card styling, meant to stand in for a table body when
|
|
48
|
+
* there are no rows.
|
|
49
|
+
*
|
|
50
|
+
* The action row flips from a horizontal row to a centered vertical stack on
|
|
51
|
+
* narrow widths, driven by a container query on the host — so it reacts to the
|
|
52
|
+
* empty-state's own width (e.g. inside a split column) rather than the viewport.
|
|
53
|
+
*
|
|
54
|
+
* <fk-table-empty-state
|
|
55
|
+
* icon="globe"
|
|
56
|
+
* title="No webhooks configured"
|
|
57
|
+
* description="Add a webhook to receive events."
|
|
58
|
+
* >
|
|
59
|
+
* <fk-button variant="primary" size="sm">Add webhook</fk-button>
|
|
60
|
+
* </fk-table-empty-state>
|
|
61
|
+
*
|
|
62
|
+
* The actions slot is an open `ng-content`, so project as many actions as the
|
|
63
|
+
* design needs (one primary is the norm; two reads cleanly).
|
|
64
|
+
*/
|
|
65
|
+
declare class TableEmptyStateComponent {
|
|
66
|
+
readonly icon: _angular_core.InputSignal<string | null>;
|
|
67
|
+
readonly title: _angular_core.InputSignal<string>;
|
|
68
|
+
readonly description: _angular_core.InputSignal<string | null>;
|
|
69
|
+
readonly className: _angular_core.InputSignal<string>;
|
|
70
|
+
readonly id: _angular_core.InputSignal<string | null>;
|
|
71
|
+
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
72
|
+
readonly classes: _angular_core.Signal<string>;
|
|
73
|
+
get hostClass(): string;
|
|
74
|
+
get hostId(): string | null;
|
|
75
|
+
get hostAriaLabel(): string | null;
|
|
76
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TableEmptyStateComponent, never>;
|
|
77
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TableEmptyStateComponent, "fk-table-empty-state", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Two-column split layout with container-query responsive stacking.
|
|
82
|
+
*
|
|
83
|
+
* Projects content into `[start]` (grows to fill) and `[end]` (content-sized or
|
|
84
|
+
* fixed width). Below 700px container width, columns stack vertically.
|
|
85
|
+
*
|
|
86
|
+
* A standalone layout primitive for custom dashboard rows.
|
|
87
|
+
*/
|
|
88
|
+
declare class ContentSplitLayoutComponent {
|
|
89
|
+
readonly endWidth: _angular_core.InputSignal<string | null>;
|
|
90
|
+
readonly gap: _angular_core.InputSignal<string>;
|
|
91
|
+
readonly hostClass = "fk-content-split-layout";
|
|
92
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentSplitLayoutComponent, never>;
|
|
93
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentSplitLayoutComponent, "fk-content-split-layout", never, { "endWidth": { "alias": "endWidth"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; }, {}, never, ["[start]", "[end]"], true, never>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Responsive column layout for dashboard content. Lays projected children out
|
|
98
|
+
* in equal-width columns that wrap — and ultimately collapse to a single
|
|
99
|
+
* stacked column — based on the layout's *own* available width, never the
|
|
100
|
+
* viewport. So it reacts correctly when the dashboard content region is
|
|
101
|
+
* squeezed (sidenav open, a drawer pushed in, a split pane) while the viewport
|
|
102
|
+
* still reports "desktop".
|
|
103
|
+
*
|
|
104
|
+
* Implemented with an intrinsic CSS grid — `repeat(auto-fit, minmax(min, 1fr))`
|
|
105
|
+
* — so there is no container query, no JS, and no viewport breakpoint to keep
|
|
106
|
+
* in sync. `minColumnWidth` is the smallest a column may shrink to before the
|
|
107
|
+
* grid drops a column; empty tracks collapse, so N children never produce more
|
|
108
|
+
* than N columns. Spacing on both axes is a single `gutter` gap, so wrapped
|
|
109
|
+
* rows and side-by-side columns separate consistently with no edge artifacts.
|
|
110
|
+
*
|
|
111
|
+
* <fk-columns minColumnWidth="18rem">
|
|
112
|
+
* <fk-field-group>…</fk-field-group>
|
|
113
|
+
* <fk-field-group>…</fk-field-group>
|
|
114
|
+
* </fk-columns>
|
|
115
|
+
*/
|
|
116
|
+
declare class ColumnsComponent {
|
|
117
|
+
/**
|
|
118
|
+
* The smallest width a column may shrink to before the grid drops to fewer
|
|
119
|
+
* columns. This is the single knob that controls when columns wrap/stack.
|
|
120
|
+
*/
|
|
121
|
+
readonly minColumnWidth: _angular_core.InputSignal<string>;
|
|
122
|
+
/** Gap between side-by-side columns and between wrapped rows. */
|
|
123
|
+
readonly gutter: _angular_core.InputSignal<string>;
|
|
124
|
+
readonly className: _angular_core.InputSignal<string>;
|
|
125
|
+
readonly id: _angular_core.InputSignal<string | null>;
|
|
126
|
+
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
127
|
+
readonly classes: _angular_core.Signal<string>;
|
|
128
|
+
get hostClass(): string;
|
|
129
|
+
get hostId(): string | null;
|
|
130
|
+
get hostAriaLabel(): string | null;
|
|
131
|
+
get hostMin(): string;
|
|
132
|
+
get hostGutter(): string;
|
|
133
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ColumnsComponent, never>;
|
|
134
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ColumnsComponent, "fk-columns", never, { "minColumnWidth": { "alias": "minColumnWidth"; "required": false; "isSignal": true; }; "gutter": { "alias": "gutter"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Padded container for an expanded dashboard detail region — typically the body
|
|
139
|
+
* of a data-table row expansion, holding an `fk-columns` of form controls.
|
|
140
|
+
*
|
|
141
|
+
* The `disabled` input greys the panel and makes its entire subtree `inert`
|
|
142
|
+
* (non-interactive, unfocusable, hidden from assistive technology) — for
|
|
143
|
+
* controls gated behind a feature toggle that must not be editable until the
|
|
144
|
+
* feature is enabled.
|
|
145
|
+
*
|
|
146
|
+
* <fk-detail-panel [disabled]="!feature.enabled">
|
|
147
|
+
* <fk-columns>…</fk-columns>
|
|
148
|
+
* </fk-detail-panel>
|
|
149
|
+
*/
|
|
150
|
+
declare class DetailPanelComponent {
|
|
151
|
+
/**
|
|
152
|
+
* Greys the panel and makes its content inert (locked) — for feature-gated
|
|
153
|
+
* controls that should not be editable until the feature is enabled.
|
|
154
|
+
*/
|
|
155
|
+
readonly disabled: _angular_core.InputSignal<boolean>;
|
|
156
|
+
readonly className: _angular_core.InputSignal<string>;
|
|
157
|
+
readonly id: _angular_core.InputSignal<string | null>;
|
|
158
|
+
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
159
|
+
readonly classes: _angular_core.Signal<string>;
|
|
160
|
+
get hostClass(): string;
|
|
161
|
+
get hostId(): string | null;
|
|
162
|
+
get hostAriaLabel(): string | null;
|
|
163
|
+
/**
|
|
164
|
+
* Lock the whole subtree when disabled. `inert` blocks pointer interaction,
|
|
165
|
+
* focus, and assistive-tech access in one attribute — stronger and more
|
|
166
|
+
* correct than `pointer-events: none`, which only blocks the mouse.
|
|
167
|
+
*/
|
|
168
|
+
get hostInert(): '' | null;
|
|
169
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DetailPanelComponent, never>;
|
|
170
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DetailPanelComponent, "fk-detail-panel", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { ColumnsComponent, ContentSplitLayoutComponent, DetailPanelComponent, PageHeaderComponent, TableEmptyStateComponent };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Supported HTTP methods for `fk-method-badge`. Lowercased to match OpenAPI
|
|
5
|
+
* conventions so consumers can pass spec values directly without translation.
|
|
6
|
+
*/
|
|
7
|
+
type MethodBadgeMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sidebar/list item for an API endpoint in developer documentation.
|
|
11
|
+
*
|
|
12
|
+
* Renders a leading `fk-method-badge` followed by the endpoint label as a
|
|
13
|
+
* single clickable router link. Used to list operations (e.g. "GET List
|
|
14
|
+
* permissions", "POST Create identity") in a documentation sidebar or any
|
|
15
|
+
* denser endpoint index.
|
|
16
|
+
*
|
|
17
|
+
* Active state is driven by `routerLinkActive`, matching the behavior of
|
|
18
|
+
* `fk-sidenav-link` so every router-aware nav item in the library resolves
|
|
19
|
+
* active state the same way.
|
|
20
|
+
*
|
|
21
|
+
* When rendered inside an `fk-app-shell`, clicks also call `dismissSidenav`
|
|
22
|
+
* on the ancestor shell — this closes the mobile overlay so the user lands
|
|
23
|
+
* directly on the selected endpoint. On desktop it's a no-op.
|
|
24
|
+
*/
|
|
25
|
+
declare class EndpointLinkComponent {
|
|
26
|
+
private readonly shell;
|
|
27
|
+
readonly method: _angular_core.InputSignal<MethodBadgeMethod>;
|
|
28
|
+
readonly label: _angular_core.InputSignal<string>;
|
|
29
|
+
readonly routerLink: _angular_core.InputSignal<string | any[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to require an exact URL match for the active state. Defaults to
|
|
32
|
+
* false — each endpoint has a unique final path segment so prefix matching
|
|
33
|
+
* resolves to the same thing in practice.
|
|
34
|
+
*/
|
|
35
|
+
readonly exact: _angular_core.InputSignal<boolean>;
|
|
36
|
+
readonly className: _angular_core.InputSignal<string>;
|
|
37
|
+
readonly classes: _angular_core.Signal<string>;
|
|
38
|
+
readonly activeOptions: _angular_core.Signal<{
|
|
39
|
+
exact: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
get hostClass(): string;
|
|
42
|
+
protected dismiss(): void;
|
|
43
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EndpointLinkComponent, never>;
|
|
44
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EndpointLinkComponent, "fk-endpoint-link", never, { "method": { "alias": "method"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "routerLink": { "alias": "routerLink"; "required": true; "isSignal": true; }; "exact": { "alias": "exact"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Small rectangular badge showing an HTTP method label (e.g. `GET`, `POST`).
|
|
49
|
+
*
|
|
50
|
+
* Each method has its own hue so consumers can identify the verb in a dense
|
|
51
|
+
* list. Background colors are exposed as CSS variables
|
|
52
|
+
* (`--fk-method-badge-bg-get`, etc.) with defaults baked in so the component
|
|
53
|
+
* renders correctly without a theme.
|
|
54
|
+
*/
|
|
55
|
+
declare class MethodBadgeComponent {
|
|
56
|
+
readonly method: _angular_core.InputSignal<MethodBadgeMethod>;
|
|
57
|
+
readonly className: _angular_core.InputSignal<string>;
|
|
58
|
+
readonly id: _angular_core.InputSignal<string | null>;
|
|
59
|
+
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
60
|
+
readonly classes: _angular_core.Signal<string>;
|
|
61
|
+
readonly label: _angular_core.Signal<string>;
|
|
62
|
+
get hostClass(): string;
|
|
63
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MethodBadgeComponent, never>;
|
|
64
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MethodBadgeComponent, "fk-method-badge", never, { "method": { "alias": "method"; "required": true; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { EndpointLinkComponent, MethodBadgeComponent };
|
|
68
|
+
export type { MethodBadgeMethod as EndpointLinkMethod, MethodBadgeMethod };
|