@iyulab/modern-app 0.3.2 → 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 CHANGED
@@ -1,5 +1,20 @@
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
+
13
+ ## 0.3.3 (2026-04-01)
14
+
15
+ ### Fixes
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
17
+
3
18
  ## 0.3.2 (2026-04-01)
4
19
 
5
20
  ### Breaking Changes
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
- # Modern App
1
+ # @iyulab/modern-app
2
2
 
3
- A modern web application framework by iyulab, built on React and Lit Element.
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
- ## Architecture
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
- This is by design Lit components render on the client side. If you need SSR capabilities, consider using Lit's experimental SSR support separately or choose a framework designed for SSR from the start.
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
- // ...layout configuration
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, render: () => html`<home-page></home-page>` },
44
- { path: 'about', render: () => html`<about-page></about-page>` },
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
- ## API Reference
48
+ ## Skills Usage
50
49
 
51
- ### Navigation
50
+ AI agent skills for this package are located in `skills/modern-app/`. Install them with `npx skills`:
52
51
 
53
- ```typescript
54
- // Navigate to a path
55
- app.navigate('/path');
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
- ### Theme Management
65
-
66
- ```typescript
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
- ### Notifications
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
- ### Localization
64
+ ### Navigation
96
65
 
97
66
  ```typescript
98
- // Access i18next instance
99
- app.localizer; // i18next instance
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
- ## Configuration
107
-
108
- ### AppConfig
72
+ ### Theme
109
73
 
110
74
  ```typescript
111
- interface AppConfig {
112
- root?: Element; // Root element (default: document.body)
113
- basepath?: string; // Base path for routing (default: '/')
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
- ### Theme Options
80
+ ### Notifications
123
81
 
124
82
  ```typescript
125
- interface ThemeInitOptions {
126
- default?: 'system' | 'light' | 'dark'; // Default theme
127
- debug?: boolean; // Enable debug logging
128
- store?: false | { // Persist theme preference
129
- type: 'cookie' | 'localStorage' | 'sessionStorage';
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
- ### Layout Configuration (Sidebar)
90
+ ### Localization (i18next)
137
91
 
138
92
  ```typescript
139
- interface SidebarLayoutConfig {
140
- type: 'sidebar';
141
- breakpoints?: [number, number]; // [small, medium] (default: [768, 1024])
142
- logo?: {
143
- type: 'icon' | 'image';
144
- icon?: string;
145
- src?: string;
146
- label?: string;
147
- onClick?: () => void;
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
- ### Route Configuration
103
+ // Access i18next
104
+ app.i18n.t('common::greeting');
105
+ app.i18n.changeLanguage('ko');
155
106
 
156
- ```typescript
157
- interface RouteConfig {
158
- index?: boolean; // Index route
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
@@ -29,5 +29,5 @@ var s = class extends n {
29
29
  t([o({
30
30
  type: Boolean,
31
31
  reflect: !0
32
- }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "icon", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "label", void 0), s = t([a("sidebar-button")], s);
32
+ }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "icon", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "label", void 0), s = t([a("u-sidebar-button")], s);
33
33
  //#endregion
@@ -51,5 +51,5 @@ t([s({
51
51
  }), e("design:type", Boolean)], c.prototype, "selected", void 0), t([s({
52
52
  type: Boolean,
53
53
  reflect: !0
54
- }), e("design:type", Boolean)], c.prototype, "collapsed", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), c = t([o("sidebar-group")], c);
54
+ }), e("design:type", Boolean)], c.prototype, "collapsed", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), c = t([o("u-sidebar-group")], c);
55
55
  //#endregion
@@ -31,6 +31,6 @@ var s = class extends n {
31
31
  t([o({
32
32
  type: Boolean,
33
33
  reflect: !0
34
- }), e("design:type", Object)], s.prototype, "selected", void 0), t([o({ type: Boolean }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "icon", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "label", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "href", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "pattern", void 0), s = t([a("sidebar-link")], s);
34
+ }), e("design:type", Object)], s.prototype, "selected", void 0), t([o({ type: Boolean }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "icon", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "label", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "href", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "pattern", void 0), s = t([a("u-sidebar-link")], s);
35
35
  //#endregion
36
36
  export { s as SidebarLink };
@@ -29,5 +29,5 @@ var s = class extends n {
29
29
  `;
30
30
  }
31
31
  };
32
- t([o({ type: Boolean }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "mainTitle", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "subTitle", void 0), s = t([a("sidebar-section")], s);
32
+ t([o({ type: Boolean }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "mainTitle", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "subTitle", void 0), s = t([a("u-sidebar-section")], s);
33
33
  //#endregion
@@ -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
- height: 4px;
160
- border-radius: 0;
161
- background-color: transparent;
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.2",
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.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
+ ```