@iyulab/modern-app 0.33.3 → 0.34.1
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 +65 -0
- package/dist/App.d.ts +11 -0
- package/dist/App.js +7 -0
- package/dist/components/ActionBar.d.ts +2 -2
- package/dist/components/GroupBox.d.ts +9 -2
- package/dist/components/GroupBox.js +18 -15
- package/dist/components/GroupBox.styles.js +28 -5
- package/dist/components/MasterDetailLayout.js +1 -1
- package/dist/components/PageHeader.d.ts +14 -4
- package/dist/components/PageHeader.js +8 -3
- package/dist/components/PageHeader.styles.js +22 -4
- package/dist/components/SidebarButton.styles.js +2 -0
- package/dist/components/SidebarGroup.styles.js +2 -0
- package/dist/components/SidebarLink.d.ts +11 -1
- package/dist/components/SidebarLink.js +2 -1
- package/dist/components/SidebarLink.styles.js +43 -7
- package/dist/components/SidebarSection.styles.js +6 -9
- package/dist/components/Wizard.js +1 -1
- package/dist/components/Wizard.styles.js +2 -0
- package/dist/layouts/SidebarLayout.d.ts +5 -0
- package/dist/layouts/SidebarLayout.js +6 -5
- package/dist/layouts/SidebarLayout.styles.js +23 -5
- package/dist/react/ActionBar.d.ts +4 -2
- package/dist/react/EmptyState.d.ts +4 -2
- package/dist/react/GroupBox.d.ts +4 -2
- package/dist/react/InfoField.d.ts +4 -2
- package/dist/react/InfoSection.d.ts +4 -2
- package/dist/react/MasterDetailLayout.d.ts +4 -2
- package/dist/react/PageHeader.d.ts +4 -2
- package/dist/react/SidebarButton.d.ts +4 -2
- package/dist/types/AppConfigs.d.ts +7 -0
- package/package.json +5 -4
- package/skills/modern-app/SKILL.md +0 -1
- package/skills/modern-app/references/api.md +3 -0
- package/skills/modern-app/references/components/action-bar.md +2 -2
- package/skills/modern-app/references/components/group-box.md +3 -0
- package/skills/modern-app/references/components/page-header.md +12 -8
- package/skills/modern-app/references/layout.md +24 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.34.1] - 2026-10-05
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The sidebar footer was cut off when the menu was longer than the screen** and the shell's parent
|
|
8
|
+
had no height of its own (a custom `root`, or `SidebarLayout` mounted from React). The shell is
|
|
9
|
+
capped at the viewport, but the sidebar took `height: 100%`, which cannot resolve against a cap, so
|
|
10
|
+
it grew to the menu's length and the shell clipped the footer out of reach. The sidebar now takes
|
|
11
|
+
the shell's height, the menu scrolls inside `part="sidebar-main"`, and the footer stays at the
|
|
12
|
+
bottom — in the default, slim, drawer and mobile states.
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
- The layout guide now says that a custom `root` also keeps the browser's default body margin, and
|
|
17
|
+
shows `:where(body) { margin: 0; }` next to the height rule.
|
|
18
|
+
|
|
19
|
+
## [0.34.0] - 2026-10-05
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Press targets follow `--u-target-size`** (`@iyulab/components` 2.0). When it is set, sidebar links,
|
|
24
|
+
buttons and group headers, the page header's back link, the shell logo and wizard steps are at least
|
|
25
|
+
that size (the logo image and the back link keep their visible size and layout). Unset, nothing changes.
|
|
26
|
+
- **`routerMode` in `app.load()`** — `'hash'` puts the route after `#` (`/app/#/orders/7`), so refreshes and
|
|
27
|
+
deep links work on a static host with no server fallback. Forwarded to `@iyulab/router`'s `mode`
|
|
28
|
+
(router 0.18.0); the default stays `'history'`.
|
|
29
|
+
- **`eyebrow` on `u-page-header`** — a small line above the title for the menu group or the parent
|
|
30
|
+
path, on the overline step. A `slot="eyebrow"` replaces the text when it needs links (a breadcrumb).
|
|
31
|
+
The row takes no space when neither is given (`part="eyebrow"`).
|
|
32
|
+
- **`description` on `u-group-box`** — one line under the title saying what the box holds or what
|
|
33
|
+
its numbers are based on, at caption size in the weak text color (`part="description"`). A
|
|
34
|
+
description alone opens the header, like `title`.
|
|
35
|
+
- **`--app-sidebar-active-icon-color` and `--app-sidebar-active-indicator-color`** (with
|
|
36
|
+
`--app-sidebar-active-indicator-width`, default `3px`) — a shell with a light active background
|
|
37
|
+
can color only the active icon and draw a bar on the start edge of the active menu, instead of
|
|
38
|
+
filling the whole item. Defaults keep today's look: the icon follows `--app-sidebar-active-fg` and
|
|
39
|
+
the bar matches the active background.
|
|
40
|
+
- **`count` on sidebar links, and `app.setNavCount(href, value)`** — a link can show the work waiting
|
|
41
|
+
behind it at the end of the item (`part="count"`, tabular figures). Set it in the item config or
|
|
42
|
+
change it while the app runs; `undefined` clears it. In the compact sidebar it stays in the
|
|
43
|
+
accessible name and is hidden visually.
|
|
44
|
+
- **Shell size tokens** — `--app-main-padding` (32px), `--app-sidebar-item-radius` (8px),
|
|
45
|
+
`--app-sidebar-item-gap` (12px), `--app-sidebar-item-padding` (8px 12px),
|
|
46
|
+
`--app-sidebar-icon-size` (20px), `--app-sidebar-active-shadow` and
|
|
47
|
+
`--app-sidebar-active-shadow-hover`. A theme can now set a denser navigation or a flat current item
|
|
48
|
+
without reaching into the shadow DOM. Defaults keep today's look.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- 🔴**Requires `@iyulab/components` 2.0.** Internal buttons (the shell's sidebar toggler and
|
|
53
|
+
overlay close, the master-detail close, the wizard's back button) use the 2.0 `appearance` vocabulary
|
|
54
|
+
(`appearance="plain"`, was `variant="ghost"`); with a 1.x components they render as solid buttons.
|
|
55
|
+
- **The routed page sits on `--u-canvas-bg-color`** (new in `@iyulab/components` 2.0 — the
|
|
56
|
+
application background), not on `--u-bg-color`, which is the surface components paint their own
|
|
57
|
+
face with. With the built-in defaults the page is a near-white `#FAFAFA`; cards and tables on it
|
|
58
|
+
keep their white surface.
|
|
59
|
+
- **`u-group-box` is a card like `u-card`**: `--u-panel-bg-color` face, `--u-border-color` line,
|
|
60
|
+
`--u-radius-xl` corners (was the chrome tint `--u-bg-color-raised`, a weak line and `2xl`). The page
|
|
61
|
+
now sits on the canvas, so a card no longer needs a tint to stand apart from it.
|
|
62
|
+
- **Sidebar section title and subtitle read role tokens** (`--u-txt-color-weak` · `--u-txt-color-weaker`)
|
|
63
|
+
instead of the neutral palette, so a theme reaches them. Same values with the built-in defaults.
|
|
64
|
+
- **`u-page-header`'s `subtitle` reads at body size** (was caption). It is the screen's one-line
|
|
65
|
+
description; at caption size it read as a footnote under a 26px title. Color stays
|
|
66
|
+
`--u-txt-color-weak`.
|
|
67
|
+
|
|
3
68
|
## [0.33.3] - 2026-10-05
|
|
4
69
|
|
|
5
70
|
### Fixed
|
package/dist/App.d.ts
CHANGED
|
@@ -35,6 +35,17 @@ declare class App {
|
|
|
35
35
|
unload(): void;
|
|
36
36
|
/** 페이지 이동 */
|
|
37
37
|
navigate(path: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Sets the count shown on a sidebar link — the work waiting behind it — without rebuilding the
|
|
40
|
+
* layout. `href` is the link's `href` as configured; `undefined` clears it (and falls back to the
|
|
41
|
+
* item's static `count`, if any).
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* app.setNavCount('/reviews', 12);
|
|
45
|
+
* app.setNavCount('/reviews', undefined);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
setNavCount(href: string, value: number | string | undefined): void;
|
|
38
49
|
/** 공지 메시지 */
|
|
39
50
|
notice(message: string, options?: NotificationOptions): Promise<void>;
|
|
40
51
|
/** 정보 메시지 */
|
package/dist/App.js
CHANGED
|
@@ -68,6 +68,7 @@ var l = class o {
|
|
|
68
68
|
this._layout.appendChild(s), this._router = new n({
|
|
69
69
|
root: this._layout,
|
|
70
70
|
basepath: a.basepath,
|
|
71
|
+
mode: a.routerMode,
|
|
71
72
|
routes: a.routes,
|
|
72
73
|
fallback: a.fallback,
|
|
73
74
|
enter: a.enter,
|
|
@@ -81,6 +82,12 @@ var l = class o {
|
|
|
81
82
|
navigate(e) {
|
|
82
83
|
this._router?.go(e);
|
|
83
84
|
}
|
|
85
|
+
setNavCount(e, t) {
|
|
86
|
+
let n = this._layout;
|
|
87
|
+
if (!n || !("counts" in n)) return;
|
|
88
|
+
let r = { ...n.counts };
|
|
89
|
+
t === void 0 ? delete r[e] : r[e] = t, n.counts = r;
|
|
90
|
+
}
|
|
84
91
|
async notice(e, t) {
|
|
85
92
|
await a.notice(e, { ...t });
|
|
86
93
|
}
|
|
@@ -11,8 +11,8 @@ type ElementParts = 'host' | 'danger' | 'main';
|
|
|
11
11
|
*
|
|
12
12
|
* ```html
|
|
13
13
|
* <u-action-bar sticky>
|
|
14
|
-
* <u-button slot="danger" color="danger"
|
|
15
|
-
* <u-button
|
|
14
|
+
* <u-button slot="danger" color="danger" appearance="plain">삭제</u-button>
|
|
15
|
+
* <u-button appearance="plain">취소</u-button>
|
|
16
16
|
* <u-button color="primary">저장</u-button>
|
|
17
17
|
* </u-action-bar>
|
|
18
18
|
* ```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
2
|
import { default as React } from 'react';
|
|
3
|
-
type ElementParts = 'host' | 'header' | 'title' | 'meta' | 'actions' | 'body';
|
|
3
|
+
type ElementParts = 'host' | 'header' | 'title' | 'meta' | 'description' | 'actions' | 'body';
|
|
4
4
|
/** 제목 단계 — 페이지 제목(h1)은 `u-page-header` 몫이라 2 부터다. */
|
|
5
5
|
export type GroupBoxLevel = 2 | 3 | 4 | 5 | 6;
|
|
6
6
|
/**
|
|
@@ -18,7 +18,7 @@ export type GroupBoxLevel = 2 | 3 | 4 | 5 | 6;
|
|
|
18
18
|
* </u-group-box>
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
21
|
-
* 오버라이드: `part`(host·header·title·meta·actions·body) + slot 치환.
|
|
21
|
+
* 오버라이드: `part`(host·header·title·meta·description·actions·body) + slot 치환.
|
|
22
22
|
* `divider` 속성으로 제목과 본문 사이 구분선을 켠다(기본 꺼짐 — 선이 많으면 화면이 시끄럽다).
|
|
23
23
|
*/
|
|
24
24
|
export declare class GroupBox extends StyledElement<ElementParts> {
|
|
@@ -33,6 +33,12 @@ export declare class GroupBox extends StyledElement<ElementParts> {
|
|
|
33
33
|
* without a `title`: it describes the title.
|
|
34
34
|
*/
|
|
35
35
|
meta?: string;
|
|
36
|
+
/**
|
|
37
|
+
* One line under the title saying what the box holds or what its numbers are based on
|
|
38
|
+
* (`Prices are per unit, VAT excluded`). Caption size, weak color — it explains the title
|
|
39
|
+
* without competing with it. Opens the header on its own, like `title`.
|
|
40
|
+
*/
|
|
41
|
+
description?: string;
|
|
36
42
|
/** 제목과 본문 사이에 구분선을 넣는다. */
|
|
37
43
|
divider: boolean;
|
|
38
44
|
/** 본문 여백을 없앤다 — 표를 카드 가장자리까지 붙일 때. */
|
|
@@ -69,6 +75,7 @@ declare module 'react' {
|
|
|
69
75
|
flush?: boolean;
|
|
70
76
|
level?: GroupBoxLevel;
|
|
71
77
|
meta?: string;
|
|
78
|
+
description?: string;
|
|
72
79
|
};
|
|
73
80
|
}
|
|
74
81
|
}
|
|
@@ -3,17 +3,17 @@ import t from "../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js
|
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { styles as i } from "./GroupBox.styles.js";
|
|
6
|
-
import { html as a } from "lit";
|
|
7
|
-
import { customElement as
|
|
8
|
-
import { html as
|
|
6
|
+
import { html as a, nothing as o } from "lit";
|
|
7
|
+
import { customElement as s, property as c, state as l } from "lit/decorators.js";
|
|
8
|
+
import { html as u, literal as d } from "lit/static-html.js";
|
|
9
9
|
//#region src/components/GroupBox.ts
|
|
10
|
-
var
|
|
11
|
-
2:
|
|
12
|
-
3:
|
|
13
|
-
4:
|
|
14
|
-
5:
|
|
15
|
-
6:
|
|
16
|
-
},
|
|
10
|
+
var f = {
|
|
11
|
+
2: d`h2`,
|
|
12
|
+
3: d`h3`,
|
|
13
|
+
4: d`h4`,
|
|
14
|
+
5: d`h5`,
|
|
15
|
+
6: d`h6`
|
|
16
|
+
}, p = class extends n {
|
|
17
17
|
constructor(...e) {
|
|
18
18
|
super(...e), this.title = "", this.divider = !1, this.flush = !1, this.level = 3, this.hasActions = !1;
|
|
19
19
|
}
|
|
@@ -21,10 +21,13 @@ var d = {
|
|
|
21
21
|
this.styles = [super.styles, i];
|
|
22
22
|
}
|
|
23
23
|
render() {
|
|
24
|
-
let e = !!this.title || this.hasActions;
|
|
24
|
+
let e = !!this.title || !!this.description || this.hasActions;
|
|
25
25
|
return a`
|
|
26
26
|
<div class="header ${this.divider ? "divider" : ""} ${e ? "" : "empty"}" part="header">
|
|
27
|
-
|
|
27
|
+
<div class="titles">
|
|
28
|
+
${u`<${f[this.level] ?? f[3]} class="title" part="title">${this.title}${this.title && this.meta ? a` <span class="meta" part="meta">${this.meta}</span>` : ""}</${f[this.level] ?? f[3]}>`}
|
|
29
|
+
${this.description ? a`<p class="description" part="description">${this.description}</p>` : o}
|
|
30
|
+
</div>
|
|
28
31
|
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
29
32
|
<slot name="actions"
|
|
30
33
|
@slotchange=${(e) => this.hasActions = r(e.target)}
|
|
@@ -35,9 +38,9 @@ var d = {
|
|
|
35
38
|
`;
|
|
36
39
|
}
|
|
37
40
|
};
|
|
38
|
-
t([
|
|
41
|
+
t([c({ type: String }), e("design:type", Object)], p.prototype, "title", void 0), t([c({ type: String }), e("design:type", String)], p.prototype, "meta", void 0), t([c({ type: String }), e("design:type", String)], p.prototype, "description", void 0), t([c({ type: Boolean }), e("design:type", Object)], p.prototype, "divider", void 0), t([c({ type: Boolean }), e("design:type", Object)], p.prototype, "flush", void 0), t([c({
|
|
39
42
|
type: Number,
|
|
40
43
|
reflect: !0
|
|
41
|
-
}), e("design:type", Object)],
|
|
44
|
+
}), e("design:type", Object)], p.prototype, "level", void 0), t([l(), e("design:type", Object)], p.prototype, "hasActions", void 0), p = t([s("u-group-box")], p);
|
|
42
45
|
//#endregion
|
|
43
|
-
export {
|
|
46
|
+
export { p as GroupBox };
|
|
@@ -7,10 +7,12 @@ var t = e`
|
|
|
7
7
|
사이드바가 열린 1280px 화면의 좁은 본문에서도, 넓은 본문을 가진 태블릿에서도
|
|
8
8
|
같은 판단을 하려면 기준이 자기 폭이어야 한다. */
|
|
9
9
|
container-type: inline-size;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/* 카드 = 떠 있는 컨테이너(--u-panel-bg-color) — u-card 와 같은 면·반경·선이다.
|
|
11
|
+
페이지는 --u-canvas-bg-color 위에 있으므로 카드는 바탕보다 한 단 밝은 면으로 선다
|
|
12
|
+
(종전에는 페이지가 흰색이라 카드를 크롬 틴트 --u-bg-color-raised 로 칠해 구분했다). */
|
|
13
|
+
background-color: var(--u-panel-bg-color, #FFFFFF);
|
|
14
|
+
border: 1px solid var(--u-border-color, #E0E0E0);
|
|
15
|
+
border-radius: var(--u-radius-xl, 8px);
|
|
14
16
|
box-shadow: var(--u-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 1px rgba(0, 0, 0, 0.04));
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -32,9 +34,17 @@ var t = e`
|
|
|
32
34
|
border-bottom: 1px solid var(--u-border-color-weak, #EEEEEE);
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/* 제목과 설명 한 줄을 세로로 묶는다 — 액션은 그 오른쪽에 선다. */
|
|
38
|
+
.titles {
|
|
39
|
+
flex: 1 1 auto;
|
|
40
|
+
min-width: 0;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: var(--u-space-3xs, 2px);
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
.title {
|
|
36
47
|
margin: 0;
|
|
37
|
-
flex: 1 1 auto;
|
|
38
48
|
min-width: 0;
|
|
39
49
|
font-size: var(--u-text-subtitle-size, 16px);
|
|
40
50
|
font-weight: var(--u-text-subtitle-weight, 600);
|
|
@@ -51,6 +61,19 @@ var t = e`
|
|
|
51
61
|
font-variant-numeric: tabular-nums;
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
.description {
|
|
65
|
+
margin: 0;
|
|
66
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
67
|
+
font-weight: var(--u-text-caption-weight, 400);
|
|
68
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
69
|
+
color: var(--u-txt-color-weak, #616161);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* 제목 없이 설명만 있으면 빈 헤딩이 자리를 잡지 않는다. */
|
|
73
|
+
.title:empty {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
.actions {
|
|
55
78
|
flex: 0 0 auto;
|
|
56
79
|
display: flex;
|
|
@@ -39,7 +39,7 @@ var u = class extends n {
|
|
|
39
39
|
<div class="master ${this.hasDetail ? "covered" : ""}" part="master"><slot></slot></div>
|
|
40
40
|
<div class="divider ${this.hasDetail ? "" : "empty"}" part="divider"></div>
|
|
41
41
|
<div class="detail ${this.hasDetail ? "" : "empty"}" part="detail">
|
|
42
|
-
<u-button class="detail-close" part="detail-close"
|
|
42
|
+
<u-button class="detail-close" part="detail-close" appearance="plain"
|
|
43
43
|
aria-label=${r(this.locale || void 0).detailClose}
|
|
44
44
|
@click=${this.handleDetailClose}
|
|
45
45
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
2
|
import { default as React } from 'react';
|
|
3
|
-
type ElementParts = 'host' | 'back' | 'heading' | 'title' | 'subtitle' | 'status' | 'actions';
|
|
3
|
+
type ElementParts = 'host' | 'back' | 'heading' | 'eyebrow' | 'title' | 'subtitle' | 'status' | 'actions';
|
|
4
4
|
/**
|
|
5
5
|
* 페이지 헤더 — 모든 LOB 화면 최상단의 같은 골격.
|
|
6
6
|
*
|
|
@@ -10,21 +10,29 @@ type ElementParts = 'host' | 'back' | 'heading' | 'title' | 'subtitle' | 'status
|
|
|
10
10
|
* 하나로 만들어지지 않았다"* 로 읽힌다.
|
|
11
11
|
*
|
|
12
12
|
* ```html
|
|
13
|
-
* <u-page-header title="주문 G-2026-I-0629" subtitle="2026-02-24 접수" back="/orders">
|
|
13
|
+
* <u-page-header eyebrow="주문 관리" title="주문 G-2026-I-0629" subtitle="2026-02-24 접수" back="/orders">
|
|
14
14
|
* <span slot="status"><!-- 배지 --></span>
|
|
15
15
|
* <span slot="actions"><!-- 버튼 --></span>
|
|
16
16
|
* </u-page-header>
|
|
17
17
|
* ```
|
|
18
18
|
*
|
|
19
|
-
* 오버라이드: `part`(host·back·heading·title·subtitle·status·actions) + slot 치환.
|
|
19
|
+
* 오버라이드: `part`(host·back·heading·eyebrow·title·subtitle·status·actions) + slot 치환.
|
|
20
20
|
* 값은 전부 토큰을 경유한다 — 리터럴을 두면 소비자가 밀도를 바꿀 수 없다.
|
|
21
21
|
*/
|
|
22
22
|
export declare class PageHeader extends StyledElement<ElementParts> {
|
|
23
23
|
static styles: import('lit').CSSResultGroup[];
|
|
24
24
|
/** 페이지 제목. 타입 스케일의 `display` 단을 쓴다. */
|
|
25
25
|
title: string;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* 제목 아래 한 줄 설명 — 이 화면이 무엇을 하는지, 숫자가 무엇을 기준으로 하는지. 없으면 렌더하지 않는다.
|
|
28
|
+
* 본문 단(`body`)을 한 단 옅은 색으로 쓴다 — 캡션 단이면 설명이 아니라 각주로 읽힌다.
|
|
29
|
+
*/
|
|
27
30
|
subtitle?: string;
|
|
31
|
+
/**
|
|
32
|
+
* 제목 위 위치 표시 — 메뉴 그룹 이름이나 상위 경로. `overline` 단.
|
|
33
|
+
* 링크가 필요하면(브레드크럼) 같은 이름의 슬롯(`slot="eyebrow"`)을 쓴다 — 슬롯이 이 문자열을 대신한다.
|
|
34
|
+
*/
|
|
35
|
+
eyebrow?: string;
|
|
28
36
|
/**
|
|
29
37
|
* 뒤로가기 링크 주소. 주면 제목 왼쪽에 `← 목록` 형태로 나온다.
|
|
30
38
|
* ⚠텍스트는 `backLabel` 또는 locale 레지스트리로 바꾼다.
|
|
@@ -42,6 +50,7 @@ export declare class PageHeader extends StyledElement<ElementParts> {
|
|
|
42
50
|
* 슬롯 배정 상태. ★CSS 로는 알 수 없다 — `<slot>` 자신이 자식이라 `:has(*)` 가
|
|
43
51
|
* 항상 참이다(실브라우저로 확인). 빈 배지 자리가 남으면 제목 위치가 화면마다 달라진다.
|
|
44
52
|
*/
|
|
53
|
+
private hasEyebrow;
|
|
45
54
|
private hasStatus;
|
|
46
55
|
private hasActions;
|
|
47
56
|
render(): import('lit-html').TemplateResult<1>;
|
|
@@ -57,6 +66,7 @@ declare module 'react' {
|
|
|
57
66
|
'u-page-header': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
|
|
58
67
|
title?: string;
|
|
59
68
|
subtitle?: string;
|
|
69
|
+
eyebrow?: string;
|
|
60
70
|
back?: string;
|
|
61
71
|
'back-label'?: string;
|
|
62
72
|
locale?: string;
|
|
@@ -9,7 +9,7 @@ import { customElement as c, property as l, state as u } from "lit/decorators.js
|
|
|
9
9
|
//#region src/components/PageHeader.ts
|
|
10
10
|
var d = class extends n {
|
|
11
11
|
constructor(...e) {
|
|
12
|
-
super(...e), this.title = "", this.backLabel = "", this.locale = "", this.hasStatus = !1, this.hasActions = !1;
|
|
12
|
+
super(...e), this.title = "", this.backLabel = "", this.locale = "", this.hasEyebrow = !1, this.hasStatus = !1, this.hasActions = !1;
|
|
13
13
|
}
|
|
14
14
|
static {
|
|
15
15
|
this.styles = [super.styles, a];
|
|
@@ -18,6 +18,11 @@ var d = class extends n {
|
|
|
18
18
|
return o`
|
|
19
19
|
${this.back ? o`<a class="back" part="back" href=${this.back}>${this.backLabel || r(this.locale || void 0).back}</a>` : s}
|
|
20
20
|
<div class="heading" part="heading">
|
|
21
|
+
<div class="eyebrow ${this.eyebrow || this.hasEyebrow ? "" : "empty"}" part="eyebrow">
|
|
22
|
+
<slot name="eyebrow"
|
|
23
|
+
@slotchange=${(e) => this.hasEyebrow = i(e.target)}
|
|
24
|
+
>${this.eyebrow ?? s}</slot>
|
|
25
|
+
</div>
|
|
21
26
|
<div class="title-row">
|
|
22
27
|
<h1 class="title" part="title">${this.title}</h1>
|
|
23
28
|
<span class="status ${this.hasStatus ? "" : "empty"}" part="status">
|
|
@@ -36,9 +41,9 @@ var d = class extends n {
|
|
|
36
41
|
`;
|
|
37
42
|
}
|
|
38
43
|
};
|
|
39
|
-
t([l({ type: String }), e("design:type", Object)], d.prototype, "title", void 0), t([l({ type: String }), e("design:type", String)], d.prototype, "subtitle", void 0), t([l({ type: String }), e("design:type", String)], d.prototype, "back", void 0), t([l({
|
|
44
|
+
t([l({ type: String }), e("design:type", Object)], d.prototype, "title", void 0), t([l({ type: String }), e("design:type", String)], d.prototype, "subtitle", void 0), t([l({ type: String }), e("design:type", String)], d.prototype, "eyebrow", void 0), t([l({ type: String }), e("design:type", String)], d.prototype, "back", void 0), t([l({
|
|
40
45
|
type: String,
|
|
41
46
|
attribute: "back-label"
|
|
42
|
-
}), e("design:type", Object)], d.prototype, "backLabel", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "locale", void 0), t([u(), e("design:type", Object)], d.prototype, "hasStatus", void 0), t([u(), e("design:type", Object)], d.prototype, "hasActions", void 0), d = t([c("u-page-header")], d);
|
|
47
|
+
}), e("design:type", Object)], d.prototype, "backLabel", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "locale", void 0), t([u(), e("design:type", Object)], d.prototype, "hasEyebrow", void 0), t([u(), e("design:type", Object)], d.prototype, "hasStatus", void 0), t([u(), e("design:type", Object)], d.prototype, "hasActions", void 0), d = t([c("u-page-header")], d);
|
|
43
48
|
//#endregion
|
|
44
49
|
export { d as PageHeader };
|
|
@@ -34,12 +34,28 @@ var t = e`
|
|
|
34
34
|
|
|
35
35
|
.subtitle {
|
|
36
36
|
margin: var(--u-space-2xs, 4px) 0 0;
|
|
37
|
-
font-size: var(--u-text-
|
|
38
|
-
font-weight: var(--u-text-
|
|
39
|
-
line-height: var(--u-text-
|
|
37
|
+
font-size: var(--u-text-body-size, 14px);
|
|
38
|
+
font-weight: var(--u-text-body-weight, 400);
|
|
39
|
+
line-height: var(--u-text-body-leading, 1.6);
|
|
40
40
|
color: var(--u-txt-color-weak, #616161);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/* 제목 위 위치 표시 — 제목보다 네 단 작고 옅다. 링크(브레드크럼)는 상속받은 색으로 선다. */
|
|
44
|
+
.eyebrow {
|
|
45
|
+
margin: 0 0 var(--u-space-2xs, 4px);
|
|
46
|
+
font-size: var(--u-text-overline-size, 11px);
|
|
47
|
+
font-weight: var(--u-text-overline-weight, 700);
|
|
48
|
+
line-height: var(--u-text-overline-leading, 1.45);
|
|
49
|
+
letter-spacing: var(--u-text-overline-tracking, 0.06em);
|
|
50
|
+
color: var(--u-txt-color-weak, #616161);
|
|
51
|
+
}
|
|
52
|
+
.eyebrow ::slotted(*) {
|
|
53
|
+
color: inherit;
|
|
54
|
+
}
|
|
55
|
+
.eyebrow.empty {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
43
59
|
.back {
|
|
44
60
|
flex: 0 0 auto;
|
|
45
61
|
align-self: center;
|
|
@@ -52,9 +68,11 @@ var t = e`
|
|
|
52
68
|
⚠고정 패딩(4px)이면 안 된다: line-height: normal 의 높이는 글꼴 메트릭이 정해서 글꼴에 따라 17px(→25)도
|
|
53
69
|
15px(→23)도 된다. 줄 상자가 이미 24px 이상이면 패딩은 0 이다. */
|
|
54
70
|
display: inline-block;
|
|
55
|
-
--
|
|
71
|
+
/* 호스트 하한(--u-target-size)이 있으면 24px 대신 그 값까지 — 폭도 그 값 이상. */
|
|
72
|
+
--_back-pad: max(0px, calc((max(24px, var(--u-target-size, 0px)) - 1lh) / 2));
|
|
56
73
|
padding-block: var(--_back-pad);
|
|
57
74
|
margin-block: calc(-1 * var(--_back-pad));
|
|
75
|
+
min-width: var(--u-target-size, 0px);
|
|
58
76
|
}
|
|
59
77
|
.back::before {
|
|
60
78
|
content: '←';
|
|
@@ -19,6 +19,8 @@ var t = e`
|
|
|
19
19
|
justify-content: space-between;
|
|
20
20
|
gap: 12px;
|
|
21
21
|
padding: 8px 12px;
|
|
22
|
+
/* 호스트 하한(--u-target-size, 미설정 = 0) — 상자는 border-box(기반 스타일)라 여백을 포함한다. */
|
|
23
|
+
min-height: var(--u-target-size, 0px);
|
|
22
24
|
background-color: transparent;
|
|
23
25
|
border: none;
|
|
24
26
|
border-radius: 8px;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DirectiveResult } from 'lit/directive.js';
|
|
2
2
|
import { StyledElement, StyleMap } from '../internals/StyledElement.js';
|
|
3
3
|
import { SidebarPermissionGuard } from '../layouts/SidebarPermission.js';
|
|
4
|
-
type ElementParts = 'host' | 'base' | 'icon' | 'label';
|
|
4
|
+
type ElementParts = 'host' | 'base' | 'icon' | 'label' | 'count';
|
|
5
5
|
/** 링크 항목 디폴트 타입 */
|
|
6
6
|
export interface SidebarLinkConfig extends SidebarPermissionGuard {
|
|
7
7
|
type: 'link';
|
|
@@ -30,6 +30,14 @@ export interface SidebarLinkConfig extends SidebarPermissionGuard {
|
|
|
30
30
|
* 다른 문서로」를 표현할 수 없다. 그것이 종전에 유일한 탈출구였고, 그래서 부족했다.
|
|
31
31
|
*/
|
|
32
32
|
target?: '_self' | '_blank';
|
|
33
|
+
/**
|
|
34
|
+
* A count shown at the end of the item — work waiting behind it (items to review, unpaid
|
|
35
|
+
* invoices). Omit it, or pass `undefined`, for items with nothing waiting; show numbers only
|
|
36
|
+
* where they ask for action, or the sidebar turns into a dashboard.
|
|
37
|
+
*
|
|
38
|
+
* The value can also be set later without rebuilding the layout: `app.setNavCount(href, n)`.
|
|
39
|
+
*/
|
|
40
|
+
count?: number | string;
|
|
33
41
|
}
|
|
34
42
|
/**
|
|
35
43
|
* SidebarLink 컴포넌트는 계층형 네비게이션 아이템을 표시합니다.
|
|
@@ -55,6 +63,8 @@ export declare class SidebarLink extends StyledElement<ElementParts> {
|
|
|
55
63
|
navigate?: 'router' | 'document';
|
|
56
64
|
/** 앵커 target — 새 탭으로 열 때 */
|
|
57
65
|
target?: '_self' | '_blank';
|
|
66
|
+
/** 항목 끝의 건수 — 처리할 일이 있을 때만. `undefined`·빈 문자열이면 그리지 않는다. */
|
|
67
|
+
count?: number | string;
|
|
58
68
|
render(): import('lit-html').TemplateResult<1>;
|
|
59
69
|
}
|
|
60
70
|
export {};
|
|
@@ -33,6 +33,7 @@ var u = class extends n {
|
|
|
33
33
|
<span part="label" ?compact=${this.compact}>
|
|
34
34
|
${this.label}
|
|
35
35
|
</span>
|
|
36
|
+
${this.count === void 0 || this.count === null || this.count === "" ? s : o`<span class="count" part="count" ?compact=${this.compact}>${this.count}</span>`}
|
|
36
37
|
${this.target === "_blank" ? o`<span class="new-tab-hint">${r().opensInNewTab}</span>` : s}
|
|
37
38
|
</div>
|
|
38
39
|
</u-link>
|
|
@@ -42,6 +43,6 @@ var u = class extends n {
|
|
|
42
43
|
t([l({
|
|
43
44
|
type: Boolean,
|
|
44
45
|
reflect: !0
|
|
45
|
-
}), e("design:type", Object)], u.prototype, "selected", void 0), t([l({ type: Boolean }), e("design:type", Object)], u.prototype, "compact", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "icon", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "lib", void 0), t([l({ type: String }), e("design:type", Object)], u.prototype, "label", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "href", void 0), t([l({ type: String }), e("design:type", Object)], u.prototype, "pattern", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "navigate", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "target", void 0), u = t([c("u-sidebar-link")], u);
|
|
46
|
+
}), e("design:type", Object)], u.prototype, "selected", void 0), t([l({ type: Boolean }), e("design:type", Object)], u.prototype, "compact", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "icon", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "lib", void 0), t([l({ type: String }), e("design:type", Object)], u.prototype, "label", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "href", void 0), t([l({ type: String }), e("design:type", Object)], u.prototype, "pattern", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "navigate", void 0), t([l({ type: String }), e("design:type", String)], u.prototype, "target", void 0), t([l(), e("design:type", Object)], u.prototype, "count", void 0), u = t([c("u-sidebar-link")], u);
|
|
46
47
|
//#endregion
|
|
47
48
|
export { u as SidebarLink };
|
|
@@ -11,7 +11,7 @@ var t = e`
|
|
|
11
11
|
display: block;
|
|
12
12
|
color: var(--app-sidebar-fg, var(--u-txt-color, #212121));
|
|
13
13
|
background-color: transparent;
|
|
14
|
-
border-radius: 8px;
|
|
14
|
+
border-radius: var(--app-sidebar-item-radius, 8px);
|
|
15
15
|
transition: all var(--u-duration-normal, 220ms) ease;
|
|
16
16
|
cursor: pointer;
|
|
17
17
|
}
|
|
@@ -22,21 +22,25 @@ var t = e`
|
|
|
22
22
|
:host([selected]) {
|
|
23
23
|
color: var(--app-sidebar-active-fg, var(--u-txt-color-inverse, #FFFFFF));
|
|
24
24
|
background-color: var(--link-active-bg);
|
|
25
|
-
box-shadow: 0 1px 3px var(--u-shadow-color-weak, rgba(0, 0, 0, 0.08));
|
|
25
|
+
box-shadow: var(--app-sidebar-active-shadow, 0 1px 3px var(--u-shadow-color-weak, rgba(0, 0, 0, 0.08)));
|
|
26
26
|
}
|
|
27
27
|
:host([selected]:hover) {
|
|
28
28
|
color: var(--app-sidebar-active-fg, var(--u-txt-color-inverse, #FFFFFF));
|
|
29
29
|
background-color: color-mix(in srgb, var(--link-active-bg) 85%, black);
|
|
30
|
-
box-shadow: 0 2px 6px var(--u-shadow-color-normal, rgba(0, 0, 0, 0.12));
|
|
30
|
+
box-shadow: var(--app-sidebar-active-shadow-hover, 0 2px 6px var(--u-shadow-color-normal, rgba(0, 0, 0, 0.12)));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.container {
|
|
34
|
+
position: relative;
|
|
34
35
|
display: flex;
|
|
35
36
|
flex-direction: row;
|
|
36
37
|
align-items: center;
|
|
37
38
|
justify-content: flex-start;
|
|
38
|
-
gap: 12px;
|
|
39
|
-
padding: 8px 12px;
|
|
39
|
+
gap: var(--app-sidebar-item-gap, 12px);
|
|
40
|
+
padding: var(--app-sidebar-item-padding, 8px 12px);
|
|
41
|
+
/* 호스트 하한(--u-target-size, 미설정 = 0). */
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
min-height: var(--u-target-size, 0px);
|
|
40
44
|
}
|
|
41
45
|
.container[compact] {
|
|
42
46
|
justify-content: center;
|
|
@@ -47,12 +51,24 @@ var t = e`
|
|
|
47
51
|
u-icon {
|
|
48
52
|
flex-shrink: 0;
|
|
49
53
|
color: var(--link-icon-color, inherit);
|
|
50
|
-
font-size: 20px;
|
|
54
|
+
font-size: var(--app-sidebar-icon-size, 20px);
|
|
51
55
|
}
|
|
52
56
|
/* Selected always wins over any consumer-supplied --link-icon-color — a
|
|
53
57
|
* high-contrast icon on the active background matters more than a brand tint. */
|
|
54
58
|
:host([selected]) u-icon {
|
|
55
|
-
color: var(--app-sidebar-active-fg, var(--u-txt-color-inverse, #FFFFFF));
|
|
59
|
+
color: var(--app-sidebar-active-icon-color, var(--app-sidebar-active-fg, var(--u-txt-color-inverse, #FFFFFF)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* 활성 표시 막대 — 시작 변(좌→우 언어에서 왼쪽). 기본 색은 활성 면과 같아 보이지 않는다(기본 외형 불변).
|
|
63
|
+
밝은 활성 면처럼 면 색만으로 현재 위치를 말하지 않는 셸이 위치를 한 번 더 표시하는 자리다. */
|
|
64
|
+
:host([selected]) .container::before {
|
|
65
|
+
content: '';
|
|
66
|
+
position: absolute;
|
|
67
|
+
inset-block: 6px;
|
|
68
|
+
inset-inline-start: 0;
|
|
69
|
+
width: var(--app-sidebar-active-indicator-width, 3px);
|
|
70
|
+
border-radius: var(--u-radius-pill, 9999px);
|
|
71
|
+
background: var(--app-sidebar-active-indicator-color, var(--app-sidebar-active-bg, var(--u-primary-color, #1976D2)));
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
/*
|
|
@@ -87,6 +103,26 @@ var t = e`
|
|
|
87
103
|
white-space: nowrap;
|
|
88
104
|
border: 0;
|
|
89
105
|
}
|
|
106
|
+
|
|
107
|
+
/* 건수 — 라벨 끝, 숫자는 표 숫자처럼 자릿수가 흔들리지 않게. 접힌 사이드바에서는 라벨처럼
|
|
108
|
+
시각적으로만 숨긴다(접근 가능한 이름에는 남는다). */
|
|
109
|
+
.count {
|
|
110
|
+
flex: none;
|
|
111
|
+
margin-inline-start: auto;
|
|
112
|
+
font-size: var(--u-text-overline-size, 11px);
|
|
113
|
+
font-weight: var(--u-text-caption-weight, 400);
|
|
114
|
+
font-variant-numeric: tabular-nums;
|
|
115
|
+
opacity: 0.8;
|
|
116
|
+
}
|
|
117
|
+
.count[compact] {
|
|
118
|
+
position: absolute;
|
|
119
|
+
width: 1px;
|
|
120
|
+
height: 1px;
|
|
121
|
+
margin: -1px;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
clip-path: inset(50%);
|
|
124
|
+
white-space: nowrap;
|
|
125
|
+
}
|
|
90
126
|
`;
|
|
91
127
|
//#endregion
|
|
92
128
|
export { t as styles };
|
|
@@ -14,19 +14,16 @@ var t = e`
|
|
|
14
14
|
padding: 8px 12px 4px;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
WCAG AA(4.5:1)에 미달한다. 현행 두 값은 6.19:1 · 4.61:1 로 통과한다.
|
|
21
|
-
⇒ 역할 층에 AA 를 넘는 보조 텍스트 단이 생기면 그때 옮긴다. */
|
|
17
|
+
/* 섹션 제목·부제는 본문보다 약한 두 단이다 — 제목 = 보조 글자(--u-txt-color-weak),
|
|
18
|
+
부제 = 3단 글자(--u-txt-color-weaker, components 2.0). 종전에는 역할 층에 AA 를 넘는
|
|
19
|
+
3단이 없어 팔레트(neutral-700/600)를 직접 읽었고, 그래서 테마가 이 두 줄에 닿지 않았다. */
|
|
22
20
|
/*
|
|
23
|
-
* ★크기·굵기·자간은 스케일의 단으로 옮겼다(용도 배정).
|
|
24
|
-
* 대한 것이고 그대로 유효하다: 두 색은 여전히 팔레트를 직접 읽는다.
|
|
21
|
+
* ★크기·굵기·자간은 스케일의 단으로 옮겼다(용도 배정).
|
|
25
22
|
* ⚠ overline 에 대문자 변환을 붙이지 않는다 — 이 줄에 ellipsis 가 있어 잘림이 늘고,
|
|
26
23
|
* 섹션 이름은 소비자가 쓴 글자다(CJK 에는 효과도 없다).
|
|
27
24
|
*/
|
|
28
25
|
.title {
|
|
29
|
-
color: var(--u-
|
|
26
|
+
color: var(--u-txt-color-weak, #616161);
|
|
30
27
|
font-size: var(--u-text-overline-size, 11px);
|
|
31
28
|
font-weight: var(--u-text-overline-weight, 700);
|
|
32
29
|
line-height: var(--u-text-overline-leading, 1.45);
|
|
@@ -37,7 +34,7 @@ var t = e`
|
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
.subtitle {
|
|
40
|
-
color: var(--u-
|
|
37
|
+
color: var(--u-txt-color-weaker, #757575);
|
|
41
38
|
font-size: var(--u-text-caption-size, 12px);
|
|
42
39
|
font-weight: var(--u-text-caption-weight, 400);
|
|
43
40
|
line-height: var(--u-text-caption-leading, 1.5);
|
|
@@ -92,7 +92,7 @@ var u = class extends n {
|
|
|
92
92
|
</div>
|
|
93
93
|
<div class="actions" part="actions">
|
|
94
94
|
<slot name="actions">
|
|
95
|
-
<u-button
|
|
95
|
+
<u-button appearance="plain" ?disabled=${this.active === 0} @click=${() => this.back()}>
|
|
96
96
|
${e.wizardBack}
|
|
97
97
|
</u-button>
|
|
98
98
|
${t ? o : a`
|
|
@@ -9,6 +9,11 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
9
9
|
state: SidebarState;
|
|
10
10
|
/** 사이드바 레이아웃 설정 */
|
|
11
11
|
config?: SidebarLayoutConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Live counts for link items, keyed by the item's `href` — overrides the static `count` of the
|
|
14
|
+
* item config. Set through `app.setNavCount(href, value)`; assigning a new object re-renders.
|
|
15
|
+
*/
|
|
16
|
+
counts: Record<string, number | string | undefined>;
|
|
12
17
|
/** 크롬 문자열(토글 버튼 접근성 라벨 등) 로케일 — 이 요소만 다른 언어로 쓸 때. 비우면 `Locale` 의 활성 로케일 */
|
|
13
18
|
locale: string;
|
|
14
19
|
progressBarEl: UProgressBar;
|
|
@@ -59,7 +59,7 @@ var j = class extends i {
|
|
|
59
59
|
C = this;
|
|
60
60
|
}
|
|
61
61
|
constructor(...t) {
|
|
62
|
-
super(...t), this.state = "default", this.locale = "", this.hasOverlay = !1, this.hasNotice = !1, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++T}`, this.isMatchedLink = (e) => !this.context || !e ? !1 : (e = typeof e == "string" ? new URLPattern(e, window.location.origin) : e, e.test(this.context.path, window.location.origin)), this.handleToggleButtonClick = () => {
|
|
62
|
+
super(...t), this.state = "default", this.counts = {}, this.locale = "", this.hasOverlay = !1, this.hasNotice = !1, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++T}`, this.isMatchedLink = (e) => !this.context || !e ? !1 : (e = typeof e == "string" ? new URLPattern(e, window.location.origin) : e, e.test(this.context.path, window.location.origin)), this.handleToggleButtonClick = () => {
|
|
63
63
|
let t = this.screenSize ?? e.screen ?? "large";
|
|
64
64
|
t === "large" ? this.state = this.state === "default" ? "slim" : "default" : t === "medium" ? this.state = this.state === "slim" ? "modal" : "slim" : t === "small" ? this.state = this.state === "mobile" ? "mobile-open" : "mobile" : console.warn("Unknown screen size:", t);
|
|
65
65
|
}, this.handleBackdropClick = () => {
|
|
@@ -171,7 +171,7 @@ var j = class extends i {
|
|
|
171
171
|
<span class="title">
|
|
172
172
|
${this.config.title}
|
|
173
173
|
</span>
|
|
174
|
-
<u-button class="toggler"
|
|
174
|
+
<u-button class="toggler" appearance="plain"
|
|
175
175
|
aria-label=${a(this.locale || void 0).toggleMobileMenu}
|
|
176
176
|
@click=${this.handleToggleButtonClick}>
|
|
177
177
|
<u-icon
|
|
@@ -189,7 +189,7 @@ var j = class extends i {
|
|
|
189
189
|
<span class="title" ?hidden=${this.state === "slim"}>
|
|
190
190
|
${this.config.title}
|
|
191
191
|
</span>
|
|
192
|
-
<u-button class="toggler"
|
|
192
|
+
<u-button class="toggler" appearance="plain"
|
|
193
193
|
aria-label=${a(this.locale || void 0).toggleSidebar}
|
|
194
194
|
@click=${this.handleToggleButtonClick}>
|
|
195
195
|
<u-icon
|
|
@@ -227,7 +227,7 @@ var j = class extends i {
|
|
|
227
227
|
</main>
|
|
228
228
|
|
|
229
229
|
<div class="overlay ${this.hasOverlay ? "" : "empty"}" part="overlay">
|
|
230
|
-
<u-button class="overlay-close" part="overlay-close"
|
|
230
|
+
<u-button class="overlay-close" part="overlay-close" appearance="plain"
|
|
231
231
|
aria-label=${a(this.locale || void 0).detailClose}
|
|
232
232
|
@click=${this.handleOverlayClose}>
|
|
233
233
|
<u-icon lib=${this.icon("lib")} name=${this.icon("overlayClose")}></u-icon>
|
|
@@ -272,6 +272,7 @@ var j = class extends i {
|
|
|
272
272
|
.pattern="${e.pattern}"
|
|
273
273
|
.navigate="${e.navigate}"
|
|
274
274
|
.target="${e.target}"
|
|
275
|
+
.count=${e.href in this.counts ? this.counts[e.href] : e.count}
|
|
275
276
|
.styles="${e.styles}"
|
|
276
277
|
></u-sidebar-link>
|
|
277
278
|
`;
|
|
@@ -377,6 +378,6 @@ var j = class extends i {
|
|
|
377
378
|
r([f({
|
|
378
379
|
type: String,
|
|
379
380
|
reflect: !0
|
|
380
|
-
}), n("design:type", Object)], j.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], j.prototype, "config", void 0), r([f({ type: String }), n("design:type", Object)], j.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], j.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], j.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], j.prototype, "hasNotice", void 0), r([m(), n("design:type", Object)], j.prototype, "context", void 0), j = C = r([d("u-sidebar-layout")], j);
|
|
381
|
+
}), n("design:type", Object)], j.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], j.prototype, "config", void 0), r([f({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], j.prototype, "counts", void 0), r([f({ type: String }), n("design:type", Object)], j.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], j.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], j.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], j.prototype, "hasNotice", void 0), r([m(), n("design:type", Object)], j.prototype, "context", void 0), j = C = r([d("u-sidebar-layout")], j);
|
|
381
382
|
//#endregion
|
|
382
383
|
export { j as SidebarLayout };
|
|
@@ -32,6 +32,12 @@ var t = e`
|
|
|
32
32
|
height: 24px;
|
|
33
33
|
width: auto;
|
|
34
34
|
object-fit: contain;
|
|
35
|
+
/* 호스트 하한(--u-target-size) — 그림은 24px 그대로, 누르는 상자만 여백으로 넓히고 같은 만큼 되돌려 배치는 그대로.
|
|
36
|
+
미설정이면 여백 0(종전). */
|
|
37
|
+
--_logo-pad: max(0px, calc((var(--u-target-size, 0px) - 24px) / 2));
|
|
38
|
+
box-sizing: content-box;
|
|
39
|
+
padding: var(--_logo-pad);
|
|
40
|
+
margin: calc(-1 * var(--_logo-pad));
|
|
35
41
|
}
|
|
36
42
|
/* 로고를 감싼 링크 — 인라인 기준선 여백이 생기지 않게 로고 상자에 딱 맞춘다. */
|
|
37
43
|
.logo-link {
|
|
@@ -58,14 +64,14 @@ var t = e`
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/*
|
|
61
|
-
* 토글러의 "테두리·배경 없음"은 마크업의
|
|
67
|
+
* 토글러의 "테두리·배경 없음"은 마크업의 appearance="plain" 이 낸다.
|
|
62
68
|
*
|
|
63
69
|
* ★여기에 border/background 를 적는 것으로는 부족했다 — 이 규칙은 <u-button> **호스트**에만
|
|
64
70
|
* 닿고, 테두리를 실제로 그리는 것은 그 섀도 루트 안의 <button> 이다(solid 기본값이
|
|
65
71
|
* --btn-border-color: var(--btn-color) 로 그린다). 호스트만 재면 0px 이라 보이지도 않았다.
|
|
66
72
|
* variant 를 명시해 컴포넌트가 스스로 투명하게 그리도록 한다.
|
|
67
73
|
*
|
|
68
|
-
* color: inherit 는 유지한다 — 헤더 색을 따라가게 하려는 의도이고
|
|
74
|
+
* color: inherit 는 유지한다 — 헤더 색을 따라가게 하려는 의도이고 plain 의 기본 전경
|
|
69
75
|
* (--u-txt-color)보다 이 레이아웃의 뜻이 앞선다.
|
|
70
76
|
*/
|
|
71
77
|
.toggler {
|
|
@@ -114,7 +120,12 @@ var t = e`
|
|
|
114
120
|
flex-direction: column;
|
|
115
121
|
justify-content: space-between;
|
|
116
122
|
gap: 8px;
|
|
117
|
-
|
|
123
|
+
/* 높이는 백분율이 아니라 flex 의 stretch 로 받는다 — 셸의 높이가 «부모의 확정 높이» 가 아니라
|
|
124
|
+
max-height 상한에서 나오면 height: 100% 는 풀리지 않아(auto) 사이드바가 메뉴 길이로 커지고,
|
|
125
|
+
셸의 overflow: hidden 이 아래쪽 계정 영역을 잘라 닿을 수 없게 된다. 늘어난 항목의 높이는
|
|
126
|
+
그 경우에도 셸의 (상한으로 잘린) 높이다. .main-region 과 같은 해법이다. 떠 있는 상태
|
|
127
|
+
(modal · mobile*)는 절대배치라 top/bottom 으로 채운다. */
|
|
128
|
+
min-height: 0;
|
|
118
129
|
/* --u-panel-bg-color 가 아니라 --u-bg-color-raised — 사이드바는 카드·메뉴류의
|
|
119
130
|
"떠 있는 패널"이 아니라 상시 크롬이고, 그 구분이 정확히 두 토큰이 갈리는 지점이다
|
|
120
131
|
(light.css 293~298행 참조). 미설정 시 페이지(--u-bg-color)와 같은 흰색으로 앉아
|
|
@@ -140,11 +151,13 @@ var t = e`
|
|
|
140
151
|
width: var(--app-sidebar-width, 260px);
|
|
141
152
|
position: absolute;
|
|
142
153
|
top: 0;
|
|
154
|
+
bottom: 0;
|
|
143
155
|
left: 0;
|
|
144
156
|
}
|
|
145
157
|
.sidebar[state="mobile"] {
|
|
146
158
|
position: absolute;
|
|
147
159
|
top: 0;
|
|
160
|
+
bottom: 0;
|
|
148
161
|
left: 0;
|
|
149
162
|
right: 0;
|
|
150
163
|
transform: translateY(-100%);
|
|
@@ -152,6 +165,7 @@ var t = e`
|
|
|
152
165
|
.sidebar[state="mobile-open"] {
|
|
153
166
|
position: absolute;
|
|
154
167
|
top: 0;
|
|
168
|
+
bottom: 0;
|
|
155
169
|
left: 0;
|
|
156
170
|
right: 0;
|
|
157
171
|
transform: translateY(0);
|
|
@@ -188,6 +202,8 @@ var t = e`
|
|
|
188
202
|
/* Sidebar Main Menu */
|
|
189
203
|
.sidebar-main {
|
|
190
204
|
flex: 1;
|
|
205
|
+
/* 열 방향 flex 항목의 자동 최소 높이는 내용 길이다 — 0 으로 풀어야 남는 높이 안에서 스크롤한다. */
|
|
206
|
+
min-height: 0;
|
|
191
207
|
display: flex;
|
|
192
208
|
flex-direction: column;
|
|
193
209
|
gap: 4px;
|
|
@@ -237,12 +253,14 @@ var t = e`
|
|
|
237
253
|
flex-direction: column;
|
|
238
254
|
}
|
|
239
255
|
|
|
256
|
+
/* The page sits on the canvas (the application background), not on a surface: cards and tables
|
|
257
|
+
placed on it keep \`--u-bg-color\` as their own face. */
|
|
240
258
|
.main {
|
|
241
259
|
position: relative;
|
|
242
260
|
flex: 1 1 auto;
|
|
243
261
|
min-height: 0;
|
|
244
|
-
padding: var(--u-space-3xl, 32px);
|
|
245
|
-
background: var(--u-bg-color, #
|
|
262
|
+
padding: var(--app-main-padding, var(--u-space-3xl, 32px));
|
|
263
|
+
background: var(--u-canvas-bg-color, #F5F5F5);
|
|
246
264
|
overflow: auto;
|
|
247
265
|
outline: none;
|
|
248
266
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ActionBar as ActionBarElement } from '@iyulab/modern-app/dist/components/ActionBar.js';
|
|
3
3
|
|
|
4
|
+
type ActionBarOwnProps = Omit<ActionBarElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const ActionBar: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<ActionBarElement>, never>
|
|
7
|
+
Partial<Omit<ActionBarElement, Exclude<keyof React.HTMLAttributes<ActionBarElement>, keyof ActionBarOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<ActionBarElement>, keyof ActionBarOwnProps | never>
|
|
7
9
|
& React.RefAttributes<ActionBarElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { EmptyState as EmptyStateElement } from '@iyulab/modern-app/dist/components/EmptyState.js';
|
|
3
3
|
|
|
4
|
+
type EmptyStateOwnProps = Omit<EmptyStateElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const EmptyState: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<EmptyStateElement>, never>
|
|
7
|
+
Partial<Omit<EmptyStateElement, Exclude<keyof React.HTMLAttributes<EmptyStateElement>, keyof EmptyStateOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<EmptyStateElement>, keyof EmptyStateOwnProps | never>
|
|
7
9
|
& React.RefAttributes<EmptyStateElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
package/dist/react/GroupBox.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { GroupBox as GroupBoxElement } from '@iyulab/modern-app/dist/components/GroupBox.js';
|
|
3
3
|
|
|
4
|
+
type GroupBoxOwnProps = Omit<GroupBoxElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const GroupBox: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<GroupBoxElement>, never>
|
|
7
|
+
Partial<Omit<GroupBoxElement, Exclude<keyof React.HTMLAttributes<GroupBoxElement>, keyof GroupBoxOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<GroupBoxElement>, keyof GroupBoxOwnProps | never>
|
|
7
9
|
& React.RefAttributes<GroupBoxElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { InfoField as InfoFieldElement } from '@iyulab/modern-app/dist/components/InfoField.js';
|
|
3
3
|
|
|
4
|
+
type InfoFieldOwnProps = Omit<InfoFieldElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const InfoField: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<InfoFieldElement>, never>
|
|
7
|
+
Partial<Omit<InfoFieldElement, Exclude<keyof React.HTMLAttributes<InfoFieldElement>, keyof InfoFieldOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<InfoFieldElement>, keyof InfoFieldOwnProps | never>
|
|
7
9
|
& React.RefAttributes<InfoFieldElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { InfoSection as InfoSectionElement } from '@iyulab/modern-app/dist/components/InfoSection.js';
|
|
3
3
|
|
|
4
|
+
type InfoSectionOwnProps = Omit<InfoSectionElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const InfoSection: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<InfoSectionElement>, never>
|
|
7
|
+
Partial<Omit<InfoSectionElement, Exclude<keyof React.HTMLAttributes<InfoSectionElement>, keyof InfoSectionOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<InfoSectionElement>, keyof InfoSectionOwnProps | never>
|
|
7
9
|
& React.RefAttributes<InfoSectionElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { MasterDetailLayout as MasterDetailLayoutElement } from '@iyulab/modern-app/dist/components/MasterDetailLayout.js';
|
|
3
3
|
|
|
4
|
+
type MasterDetailLayoutOwnProps = Omit<MasterDetailLayoutElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const MasterDetailLayout: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<MasterDetailLayoutElement>, 'onDetailClose'>
|
|
7
|
+
Partial<Omit<MasterDetailLayoutElement, Exclude<keyof React.HTMLAttributes<MasterDetailLayoutElement>, keyof MasterDetailLayoutOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<MasterDetailLayoutElement>, keyof MasterDetailLayoutOwnProps | 'onDetailClose'>
|
|
7
9
|
& React.RefAttributes<MasterDetailLayoutElement>
|
|
8
10
|
& {
|
|
9
11
|
onDetailClose?: (event: CustomEvent) => void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { PageHeader as PageHeaderElement } from '@iyulab/modern-app/dist/components/PageHeader.js';
|
|
3
3
|
|
|
4
|
+
type PageHeaderOwnProps = Omit<PageHeaderElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const PageHeader: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<PageHeaderElement>, never>
|
|
7
|
+
Partial<Omit<PageHeaderElement, Exclude<keyof React.HTMLAttributes<PageHeaderElement>, keyof PageHeaderOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<PageHeaderElement>, keyof PageHeaderOwnProps | never>
|
|
7
9
|
& React.RefAttributes<PageHeaderElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { SidebarButton as SidebarButtonElement } from '@iyulab/modern-app/dist/components/SidebarButton.js';
|
|
3
3
|
|
|
4
|
+
type SidebarButtonOwnProps = Omit<SidebarButtonElement, keyof HTMLElement>;
|
|
5
|
+
|
|
4
6
|
export declare const SidebarButton: React.ForwardRefExoticComponent<
|
|
5
|
-
Omit<
|
|
6
|
-
& Omit<React.HTMLAttributes<SidebarButtonElement>, never>
|
|
7
|
+
Partial<Omit<SidebarButtonElement, Exclude<keyof React.HTMLAttributes<SidebarButtonElement>, keyof SidebarButtonOwnProps>>>
|
|
8
|
+
& Omit<React.HTMLAttributes<SidebarButtonElement>, keyof SidebarButtonOwnProps | never>
|
|
7
9
|
& React.RefAttributes<SidebarButtonElement>
|
|
8
10
|
& {
|
|
9
11
|
}
|
|
@@ -33,6 +33,13 @@ export interface AppConfig {
|
|
|
33
33
|
* @default '/'
|
|
34
34
|
*/
|
|
35
35
|
basepath?: string;
|
|
36
|
+
/**
|
|
37
|
+
* 라우트를 주소의 어디에서 읽는가 — `@iyulab/router` 의 `mode` 로 그대로 전달된다.
|
|
38
|
+
* - `'history'`: 경로(`/app/orders/7`). 서버가 앱의 모든 경로에 같은 문서를 돌려줘야 새로고침·딥링크가 동작한다.
|
|
39
|
+
* - `'hash'`: `#` 뒤(`/app/#/orders/7`). 정적 호스팅에서 서버 설정 없이 딥링크가 동작한다.
|
|
40
|
+
* @default 'history'
|
|
41
|
+
*/
|
|
42
|
+
routerMode?: 'history' | 'hash';
|
|
36
43
|
/**
|
|
37
44
|
* 현재 애플리케이션의 기본 아이콘 경로 설정
|
|
38
45
|
* @default '/assets/icons/'
|
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.
|
|
4
|
+
"version": "0.34.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"preversion": "node -e \"if(require('fs').existsSync('../../scripts/preversion-check.mjs'))require('child_process').execFileSync('node',['../../scripts/preversion-check.mjs'],{stdio:'inherit'})\"",
|
|
53
|
+
"pretest": "node -e \"if(require('fs').existsSync('../../scripts/sibling-dist-check.js'))require('child_process').execFileSync('node',['../../scripts/sibling-dist-check.js'],{stdio:'inherit'})\"",
|
|
53
54
|
"test": "vitest run",
|
|
54
55
|
"test:watch": "vitest",
|
|
55
56
|
"start": "vite",
|
|
@@ -59,12 +60,12 @@
|
|
|
59
60
|
"test:react-types": "tsc -p tsconfig.react-smoke.json"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@iyulab/router": "^0.
|
|
63
|
+
"@iyulab/router": "^0.19.0",
|
|
63
64
|
"i18next": "^26.3.6",
|
|
64
65
|
"lit": "^3.3.3"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
67
|
-
"@iyulab/components": ">=
|
|
68
|
+
"@iyulab/components": ">=2.0.0",
|
|
68
69
|
"@lit/react": "^1.0.8",
|
|
69
70
|
"react": "^18.0.0 || ^19.0.0"
|
|
70
71
|
},
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
}
|
|
78
79
|
},
|
|
79
80
|
"devDependencies": {
|
|
80
|
-
"@iyulab/components": ">=
|
|
81
|
+
"@iyulab/components": ">=2.0.0",
|
|
81
82
|
"@lit/react": "^1.0.8",
|
|
82
83
|
"@types/node": "^26.1.1",
|
|
83
84
|
"@types/react": "^19.2.14",
|
|
@@ -10,6 +10,9 @@ interface AppConfig {
|
|
|
10
10
|
/** Base path for all routes. Default: '/' */
|
|
11
11
|
basepath?: string;
|
|
12
12
|
|
|
13
|
+
/** Where the route lives: 'history' = the path, 'hash' = after `#` (static hosting, no server fallback). Default: 'history' */
|
|
14
|
+
routerMode?: 'history' | 'hash';
|
|
15
|
+
|
|
13
16
|
/** Base URL for icon assets. Default: '/assets/icons/' */
|
|
14
17
|
iconBasepath?: string;
|
|
15
18
|
|
|
@@ -7,8 +7,8 @@ destructive ones separated on the trailing edge.
|
|
|
7
7
|
|
|
8
8
|
```html
|
|
9
9
|
<u-action-bar sticky>
|
|
10
|
-
<u-button
|
|
11
|
-
<u-button
|
|
10
|
+
<u-button appearance="solid">Save</u-button>
|
|
11
|
+
<u-button appearance="outlined">Cancel</u-button>
|
|
12
12
|
<u-button slot="danger" color="danger">Delete</u-button>
|
|
13
13
|
</u-action-bar>
|
|
14
14
|
```
|
|
@@ -31,6 +31,7 @@ a left edge without any consumer CSS.
|
|
|
31
31
|
|----------|------|---------|---------|-------------|
|
|
32
32
|
| `title` | `string` | `''` | | Header title |
|
|
33
33
|
| `meta` | `string` | — | | Secondary text after the title — a count, progress or short status (`3 items`, `2/5 done`), one step below the title: body size and weight, weak color, tabular figures. It sits inside the heading, so the heading's accessible name includes it. Not drawn without `title` |
|
|
34
|
+
| `description` | `string` | — | | One line under the title — what the box holds or what its numbers are based on. Caption size, weak color. Opens the header on its own |
|
|
34
35
|
| `divider` | `boolean` | `false` | | Rule between header and body. Off by default — many rules make a screen noisy |
|
|
35
36
|
| `flush` | `boolean` | `false` | | Remove body padding, for tables and lists that draw their own edges |
|
|
36
37
|
| `level` | `2|3|4|5|6` | `3` | ✓ | Heading level of the title in the document outline. Set `2` for a box directly under `u-page-header` (the page's `h1`), so the outline does not skip a level. Semantics only — the title looks the same at every level |
|
|
@@ -41,6 +42,7 @@ a left edge without any consumer CSS.
|
|
|
41
42
|
|------|-------------|
|
|
42
43
|
| `header` · `title` · `actions` | Header row |
|
|
43
44
|
| `meta` | Secondary text inside the title |
|
|
45
|
+
| `description` | One-line description under the title |
|
|
44
46
|
| `body` | Body wrapper |
|
|
45
47
|
|
|
46
48
|
## CSS Custom Properties
|
|
@@ -52,6 +54,7 @@ a left edge without any consumer CSS.
|
|
|
52
54
|
| `--u-text-subtitle-leading` | Title line height |
|
|
53
55
|
| `--u-text-subtitle-tracking` | Title letter spacing |
|
|
54
56
|
| `--u-text-body-size` / `--u-text-body-weight` / `--u-txt-color-weak` | `meta` size, weight and color |
|
|
57
|
+
| `--u-text-caption-size` / `-weight` / `-leading` | `description` typography |
|
|
55
58
|
| `--u-bg-color-raised` | Surface background |
|
|
56
59
|
| `--u-border-color-weak` | Border and header divider color |
|
|
57
60
|
| `--u-radius-2xl` | Corner radius |
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
**Tag:** `u-page-header`
|
|
4
4
|
|
|
5
|
-
The same skeleton at the top of every LOB screen: optional back link,
|
|
6
|
-
a status slot, and an actions slot.
|
|
5
|
+
The same skeleton at the top of every LOB screen: optional back link, an eyebrow (menu group or
|
|
6
|
+
breadcrumb), title, a one-line description, a status slot, and an actions slot.
|
|
7
7
|
|
|
8
8
|
Hand-rolling this per screen is where a product stops looking like one product — title sizes
|
|
9
9
|
drift, action alignment goes slightly off, and the narrow-viewport rule gets forgotten on the
|
|
10
10
|
screens nobody opens.
|
|
11
11
|
|
|
12
12
|
```html
|
|
13
|
-
<u-page-header title="Order G-2026-I-0629" subtitle="Received 2026-02-24" back="/orders">
|
|
13
|
+
<u-page-header eyebrow="Sales" title="Order G-2026-I-0629" subtitle="Received 2026-02-24" back="/orders">
|
|
14
14
|
<span slot="status"><u-tag color="success">Confirmed</u-tag></span>
|
|
15
15
|
<span slot="actions"><u-button>Edit</u-button></span>
|
|
16
16
|
</u-page-header>
|
|
@@ -20,6 +20,7 @@ screens nobody opens.
|
|
|
20
20
|
|
|
21
21
|
| Name | Description |
|
|
22
22
|
|------|-------------|
|
|
23
|
+
| `eyebrow` | Replaces the `eyebrow` text — e.g. breadcrumb links |
|
|
23
24
|
| `status` | Badge or tag beside the title |
|
|
24
25
|
| `actions` | Buttons on the trailing edge |
|
|
25
26
|
|
|
@@ -28,7 +29,8 @@ screens nobody opens.
|
|
|
28
29
|
| Property | Type | Default | Reflect | Description |
|
|
29
30
|
|----------|------|---------|---------|-------------|
|
|
30
31
|
| `title` | `string` | `''` | | Page title |
|
|
31
|
-
| `subtitle` | `string` | — | |
|
|
32
|
+
| `subtitle` | `string` | — | | One-line description under the title — what the screen is for. Body size, weak color |
|
|
33
|
+
| `eyebrow` | `string` | — | | Small line above the title — the menu group or parent path. Overline step; the `eyebrow` slot replaces it |
|
|
32
34
|
| `back` | `string` | — | | Back-link href; renders a back affordance before the title |
|
|
33
35
|
| `backLabel` | `string` | `''` | | Back-link text (`back-label`). Empty falls back to the locale registry, then English |
|
|
34
36
|
| `locale` | `string` | `''` | | Locale tag override for built-in strings |
|
|
@@ -38,7 +40,8 @@ screens nobody opens.
|
|
|
38
40
|
| Part | Description |
|
|
39
41
|
|------|-------------|
|
|
40
42
|
| `back` | Back link |
|
|
41
|
-
| `heading` |
|
|
43
|
+
| `heading` | Eyebrow + title + subtitle wrapper |
|
|
44
|
+
| `eyebrow` | Eyebrow row (hidden when empty) |
|
|
42
45
|
| `title` · `subtitle` | Text elements |
|
|
43
46
|
| `status` · `actions` | Slot wrappers |
|
|
44
47
|
|
|
@@ -48,14 +51,15 @@ screens nobody opens.
|
|
|
48
51
|
|----------|-------------|
|
|
49
52
|
| `--u-text-display-size` / `-weight` / `-leading` / `-tracking` | Title typography |
|
|
50
53
|
| `--u-txt-color` | Title color |
|
|
51
|
-
| `--u-text-
|
|
52
|
-
| `--u-
|
|
54
|
+
| `--u-text-body-size` / `-weight` / `-leading` | Subtitle typography |
|
|
55
|
+
| `--u-text-overline-size` / `-weight` / `-leading` / `-tracking` | Eyebrow typography |
|
|
56
|
+
| `--u-txt-color-weak` | Subtitle and eyebrow color |
|
|
53
57
|
| `--u-link-txt-color` | Back-link color |
|
|
54
58
|
| `--u-text-label-size` / `-weight` | Back-link typography |
|
|
55
59
|
| `--u-space-2xl` | Host bottom margin |
|
|
56
60
|
| `--u-space-md` | Row gap (heading vs. actions) |
|
|
57
61
|
| `--u-space-sm` | Title-row and actions internal gap |
|
|
58
|
-
| `--u-space-2xs` |
|
|
62
|
+
| `--u-space-2xs` | Eyebrow-to-title and title-to-subtitle spacing |
|
|
59
63
|
| `--u-space-3xs` | Back-arrow-to-label spacing |
|
|
60
64
|
|
|
61
65
|
⚠ Built-in strings default to **English**, with Korean built in — choose the language with
|
|
@@ -61,7 +61,9 @@ interface SidebarLinkConfig {
|
|
|
61
61
|
navigate?: 'router' | 'document';
|
|
62
62
|
/** Anchor target. Use for opening in a new tab. */
|
|
63
63
|
target?: '_self' | '_blank';
|
|
64
|
-
|
|
64
|
+
/** Work waiting behind the item, shown at its end. Change it at runtime with app.setNavCount(href, n). */
|
|
65
|
+
count?: number | string;
|
|
66
|
+
styles?: StyleMap<'host' | 'base' | 'icon' | 'label' | 'count'>;
|
|
65
67
|
}
|
|
66
68
|
```
|
|
67
69
|
|
|
@@ -87,6 +89,21 @@ highlighting still works, and it can live inside a section or group. `target: '_
|
|
|
87
89
|
is available too, but it is not a substitute — it forces a new tab, so same-tab
|
|
88
90
|
navigation to another document needs `navigate`.
|
|
89
91
|
|
|
92
|
+
#### Counts — work waiting behind an item
|
|
93
|
+
|
|
94
|
+
A link can end with a count: items to review, unpaid invoices. Show numbers only where
|
|
95
|
+
they ask for action, or the sidebar turns into a dashboard.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
{ type: 'link', icon: 'inbox', label: 'Reviews', href: '/reviews', count: 12 }
|
|
99
|
+
|
|
100
|
+
// later, without rebuilding the layout — `undefined` clears it
|
|
101
|
+
app.setNavCount('/reviews', 3);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The count is `part="count"`; in the compact sidebar it stays in the link's accessible
|
|
105
|
+
name and is hidden visually.
|
|
106
|
+
|
|
90
107
|
---
|
|
91
108
|
|
|
92
109
|
### `SidebarGroupConfig` — `type: 'group'`
|
|
@@ -285,11 +302,14 @@ rule of your own wins.
|
|
|
285
302
|
|
|
286
303
|
⚠ **A custom `root` receives no styling.** Hand it a container with no height of its own and
|
|
287
304
|
`height: 100%` has nothing to resolve against: the shell renders at whatever its own chrome
|
|
288
|
-
resolves to (measured: about 133px) instead of filling the screen —
|
|
289
|
-
|
|
290
|
-
symptom only shows on short screens.)
|
|
305
|
+
resolves to (measured: about 133px) instead of filling the screen — no error, only a
|
|
306
|
+
development-mode console warning when it is shorter than 200px. (With a long screen the shell grows
|
|
307
|
+
to the viewport cap instead, so the symptom only shows on short screens.) The body margin is not
|
|
308
|
+
removed either, so the browser's default 8px stays around the shell. Give that container a height —
|
|
309
|
+
for screen only, so printing is not cut at one page — and clear the margin:
|
|
291
310
|
|
|
292
311
|
```css
|
|
312
|
+
:where(body) { margin: 0; }
|
|
293
313
|
@media screen {
|
|
294
314
|
#app { height: 100vh; } /* or 100%, inside an already-constrained ancestor */
|
|
295
315
|
}
|