@iyulab/modern-app 0.3.3 → 0.3.4
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/CHANGELOG.md +11 -3
- package/README.md +73 -123
- package/dist/layouts/SidebarLayout.d.ts +2 -0
- package/dist/layouts/SidebarLayout.js +10 -4
- package/dist/layouts/SidebarLayout.styles.js +14 -3
- package/package.json +3 -2
- package/skills/modern-app/SKILL.md +219 -0
- package/skills/modern-app/references/api.md +179 -0
- package/skills/modern-app/references/layout.md +204 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4 (2026-04-01)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- Progress bar now fades in/out with CSS transition on route begin/done
|
|
7
|
+
- Progress bar shows error state (red) on `route-error` and auto-dismisses
|
|
8
|
+
|
|
9
|
+
### Documentation
|
|
10
|
+
- Added `docs/` topic guides and `skills/modern-app/` agent skill package
|
|
11
|
+
- Rewrote `README.md` with accurate API and links to docs/skills
|
|
12
|
+
|
|
3
13
|
## 0.3.3 (2026-04-01)
|
|
4
14
|
|
|
5
15
|
### Fixes
|
|
6
|
-
-
|
|
7
|
-
- `SidebarButton`, `SidebarGroup`, `SidebarLink`, `SidebarSection` 4개 컴포넌트
|
|
8
|
-
- `SidebarLayout` 템플릿에서 `u-sidebar-*`로 참조하지만 등록은 `sidebar-*`로 되어 있어 렌더링 안 되던 버그 수정
|
|
16
|
+
- Fixed missing `u-` prefix in `customElement` registration for `SidebarButton`, `SidebarGroup`, `SidebarLink`, and `SidebarSection` — components were referenced as `u-sidebar-*` in the template but registered as `sidebar-*`, causing them not to render
|
|
9
17
|
|
|
10
18
|
## 0.3.2 (2026-04-01)
|
|
11
19
|
|
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @iyulab/modern-app
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
For complete examples and documentation, visit our demo site: [https://modern-app.iyulab.com](https://modern-app.iyulab.com)
|
|
3
|
+
A client-side SPA framework built on [Lit Element](https://lit.dev/) by iyulab. It bundles routing, a responsive sidebar layout, theme management, toast notifications, and i18n into a single `app` singleton.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
@@ -10,167 +8,119 @@ For complete examples and documentation, visit our demo site: [https://modern-ap
|
|
|
10
8
|
npm install @iyulab/modern-app
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
`@iyulab/modern-app` is a **client-side SPA framework** built on Lit Element. It is designed for:
|
|
16
|
-
|
|
17
|
-
✅ **Suitable for:**
|
|
18
|
-
- Single Page Applications (SPA)
|
|
19
|
-
- Admin dashboards and internal tools
|
|
20
|
-
- Progressive Web Apps (PWA)
|
|
21
|
-
- Projects where SEO is not a primary concern
|
|
22
|
-
|
|
23
|
-
❌ **Not suitable for:**
|
|
24
|
-
- Server-Side Rendering (SSR) frameworks (Next.js, Nuxt, SvelteKit, etc.)
|
|
25
|
-
- Static Site Generation (SSG)
|
|
26
|
-
- SEO-critical public-facing pages
|
|
27
|
-
- Projects requiring initial HTML content for search engines
|
|
11
|
+
## When to use it
|
|
28
12
|
|
|
29
|
-
|
|
13
|
+
| ✅ Good fit | ❌ Not a good fit |
|
|
14
|
+
|-------------|-----------------|
|
|
15
|
+
| Single Page Applications (SPA) | SSR frameworks (Next.js, Nuxt, SvelteKit) |
|
|
16
|
+
| Admin dashboards and internal tools | Static Site Generation (SSG) |
|
|
17
|
+
| Progressive Web Apps (PWA) | SEO-critical public-facing pages |
|
|
30
18
|
|
|
31
19
|
## Quick Start
|
|
32
20
|
|
|
33
21
|
```typescript
|
|
34
22
|
import { app } from '@iyulab/modern-app';
|
|
23
|
+
import { html } from 'lit';
|
|
35
24
|
|
|
36
25
|
await app.load({
|
|
37
26
|
basepath: '/',
|
|
38
27
|
layout: {
|
|
39
28
|
type: 'sidebar',
|
|
40
|
-
|
|
29
|
+
logo: '/assets/logo.svg',
|
|
30
|
+
title: 'My App',
|
|
31
|
+
main: [
|
|
32
|
+
{ type: 'link', icon: 'home', label: 'Home', href: '/' },
|
|
33
|
+
{ type: 'link', icon: 'users', label: 'Users', href: '/users' },
|
|
34
|
+
],
|
|
41
35
|
},
|
|
42
36
|
routes: [
|
|
43
|
-
{ index: true,
|
|
44
|
-
{ path: '
|
|
37
|
+
{ index: true, render: () => html`<home-page></home-page>` },
|
|
38
|
+
{ path: 'users', render: () => html`<users-page></users-page>` },
|
|
39
|
+
{ path: 'users/:id', render: (ctx) => html`<user-detail .userId=${ctx.params.id}></user-detail>` },
|
|
45
40
|
],
|
|
41
|
+
fallback: {
|
|
42
|
+
render: (ctx) => html`<error-page .error=${ctx.error}></error-page>`,
|
|
43
|
+
},
|
|
44
|
+
theme: { default: 'system' },
|
|
46
45
|
});
|
|
47
46
|
```
|
|
48
47
|
|
|
49
|
-
##
|
|
48
|
+
## Skills Usage
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
AI agent skills for this package are located in `skills/modern-app/`. Install them with `npx skills`:
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
app
|
|
56
|
-
|
|
57
|
-
// Access router instance
|
|
58
|
-
app.router?.go('/path');
|
|
59
|
-
app.router?.basepath; // Get base path
|
|
60
|
-
app.router?.routes; // Get registered routes
|
|
61
|
-
app.router?.context; // Get current route context
|
|
52
|
+
**From GitHub:**
|
|
53
|
+
```bash
|
|
54
|
+
npx skills add iyulab/node-modern-app
|
|
62
55
|
```
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Get current theme
|
|
68
|
-
app.theme.get(); // Returns: 'system' | 'light' | 'dark' | undefined
|
|
69
|
-
|
|
70
|
-
// Set theme
|
|
71
|
-
app.theme.set('dark'); // 'system' | 'light' | 'dark'
|
|
72
|
-
|
|
73
|
-
// Check initialization status
|
|
74
|
-
app.theme.isInitialized;
|
|
57
|
+
**From local `node_modules`:**
|
|
58
|
+
```bash
|
|
59
|
+
npx skills add ./node_modules/@iyulab/modern-app
|
|
75
60
|
```
|
|
76
61
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
// Display notifications (returns Promise<void>)
|
|
81
|
-
await app.notice('Notice message');
|
|
82
|
-
await app.info('Info message');
|
|
83
|
-
await app.success('Success message');
|
|
84
|
-
await app.warning('Warning message');
|
|
85
|
-
await app.error('Error message');
|
|
86
|
-
|
|
87
|
-
// With options
|
|
88
|
-
await app.success('Saved!', {
|
|
89
|
-
title: 'Success',
|
|
90
|
-
duration: 5000, // milliseconds (default: 3000)
|
|
91
|
-
position: 'top-right' // 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
|
|
92
|
-
});
|
|
93
|
-
```
|
|
62
|
+
## Core API
|
|
94
63
|
|
|
95
|
-
###
|
|
64
|
+
### Navigation
|
|
96
65
|
|
|
97
66
|
```typescript
|
|
98
|
-
//
|
|
99
|
-
app.
|
|
100
|
-
|
|
101
|
-
// Usage with lit-i18n
|
|
102
|
-
import { translate } from 'lit-i18n';
|
|
103
|
-
html`<p>${translate('namespace::key')}</p>`;
|
|
67
|
+
app.navigate('/users/42'); // push a route
|
|
68
|
+
app.router?.go('/users/42'); // via router instance
|
|
69
|
+
app.router?.context; // current RouteContext
|
|
104
70
|
```
|
|
105
71
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
### AppConfig
|
|
72
|
+
### Theme
|
|
109
73
|
|
|
110
74
|
```typescript
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
routes: RouteConfig[]; // Route definitions
|
|
115
|
-
fallback?: FallbackConfig; // Error fallback route
|
|
116
|
-
theme?: ThemeInitOptions; // Theme configuration
|
|
117
|
-
localization?: i18next.InitOptions; // i18next options
|
|
118
|
-
layout: LayoutConfig; // Layout configuration
|
|
119
|
-
}
|
|
75
|
+
app.theme.get(); // 'system' | 'light' | 'dark' | undefined
|
|
76
|
+
app.theme.set('dark');
|
|
77
|
+
app.theme.isInitialized; // boolean
|
|
120
78
|
```
|
|
121
79
|
|
|
122
|
-
###
|
|
80
|
+
### Notifications
|
|
123
81
|
|
|
124
82
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
prefix?: string;
|
|
131
|
-
};
|
|
132
|
-
useBuiltIn?: boolean; // Use built-in styles (default: true)
|
|
133
|
-
}
|
|
83
|
+
await app.success('Saved!', { title: 'Done', duration: 4000, position: 'top-right' });
|
|
84
|
+
await app.error('Something went wrong');
|
|
85
|
+
await app.info('Info message');
|
|
86
|
+
await app.warning('Double-check this');
|
|
87
|
+
await app.notice('Neutral notice');
|
|
134
88
|
```
|
|
135
89
|
|
|
136
|
-
###
|
|
90
|
+
### Localization (i18next)
|
|
137
91
|
|
|
138
92
|
```typescript
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
};
|
|
149
|
-
menu?: MenuItem[]; // Navigation menu items
|
|
150
|
-
footer?: FooterItem[]; // Footer buttons/items
|
|
151
|
-
}
|
|
152
|
-
```
|
|
93
|
+
// Pass i18next plugins and InitOptions
|
|
94
|
+
await app.load({
|
|
95
|
+
// ...
|
|
96
|
+
i18n: {
|
|
97
|
+
plugins: [i18nextHttpBackend],
|
|
98
|
+
lng: 'en',
|
|
99
|
+
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
153
102
|
|
|
154
|
-
|
|
103
|
+
// Access i18next
|
|
104
|
+
app.i18n.t('common::greeting');
|
|
105
|
+
app.i18n.changeLanguage('ko');
|
|
155
106
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
path?: string; // Route path (supports :param patterns)
|
|
160
|
-
title?: string; // Document title
|
|
161
|
-
force?: boolean; // Force re-render
|
|
162
|
-
render: (context: RouteContext) => RenderResult | Promise<RenderResult>;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
interface RouteContext {
|
|
166
|
-
href: string; // Full URL
|
|
167
|
-
pathname: string; // Path portion
|
|
168
|
-
basepath: string; // Base path
|
|
169
|
-
params: Record<string, string>; // URL parameters
|
|
170
|
-
progress: (value: number) => void; // Progress callback (0-100)
|
|
171
|
-
}
|
|
107
|
+
// Reactive translations in Lit templates
|
|
108
|
+
import { translate } from 'lit-i18n';
|
|
109
|
+
html`<p>${translate('common::greeting')}</p>`;
|
|
172
110
|
```
|
|
173
111
|
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
| Guide | Description |
|
|
115
|
+
|-------|-------------|
|
|
116
|
+
| [getting-started.md](./docs/getting-started.md) | Bootstrap, architecture, entry point setup |
|
|
117
|
+
| [routing.md](./docs/routing.md) | Route config, URL params, async routes, progress |
|
|
118
|
+
| [layout.md](./docs/layout.md) | Sidebar layout, all menu item types, responsive behaviour |
|
|
119
|
+
| [theme.md](./docs/theme.md) | Theme init, runtime switching, CSS tokens |
|
|
120
|
+
| [notifications.md](./docs/notifications.md) | Toast methods and options |
|
|
121
|
+
| [i18n.md](./docs/i18n.md) | i18next setup, plugins, lit-i18n usage |
|
|
122
|
+
| [configuration.md](./docs/configuration.md) | Full TypeScript interface reference |
|
|
123
|
+
|
|
174
124
|
## License
|
|
175
125
|
|
|
176
126
|
MIT
|
|
@@ -49,6 +49,8 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
49
49
|
private handleRouteProgress;
|
|
50
50
|
/** 라우트 변경 완료 핸들러 */
|
|
51
51
|
private handleRouteDone;
|
|
52
|
+
/** 라우트 에러 핸들러 */
|
|
53
|
+
private handleRouteError;
|
|
52
54
|
/** 화면 크기 변경에 따른 사이드바 상태 업데이트 */
|
|
53
55
|
private handleScreenResize;
|
|
54
56
|
}
|
|
@@ -25,11 +25,17 @@ var m, h = class extends r {
|
|
|
25
25
|
}, this.handleBackdropClick = () => {
|
|
26
26
|
this.state = "slim";
|
|
27
27
|
}, this.handleRouteBegin = (e) => {
|
|
28
|
-
this.progressBarEl.value = 0, this.state === "modal" && (this.state = "slim"), this.state === "mobile-open" && (this.state = "mobile"), this.context = e.context;
|
|
28
|
+
this.progressBarEl.setAttribute("visible", ""), this.progressBarEl.value = 0, this.state === "modal" && (this.state = "slim"), this.state === "mobile-open" && (this.state = "mobile"), this.context = e.context;
|
|
29
29
|
}, this.handleRouteProgress = (e) => {
|
|
30
30
|
this.progressBarEl.value = e.progress;
|
|
31
31
|
}, this.handleRouteDone = (e) => {
|
|
32
|
-
this.progressBarEl.value = 100
|
|
32
|
+
this.progressBarEl.value = 100, setTimeout(() => {
|
|
33
|
+
this.progressBarEl.removeAttribute("visible");
|
|
34
|
+
}, 300);
|
|
35
|
+
}, this.handleRouteError = (e) => {
|
|
36
|
+
this.progressBarEl.setAttribute("error", ""), this.progressBarEl.value = 100, setTimeout(() => {
|
|
37
|
+
this.progressBarEl.removeAttribute("visible"), this.progressBarEl.removeAttribute("error");
|
|
38
|
+
}, 300);
|
|
33
39
|
}, this.handleScreenResize = (e) => {
|
|
34
40
|
let t = e.detail.size;
|
|
35
41
|
t === "large" ? this.state = "default" : t === "medium" ? this.state = "slim" : t === "small" ? this.state = "mobile" : console.warn("Unknown screen size:", t);
|
|
@@ -39,10 +45,10 @@ var m, h = class extends r {
|
|
|
39
45
|
this.styles = [super.styles, i];
|
|
40
46
|
}
|
|
41
47
|
connectedCallback() {
|
|
42
|
-
super.connectedCallback(), window.addEventListener("route-begin", this.handleRouteBegin), window.addEventListener("route-done", this.handleRouteDone), window.addEventListener("route-progress", this.handleRouteProgress), window.addEventListener("screen-resize", this.handleScreenResize);
|
|
48
|
+
super.connectedCallback(), window.addEventListener("route-begin", this.handleRouteBegin), window.addEventListener("route-done", this.handleRouteDone), window.addEventListener("route-progress", this.handleRouteProgress), window.addEventListener("route-error", this.handleRouteError), window.addEventListener("screen-resize", this.handleScreenResize);
|
|
43
49
|
}
|
|
44
50
|
disconnectedCallback() {
|
|
45
|
-
window.removeEventListener("route-begin", this.handleRouteBegin), window.removeEventListener("route-done", this.handleRouteDone), window.removeEventListener("route-progress", this.handleRouteProgress), window.removeEventListener("screen-resize", this.handleScreenResize), super.disconnectedCallback();
|
|
51
|
+
window.removeEventListener("route-begin", this.handleRouteBegin), window.removeEventListener("route-done", this.handleRouteDone), window.removeEventListener("route-progress", this.handleRouteProgress), window.removeEventListener("route-error", this.handleRouteError), window.removeEventListener("screen-resize", this.handleScreenResize), super.disconnectedCallback();
|
|
46
52
|
}
|
|
47
53
|
willUpdate(e) {
|
|
48
54
|
super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
|
|
@@ -151,14 +151,25 @@ var t = e`
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
.main u-progress-bar {
|
|
154
|
+
--progress-bar-height: 4px;
|
|
155
|
+
--progres-bar-track-color: transparent;
|
|
156
|
+
|
|
154
157
|
position: absolute;
|
|
155
158
|
z-index: 100;
|
|
156
159
|
top: 0;
|
|
157
160
|
left: 0;
|
|
158
161
|
right: 0;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
opacity: 0;
|
|
163
|
+
transform: translateY(-4px);
|
|
164
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
}
|
|
167
|
+
.main u-progress-bar[visible] {
|
|
168
|
+
opacity: 1;
|
|
169
|
+
transform: translateY(0);
|
|
170
|
+
}
|
|
171
|
+
.main u-progress-bar[error] {
|
|
172
|
+
--progress-bar-color: var(--u-red-500);
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
/* Backdrop for modal mode */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/modern-app",
|
|
3
3
|
"description": "web-framework by iyulab based on lit-element",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
+
"skills",
|
|
18
19
|
"package.json",
|
|
19
20
|
"README.md",
|
|
20
21
|
"CHANGELOG.md",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"build": "vite build"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"@iyulab/components": "^1.0.
|
|
41
|
+
"@iyulab/components": "^1.0.1",
|
|
41
42
|
"@iyulab/router": "^0.7.4",
|
|
42
43
|
"i18next": "^25.10.10",
|
|
43
44
|
"lit": "^3.3.2"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: modern-app
|
|
3
|
+
description: Client-side SPA framework built on Lit Element. Bootstraps an application with sidebar layout, client-side routing, theme management, toast notifications, and i18n (i18next). Use when working with the @iyulab/modern-app package — setting up a new app, configuring routes, adding navigation menu items, managing theme, showing toasts, or wiring up localization.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: iyulab
|
|
7
|
+
version: "0.3.4"
|
|
8
|
+
compatibility: Designed for Lit Element / TypeScript projects. Requires @iyulab/modern-app.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# @iyulab/modern-app
|
|
12
|
+
|
|
13
|
+
A client-side SPA framework built on Lit Element. Provides a single `app` singleton that wires together routing, layout, theme, notifications, and i18n.
|
|
14
|
+
|
|
15
|
+
> **Suitable for:** SPAs, admin dashboards, internal tools, PWAs.
|
|
16
|
+
> **Not suitable for:** SSR (Next.js, Nuxt, SvelteKit), SSG, SEO-critical pages.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @iyulab/modern-app
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Bootstrap
|
|
27
|
+
|
|
28
|
+
Call `app.load()` once at the entry point. All options are in [`AppConfig`](./references/api.md#appconfig).
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { app } from '@iyulab/modern-app';
|
|
32
|
+
import { html } from 'lit';
|
|
33
|
+
|
|
34
|
+
await app.load({
|
|
35
|
+
basepath: '/',
|
|
36
|
+
layout: {
|
|
37
|
+
type: 'sidebar',
|
|
38
|
+
logo: '/assets/logo.svg',
|
|
39
|
+
title: 'My App',
|
|
40
|
+
main: [
|
|
41
|
+
{ type: 'link', icon: 'home', label: 'Home', href: '/' },
|
|
42
|
+
{ type: 'link', icon: 'users', label: 'Users', href: '/users' },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
routes: [
|
|
46
|
+
{ index: true, render: () => html`<home-page></home-page>` },
|
|
47
|
+
{ path: 'users', render: () => html`<users-page></users-page>` },
|
|
48
|
+
{ path: 'users/:id', render: (ctx) => html`<user-detail .userId=${ctx.params.id}></user-detail>` },
|
|
49
|
+
],
|
|
50
|
+
fallback: {
|
|
51
|
+
render: (ctx) => html`<error-page .error=${ctx.error}></error-page>`,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To tear down the app:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
app.unload();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Navigation
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
app.navigate('/users/42'); // push route
|
|
68
|
+
app.router?.go('/users/42'); // same via router instance
|
|
69
|
+
app.router?.basepath; // base path string
|
|
70
|
+
app.router?.context; // current RouteContext
|
|
71
|
+
app.router?.routes; // registered routes
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Theme
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
app.theme.get(); // 'system' | 'light' | 'dark' | undefined
|
|
80
|
+
app.theme.set('dark'); // 'system' | 'light' | 'dark'
|
|
81
|
+
app.theme.isInitialized; // boolean
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Theme `store` options persist the preference across sessions:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
theme: {
|
|
88
|
+
default: 'system',
|
|
89
|
+
store: { type: 'localStorage', prefix: 'myapp' },
|
|
90
|
+
useBuiltIn: true,
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Notifications (Toast)
|
|
97
|
+
|
|
98
|
+
All methods return `Promise<void>`.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
await app.notice('A general notice');
|
|
102
|
+
await app.info('Loaded successfully');
|
|
103
|
+
await app.success('Saved!', { title: 'Done', duration: 4000, position: 'top-right' });
|
|
104
|
+
await app.warning('Check your input');
|
|
105
|
+
await app.error('Something went wrong');
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`position` values: `'top-right'` | `'top-left'` | `'bottom-right'` | `'bottom-left'`
|
|
109
|
+
`duration` default: `3000` ms
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Localization (i18next)
|
|
114
|
+
|
|
115
|
+
Pass standard i18next `InitOptions` plus an optional `plugins` array.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import i18nextHttpBackend from 'i18next-http-backend';
|
|
119
|
+
|
|
120
|
+
await app.load({
|
|
121
|
+
// ...
|
|
122
|
+
i18n: {
|
|
123
|
+
plugins: [i18nextHttpBackend],
|
|
124
|
+
lng: 'en',
|
|
125
|
+
fallbackLng: 'en',
|
|
126
|
+
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Access i18next instance
|
|
131
|
+
app.i18n.t('namespace::key');
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Use in Lit templates with `lit-i18n`:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { translate } from 'lit-i18n';
|
|
138
|
+
html`<p>${translate('namespace::greeting')}</p>`;
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Sidebar Layout
|
|
144
|
+
|
|
145
|
+
Full configuration reference: [references/layout.md](./references/layout.md)
|
|
146
|
+
|
|
147
|
+
### Menu item types
|
|
148
|
+
|
|
149
|
+
| type | Description |
|
|
150
|
+
|------|-------------|
|
|
151
|
+
| `'link'` | Single navigation link with optional icon |
|
|
152
|
+
| `'group'` | Collapsible group of links |
|
|
153
|
+
| `'section'` | Labelled section grouping links and groups |
|
|
154
|
+
| `'button'` | Action button (non-navigation) |
|
|
155
|
+
| `'html'` | Custom Lit template rendered inline |
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
layout: {
|
|
159
|
+
type: 'sidebar',
|
|
160
|
+
logo: '/logo.svg',
|
|
161
|
+
title: 'App Name',
|
|
162
|
+
main: [
|
|
163
|
+
{
|
|
164
|
+
type: 'section',
|
|
165
|
+
title: 'Management',
|
|
166
|
+
items: [
|
|
167
|
+
{ type: 'link', icon: 'users', label: 'Users', href: '/users' },
|
|
168
|
+
{
|
|
169
|
+
type: 'group', icon: 'settings', label: 'Settings',
|
|
170
|
+
items: [
|
|
171
|
+
{ type: 'link', label: 'Profile', href: '/settings/profile' },
|
|
172
|
+
{ type: 'link', label: 'Security', href: '/settings/security' },
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
footer: [
|
|
179
|
+
{ type: 'button', icon: 'logout', label: 'Logout', onClick: () => signOut() },
|
|
180
|
+
],
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Responsive breakpoints
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
layout: {
|
|
188
|
+
type: 'sidebar',
|
|
189
|
+
breakpoints: [768, 1024], // [tablet-min-px, desktop-min-px]
|
|
190
|
+
// ...
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Sidebar states: `'default'` | `'slim'` | `'modal'` | `'mobile'` | `'mobile-open'`
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Routes with progress
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
{
|
|
202
|
+
path: 'dashboard',
|
|
203
|
+
title: 'Dashboard',
|
|
204
|
+
render: async (ctx) => {
|
|
205
|
+
ctx.progress(30);
|
|
206
|
+
const data = await fetchData();
|
|
207
|
+
ctx.progress(100);
|
|
208
|
+
return html`<dashboard-page .data=${data}></dashboard-page>`;
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`RouteContext` fields: `href`, `pathname`, `basepath`, `params`, `progress`
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Full `AppConfig` reference
|
|
218
|
+
|
|
219
|
+
See [references/api.md](./references/api.md) for all TypeScript interfaces.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# API Reference — @iyulab/modern-app
|
|
2
|
+
|
|
3
|
+
## `AppConfig`
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
interface AppConfig {
|
|
7
|
+
/** Root element to render into. Default: document.body */
|
|
8
|
+
root?: Element;
|
|
9
|
+
|
|
10
|
+
/** Base path for all routes. Default: '/' */
|
|
11
|
+
basepath?: string;
|
|
12
|
+
|
|
13
|
+
/** Base URL for icon assets. Default: '/assets/icons/' */
|
|
14
|
+
iconBasepath?: string;
|
|
15
|
+
|
|
16
|
+
/** Route definitions. */
|
|
17
|
+
routes?: RouteConfig[];
|
|
18
|
+
|
|
19
|
+
/** Fallback rendered on 404 or unhandled errors. */
|
|
20
|
+
fallback?: FallbackRouteConfig;
|
|
21
|
+
|
|
22
|
+
/** Layout configuration. Currently only 'sidebar' is supported. */
|
|
23
|
+
layout: LayoutConfig;
|
|
24
|
+
|
|
25
|
+
/** Theme initialization options. */
|
|
26
|
+
theme?: ThemeInitOptions;
|
|
27
|
+
|
|
28
|
+
/** i18next options plus optional plugins array. Omit to skip i18n. */
|
|
29
|
+
i18n?: I18nInitOptions;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## `LayoutConfig`
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
type LayoutConfig = SidebarLayoutConfig & {
|
|
39
|
+
/** Responsive breakpoints [tablet-min, desktop-min] in px. Default: [768, 1024] */
|
|
40
|
+
breakpoints?: [number, number];
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## `ThemeInitOptions`
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
interface ThemeInitOptions {
|
|
50
|
+
/** Initial theme. Default: 'system' */
|
|
51
|
+
default?: 'system' | 'light' | 'dark';
|
|
52
|
+
|
|
53
|
+
/** Log theme decisions to console. */
|
|
54
|
+
debug?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Persist the user's preference.
|
|
58
|
+
* Set to `false` to disable persistence.
|
|
59
|
+
* Default: localStorage with no prefix.
|
|
60
|
+
*/
|
|
61
|
+
store?: false | {
|
|
62
|
+
type: 'cookie' | 'localStorage' | 'sessionStorage';
|
|
63
|
+
prefix?: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Apply built-in CSS custom properties. Default: true */
|
|
67
|
+
useBuiltIn?: boolean;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## `RouteConfig`
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
interface RouteConfig {
|
|
77
|
+
/** Matches the root path (equivalent to `path: ''`). */
|
|
78
|
+
index?: boolean;
|
|
79
|
+
|
|
80
|
+
/** Path string. Supports `:param` segments. */
|
|
81
|
+
path?: string;
|
|
82
|
+
|
|
83
|
+
/** Sets `document.title` when the route activates. */
|
|
84
|
+
title?: string;
|
|
85
|
+
|
|
86
|
+
/** Force a re-render even if the path did not change. */
|
|
87
|
+
force?: boolean;
|
|
88
|
+
|
|
89
|
+
/** Render function. May be async. */
|
|
90
|
+
render: (context: RouteContext) => RenderResult | Promise<RenderResult>;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## `RouteContext`
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface RouteContext {
|
|
100
|
+
/** Full URL string. */
|
|
101
|
+
href: string;
|
|
102
|
+
|
|
103
|
+
/** Pathname portion of the URL. */
|
|
104
|
+
pathname: string;
|
|
105
|
+
|
|
106
|
+
/** Configured basepath. */
|
|
107
|
+
basepath: string;
|
|
108
|
+
|
|
109
|
+
/** Named URL parameters extracted from the path pattern. */
|
|
110
|
+
params: Record<string, string>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Report loading progress (0–100).
|
|
114
|
+
* Drives the progress bar shown in the layout header.
|
|
115
|
+
*/
|
|
116
|
+
progress: (value: number) => void;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## `FallbackRouteConfig`
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
interface FallbackRouteConfig {
|
|
126
|
+
render: (context: RouteContext) => RenderResult | Promise<RenderResult>;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The fallback `RouteContext` will include an `error` property when triggered by
|
|
131
|
+
a routing error.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## `I18nInitOptions`
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
type I18nInitOptions = i18next.InitOptions & {
|
|
139
|
+
/** i18next plugins to register via i18next.use() before init. */
|
|
140
|
+
plugins?: (Module | NewableModule<Module> | Newable<Module>)[];
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## `NotificationOptions`
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
interface NotificationOptions {
|
|
150
|
+
title?: string;
|
|
151
|
+
duration?: number; // milliseconds, default 3000
|
|
152
|
+
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## `app` singleton methods
|
|
159
|
+
|
|
160
|
+
| Method | Signature | Description |
|
|
161
|
+
|--------|-----------|-------------|
|
|
162
|
+
| `load` | `(config: AppConfig) => Promise<void>` | Initialize and mount the application |
|
|
163
|
+
| `unload` | `() => void` | Tear down layout, router, and observers |
|
|
164
|
+
| `navigate` | `(path: string) => void` | Push a new route |
|
|
165
|
+
| `notice` | `(msg, opts?) => Promise<void>` | Show a neutral toast |
|
|
166
|
+
| `info` | `(msg, opts?) => Promise<void>` | Show an info toast |
|
|
167
|
+
| `success` | `(msg, opts?) => Promise<void>` | Show a success toast |
|
|
168
|
+
| `warning` | `(msg, opts?) => Promise<void>` | Show a warning toast |
|
|
169
|
+
| `error` | `(msg, opts?) => Promise<void>` | Show an error toast |
|
|
170
|
+
|
|
171
|
+
### `app` singleton properties
|
|
172
|
+
|
|
173
|
+
| Property | Type | Description |
|
|
174
|
+
|----------|------|-------------|
|
|
175
|
+
| `config` | `AppConfig \| undefined` | Current config passed to `load()` |
|
|
176
|
+
| `router` | `Router \| undefined` | Underlying `@iyulab/router` instance |
|
|
177
|
+
| `screen` | `ScreenSize \| undefined` | Current responsive screen size |
|
|
178
|
+
| `theme` | `Theme` (static) | Theme utility (`get`, `set`, `isInitialized`) |
|
|
179
|
+
| `i18n` | `i18next` | Raw i18next instance |
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Sidebar Layout Reference — @iyulab/modern-app
|
|
2
|
+
|
|
3
|
+
## `SidebarLayoutConfig`
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
interface SidebarLayoutConfig {
|
|
7
|
+
type: 'sidebar';
|
|
8
|
+
|
|
9
|
+
/** URL or path to the logo image. */
|
|
10
|
+
logo?: string;
|
|
11
|
+
|
|
12
|
+
/** Application title displayed beside the logo. */
|
|
13
|
+
title?: string;
|
|
14
|
+
|
|
15
|
+
/** Main (top) navigation items. */
|
|
16
|
+
main?: SidebarItem[];
|
|
17
|
+
|
|
18
|
+
/** Footer (bottom-pinned) items. */
|
|
19
|
+
footer?: SidebarItem[];
|
|
20
|
+
|
|
21
|
+
/** Per-part style overrides (CSS custom properties / inline styles). */
|
|
22
|
+
styles?: StyleMap<SidebarParts>;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## `SidebarItem` union
|
|
29
|
+
|
|
30
|
+
`SidebarItem` is the union of all six item types below.
|
|
31
|
+
|
|
32
|
+
### `SidebarLinkConfig` — `type: 'link'`
|
|
33
|
+
|
|
34
|
+
A single navigation link. Highlights automatically when the current URL matches `href` (or `pattern`).
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
interface SidebarLinkConfig {
|
|
38
|
+
type: 'link';
|
|
39
|
+
label: string | DirectiveResult;
|
|
40
|
+
href: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
/** Override the URL matching pattern. Accepts a string or URLPattern. */
|
|
43
|
+
pattern?: string | URLPattern;
|
|
44
|
+
styles?: StyleMap<'host' | 'base' | 'icon' | 'label'>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
{ type: 'link', icon: 'dashboard', label: 'Dashboard', href: '/' }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### `SidebarGroupConfig` — `type: 'group'`
|
|
57
|
+
|
|
58
|
+
Collapsible group that contains links.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
interface SidebarGroupConfig {
|
|
62
|
+
type: 'group';
|
|
63
|
+
icon: string;
|
|
64
|
+
label: string | DirectiveResult;
|
|
65
|
+
items: SidebarLinkConfig[];
|
|
66
|
+
/** Start collapsed. Default: true */
|
|
67
|
+
collapsed?: boolean;
|
|
68
|
+
styles?: StyleMap<'host' | 'header' | 'icon' | 'label' | 'caret' | 'items'>;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
{
|
|
76
|
+
type: 'group',
|
|
77
|
+
icon: 'settings',
|
|
78
|
+
label: 'Settings',
|
|
79
|
+
collapsed: false,
|
|
80
|
+
items: [
|
|
81
|
+
{ type: 'link', label: 'Profile', href: '/settings/profile' },
|
|
82
|
+
{ type: 'link', label: 'Security', href: '/settings/security' },
|
|
83
|
+
],
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### `SidebarSectionConfig` — `type: 'section'`
|
|
90
|
+
|
|
91
|
+
Labelled section that groups links and groups.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface SidebarSectionConfig {
|
|
95
|
+
type: 'section';
|
|
96
|
+
title: string | DirectiveResult;
|
|
97
|
+
subTitle?: string | DirectiveResult;
|
|
98
|
+
items: (SidebarGroupConfig | SidebarLinkConfig)[];
|
|
99
|
+
styles?: StyleMap<'host' | 'header' | 'title' | 'subtitle' | 'items'>;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
{
|
|
107
|
+
type: 'section',
|
|
108
|
+
title: 'Administration',
|
|
109
|
+
items: [
|
|
110
|
+
{ type: 'link', icon: 'users', label: 'Users', href: '/admin/users' },
|
|
111
|
+
{ type: 'link', icon: 'database', label: 'Database', href: '/admin/db' },
|
|
112
|
+
],
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `SidebarButtonConfig` — `type: 'button'`
|
|
119
|
+
|
|
120
|
+
Action button — triggers a callback instead of navigating.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
interface SidebarButtonConfig {
|
|
124
|
+
type: 'button';
|
|
125
|
+
icon?: string;
|
|
126
|
+
label: string | DirectiveResult;
|
|
127
|
+
onClick: () => void;
|
|
128
|
+
styles?: StyleMap<string>;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
{ type: 'button', icon: 'logout', label: 'Sign Out', onClick: () => auth.signOut() }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### `SidebarHtmlConfig` — `type: 'html'`
|
|
141
|
+
|
|
142
|
+
Renders a custom Lit template or raw HTML element. The `render` function
|
|
143
|
+
receives the current sidebar state so you can adapt the content.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
interface SidebarHtmlConfig {
|
|
147
|
+
type: 'html';
|
|
148
|
+
render: (state: SidebarState) => TemplateResult<1> | HTMLElement | string;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`SidebarState` values: `'default'` | `'slim'` | `'modal'` | `'mobile'` | `'mobile-open'`
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
{
|
|
158
|
+
type: 'html',
|
|
159
|
+
render: (state) => html`
|
|
160
|
+
<div class="user-card" ?hidden=${state === 'slim'}>
|
|
161
|
+
<img src="/avatar.png" />
|
|
162
|
+
<span>John Doe</span>
|
|
163
|
+
</div>
|
|
164
|
+
`,
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Sidebar parts
|
|
171
|
+
|
|
172
|
+
Parts available for `styles` overrides on the root layout:
|
|
173
|
+
|
|
174
|
+
| Part | Element |
|
|
175
|
+
|------|---------|
|
|
176
|
+
| `host` | Outer layout shell |
|
|
177
|
+
| `mobile-header` | Top bar shown on mobile |
|
|
178
|
+
| `sidebar` | Sidebar panel |
|
|
179
|
+
| `sidebar-header` | Logo + title area |
|
|
180
|
+
| `sidebar-main` | Scrollable main nav area |
|
|
181
|
+
| `sidebar-footer` | Pinned footer area |
|
|
182
|
+
| `main` | Main content area |
|
|
183
|
+
| `progress` | Top progress bar |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Responsive behaviour
|
|
188
|
+
|
|
189
|
+
| Screen width | Sidebar state |
|
|
190
|
+
|--------------|--------------|
|
|
191
|
+
| < breakpoints[0] | `mobile` / `mobile-open` |
|
|
192
|
+
| breakpoints[0] – breakpoints[1] | `slim` (icons only) |
|
|
193
|
+
| > breakpoints[1] | `default` (full labels) |
|
|
194
|
+
|
|
195
|
+
Default breakpoints: `[768, 1024]` px.
|
|
196
|
+
Override per app:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
layout: {
|
|
200
|
+
type: 'sidebar',
|
|
201
|
+
breakpoints: [640, 1280],
|
|
202
|
+
// ...
|
|
203
|
+
}
|
|
204
|
+
```
|