@iyulab/modern-app 0.10.0 → 0.11.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 +61 -1
- package/LICENSE +1 -1
- package/dist/components/ActionBar.styles.js +9 -3
- package/dist/components/GroupBox.styles.js +9 -0
- package/dist/components/SidebarButton.js +12 -10
- package/dist/components/SidebarButton.styles.js +4 -3
- package/dist/components/SidebarGroup.js +22 -14
- package/dist/components/SidebarGroup.styles.js +8 -3
- package/dist/components/SidebarLink.js +12 -10
- package/dist/components/SidebarLink.styles.js +8 -3
- package/dist/components/SidebarSection.styles.js +14 -6
- package/dist/internals/nav-icon.d.ts +19 -0
- package/dist/internals/nav-icon.js +4 -0
- package/dist/layouts/SidebarLayout.styles.js +10 -3
- package/package.json +5 -4
- package/skills/modern-app/SKILL.md +14 -0
- package/skills/modern-app/references/components/action-bar.md +38 -0
- package/skills/modern-app/references/components/empty-state.md +43 -0
- package/skills/modern-app/references/components/group-box.md +41 -0
- package/skills/modern-app/references/components/info-field.md +45 -0
- package/skills/modern-app/references/components/info-section.md +35 -0
- package/skills/modern-app/references/components/page-header.md +46 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.11.1] - 2026-08-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- 🔴**`@iyulab/components` 의존 범위가 실제로 필요한 것보다 느슨했다** — `^1.21.0` 이었으나
|
|
8
|
+
`0.11.0` 의 **내비 아이콘 폴백은 `u-icon[fallback]`(1.24.0)** 을 쓴다. 범위가 그것을
|
|
9
|
+
«요구»하지 않으므로, components 를 `1.22.x` 에 고정한 소비자에게는 폴백이 **조용히
|
|
10
|
+
동작하지 않는다** — 아이콘 없는 그룹 항목이 접힌 사이드바에서 다시 빈 버튼이 된다.
|
|
11
|
+
⇒ `^1.24.0` 으로 좁혔다.
|
|
12
|
+
|
|
13
|
+
★**이것을 드러낸 것은 CI 였다** — `0.11.0` 태그 직전의 main 빌드가 아직 게시되지 않은
|
|
14
|
+
`1.24.0` 대신 `1.23.0` 을 설치했고, 그 조합에서 폴백 회귀 7건이 실패했다.
|
|
15
|
+
***«범위가 담는다»와 «범위가 요구한다»는 다르다.***
|
|
16
|
+
|
|
17
|
+
## [0.11.0] - 2026-08-04
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- 🔴**`u-action-bar[sticky]` 가 부모 밖으로 넘쳤다 — 가로 스크롤바가 생기고 주 액션이 잘렸다.**
|
|
22
|
+
좌우 음수 마진(`margin: 0 calc(-1 * var(--u-space-md))`)으로 «가장자리까지» 늘리고 있었는데,
|
|
23
|
+
그것은 ***부모가 정확히 그만큼의 가로 패딩을 갖고 있다***는 가정이다. 라이브러리가 소비자의
|
|
24
|
+
여백을 알 수는 없다 ⇒ 음수 마진을 제거하고 세로 패딩만 남겼다.
|
|
25
|
+
실측: 1034px 뷰포트에서 우측 12px 초과 → 저장 버튼 클리핑.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- 🔴**내비게이션 항목에 아이콘이 없거나 해석에 실패하면 기본 아이콘(큐브)을 그린다.**
|
|
30
|
+
사이드바가 접히면 라벨이 숨고 아이콘만 남는데, 그 아이콘이 없으면 그 메뉴는 **높이만 있고
|
|
31
|
+
아무것도 없는 빈 줄**이 되어 ***그 화면으로 갈 수단이 통째로 사라진다.***
|
|
32
|
+
⚠«이름이 없을 때»만 막으면 부족하다 — 실측 사례는 *이름은 있는데 파일이 없는* 쪽이었다.
|
|
33
|
+
그래서 폴백은 `u-icon` 의 **해석 실패 지점**에 걸린다(`u-icon[fallback]`, `@iyulab/components`).
|
|
34
|
+
폴백 SVG 는 **인라인**이다 — 다시 네트워크에 의존하면 같은 실패가 재현된다.
|
|
35
|
+
|
|
36
|
+
🔴**그리고 그 폴백이 호출부 셋 중 둘에만 걸려 있었다** — `u-sidebar-group` 은
|
|
37
|
+
`?hidden` 으로 폴백을 무력화한 채였고, 접힌 상태에서는 라벨과 캐럿도 함께 숨으므로
|
|
38
|
+
***그룹 버튼이 통째로 비어 있었다.*** 세 종류를 한 목록으로 도는 회귀 7건을 신설했다
|
|
39
|
+
(종전에는 이 축의 테스트가 **0건**이었고, 그래서 누락이 조용했다).
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
- 🔴**LOB 프리미티브 6종이 스킬 문서에 아예 없었다** — `u-page-header`·`u-group-box`·
|
|
44
|
+
`u-info-section`·`u-info-field`·`u-empty-state`·`u-action-bar`. 여섯 다 `index.ts` 에서
|
|
45
|
+
**공개**되고 화면이 그것으로 서는데, 스킬은 `app.load()` 설정만 설명하고 있었다.
|
|
46
|
+
스킬은 소비자(그리고 에이전트)가 API 를 찾는 자리이므로 **거기 없으면 없는 것과 같다.**
|
|
47
|
+
⇒ `references/components/*.md` 여섯과 목차 절을 더했다.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- **셸 크롬의 타이포가 디자인 토큰 스케일의 단을 읽는다**(`--u-text-*`).
|
|
52
|
+
종전에는 사이드바 계열 5개 파일이 **20건 전부 리터럴**이었고, 같은 패키지의 콘텐츠
|
|
53
|
+
프리미티브(`u-page-header`·`u-group-box`·`u-info-field`·`u-empty-state`)는 **23건 전부
|
|
54
|
+
토큰**이었다 — 스케일이 있는데 셸에는 닿지 않는 상태였다.
|
|
55
|
+
단 배정은 관측값 근사가 아니라 **용도**로 했다: 앱 제목 → `subtitle` · 섹션 제목 →
|
|
56
|
+
`overline` · 섹션 부제 → `caption` · 내비 항목 → `label`.
|
|
57
|
+
|
|
58
|
+
⚠**소비자 영향 — 사이드바 글자가 바뀐다**: 내비 항목 `14px/500` → `13px/600`(작고 굵게),
|
|
59
|
+
앱 제목 `18px/700` → `16px/600`, 섹션 제목 `12px` → `11px`.
|
|
60
|
+
★**배치는 바뀌지 않는다** — 내비 행 높이(라벨 줄 박스 19.5px가 20px 아이콘 아래에 들어간다)와
|
|
61
|
+
헤더 제목 줄 박스(16 × 1.5 = 24px, 종전 `line-height` 와 동일)를 실측으로 고정했다.
|
|
62
|
+
⚠**아이콘 크기는 타이포가 아니다** — `u-icon`·`.caret`·`.logo`·`.toggler` 의 `font-size` 는
|
|
63
|
+
도형 크기라 그대로 두었다.
|
|
4
64
|
|
|
5
65
|
### Added
|
|
6
66
|
|
package/LICENSE
CHANGED
|
@@ -37,14 +37,20 @@ var t = e`
|
|
|
37
37
|
margin-left: auto;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/* 화면 하단에 고정 — 긴 폼에서 저장 버튼을 찾아 스크롤하지 않아도 된다.
|
|
40
|
+
/* 화면 하단에 고정 — 긴 폼에서 저장 버튼을 찾아 스크롤하지 않아도 된다.
|
|
41
|
+
|
|
42
|
+
🔴**음수 마진으로 «가장자리까지 늘리는» 것을 하지 않는다.** 첫 판이
|
|
43
|
+
margin: 0 calc(-1 * var(--u-space-md, 12px)) 로 좌우를 넘겼는데, 그것은
|
|
44
|
+
***부모가 정확히 그만큼의 가로 패딩을 갖고 있다***고 가정하는 것이다. 실측에서
|
|
45
|
+
그 가정이 깨졌다 — 샘플 화면에서 좌우 12px 씩 넘쳐 **가로 스크롤바가 생기고
|
|
46
|
+
주 액션(저장)이 잘렸다.** 라이브러리가 소비자의 여백을 알 수는 없다.
|
|
47
|
+
⚠이 주석에 백틱을 쓰지 말 것 — css 태그드 템플릿을 그 자리에서 끝낸다. */
|
|
41
48
|
:host([sticky]) {
|
|
42
49
|
position: sticky;
|
|
43
50
|
bottom: 0;
|
|
44
51
|
background-color: var(--u-bg-color, #FFFFFF);
|
|
45
52
|
border-top: 1px solid var(--u-border-color-weak, #EEEEEE);
|
|
46
|
-
padding: var(--u-space-md, 12px);
|
|
47
|
-
margin: 0 calc(-1 * var(--u-space-md, 12px));
|
|
53
|
+
padding: var(--u-space-md, 12px) 0;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/* 좁을 때: 두 무리가 각자 한 줄을 차지하고 주 액션이 위로 온다.
|
|
@@ -59,8 +59,17 @@ var t = e`
|
|
|
59
59
|
display: none;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/* 🔴**본문은 세로 흐름이고, 그 리듬을 여기서 정한다.**
|
|
63
|
+
종전에는 padding 만 있어 블록 자식이 **간격 없이 붙었고**, 그보다 나쁘게는
|
|
64
|
+
shrink-to-fit 요소들이 각자 다른 폭으로 앉아 **왼쪽 끝이 어긋났다**(실측: 알림 넷을
|
|
65
|
+
나란히 두면 계단처럼 내려갔다). 소비자가 그것을 고치려면 자기 CSS 를 쓰게 되고,
|
|
66
|
+
그 순간 이 패키지가 존재하는 이유가 사라진다.
|
|
67
|
+
⇒ flex column + gap. 자식은 폭을 채우고 간격은 토큰이 정한다. */
|
|
62
68
|
.body {
|
|
63
69
|
padding: var(--u-space-xl, 20px);
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
gap: var(--u-space-md, 12px);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
/* 좁을 때: 헤더의 툴바(검색·필터)가 제목 아래로 내려가고 폭을 채운다.
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
2
|
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
|
|
5
|
+
import { styles as i } from "./SidebarButton.styles.js";
|
|
6
|
+
import { html as a } from "lit";
|
|
7
|
+
import { customElement as o, property as s } from "lit/decorators.js";
|
|
7
8
|
import "@iyulab/components/dist/components/icon/UIcon.js";
|
|
8
9
|
//#region src/components/SidebarButton.ts
|
|
9
|
-
var
|
|
10
|
+
var c = class extends n {
|
|
10
11
|
constructor(...e) {
|
|
11
12
|
super(...e), this.compact = !1;
|
|
12
13
|
}
|
|
13
14
|
static {
|
|
14
|
-
this.styles = [super.styles,
|
|
15
|
+
this.styles = [super.styles, i];
|
|
15
16
|
}
|
|
16
17
|
render() {
|
|
17
|
-
return
|
|
18
|
+
return a`
|
|
18
19
|
<button part="base" ?compact=${this.compact}>
|
|
19
|
-
<u-icon part="icon"
|
|
20
|
+
<u-icon part="icon"
|
|
20
21
|
.name=${this.icon}
|
|
22
|
+
.fallback=${r}
|
|
21
23
|
></u-icon>
|
|
22
24
|
<span part="label" ?hidden=${this.compact}>
|
|
23
25
|
${this.label}
|
|
@@ -26,9 +28,9 @@ var s = class extends n {
|
|
|
26
28
|
`;
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
|
-
t([
|
|
31
|
+
t([s({
|
|
30
32
|
type: Boolean,
|
|
31
33
|
reflect: !0
|
|
32
|
-
}), e("design:type", Object)],
|
|
34
|
+
}), e("design:type", Object)], c.prototype, "compact", 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-button")], c);
|
|
33
35
|
//#endregion
|
|
34
|
-
export {
|
|
36
|
+
export { c as SidebarButton };
|
|
@@ -45,11 +45,12 @@ var t = e`
|
|
|
45
45
|
font-size: 20px;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/* ★SidebarLink 와 같은 자리·같은 단(label). 두 곳이 어긋나면 목록이 계단이 된다. */
|
|
48
49
|
span {
|
|
49
50
|
flex: 1;
|
|
50
|
-
font-size:
|
|
51
|
-
font-weight:
|
|
52
|
-
line-height:
|
|
51
|
+
font-size: var(--u-text-label-size, 13px);
|
|
52
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
53
|
+
line-height: var(--u-text-label-leading, 1.5);
|
|
53
54
|
white-space: nowrap;
|
|
54
55
|
overflow: hidden;
|
|
55
56
|
text-overflow: ellipsis;
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
2
|
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
|
|
5
|
+
import { SidebarLink as i } from "./SidebarLink.js";
|
|
6
|
+
import { styles as a } from "./SidebarGroup.styles.js";
|
|
7
|
+
import { html as o } from "lit";
|
|
8
|
+
import { customElement as s, property as c } from "lit/decorators.js";
|
|
8
9
|
import "@iyulab/components/dist/components/icon/UIcon.js";
|
|
9
10
|
//#region src/components/SidebarGroup.ts
|
|
10
|
-
var
|
|
11
|
+
var l = class extends n {
|
|
11
12
|
constructor(...e) {
|
|
12
13
|
super(...e), this.compact = !1, this.selected = !1, this.collapsed = !0, this.handleButtonClick = (e) => {
|
|
13
|
-
this.compact ? ((this.shadowRoot?.querySelector("slot")?.assignedElements({ flatten: !0 }).find((e) => e instanceof
|
|
14
|
+
this.compact ? ((this.shadowRoot?.querySelector("slot")?.assignedElements({ flatten: !0 }).find((e) => e instanceof i))?.renderRoot.querySelector("u-link"))?.click() : this.collapsed = !this.collapsed;
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
static {
|
|
17
|
-
this.styles = [super.styles,
|
|
18
|
+
this.styles = [super.styles, a];
|
|
18
19
|
}
|
|
19
20
|
render() {
|
|
20
|
-
return
|
|
21
|
+
return o`
|
|
21
22
|
<button part="header"
|
|
22
23
|
?compact=${this.compact}
|
|
23
24
|
?selected=${this.selected}
|
|
24
25
|
@click=${this.handleButtonClick}>
|
|
25
|
-
|
|
26
|
+
<!--
|
|
27
|
+
⚠ ?hidden 을 걸지 않는다. 접힌 상태에서는 라벨과 캐럿이 모두 숨으므로,
|
|
28
|
+
아이콘까지 숨기면 **버튼이 통째로 비어 누를 것이 없어진다** — 이것이
|
|
29
|
+
SidebarLink·SidebarButton 에서 폴백을 도입한 바로 그 결함이고, 이 자리만
|
|
30
|
+
빠져 있었다.
|
|
31
|
+
-->
|
|
32
|
+
<u-icon class="icon" part="icon"
|
|
26
33
|
.name=${this.icon}
|
|
34
|
+
.fallback=${r}
|
|
27
35
|
></u-icon>
|
|
28
36
|
<span class="label" part="label" ?hidden=${this.compact}>
|
|
29
37
|
${this.label}
|
|
@@ -42,15 +50,15 @@ var c = class extends n {
|
|
|
42
50
|
`;
|
|
43
51
|
}
|
|
44
52
|
};
|
|
45
|
-
t([
|
|
53
|
+
t([c({
|
|
46
54
|
type: Boolean,
|
|
47
55
|
reflect: !0
|
|
48
|
-
}), e("design:type", Boolean)],
|
|
56
|
+
}), e("design:type", Boolean)], l.prototype, "compact", void 0), t([c({
|
|
49
57
|
type: Boolean,
|
|
50
58
|
reflect: !0
|
|
51
|
-
}), e("design:type", Boolean)],
|
|
59
|
+
}), e("design:type", Boolean)], l.prototype, "selected", void 0), t([c({
|
|
52
60
|
type: Boolean,
|
|
53
61
|
reflect: !0
|
|
54
|
-
}), e("design:type", Boolean)],
|
|
62
|
+
}), e("design:type", Boolean)], l.prototype, "collapsed", void 0), t([c({ type: String }), e("design:type", String)], l.prototype, "icon", void 0), t([c({ type: String }), e("design:type", Object)], l.prototype, "label", void 0), l = t([s("u-sidebar-group")], l);
|
|
55
63
|
//#endregion
|
|
56
|
-
export {
|
|
64
|
+
export { l as SidebarGroup };
|
|
@@ -53,11 +53,16 @@ var t = e`
|
|
|
53
53
|
font-size: 20px;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/*
|
|
57
|
+
* ★그룹도 자식 링크와 같은 label 단이다. 종전에는 굵기로 위계를 냈지만(600 vs 500),
|
|
58
|
+
* 자식은 이미 **32px 들여쓰기 + 2px 레일 + 캐럿**으로 갈린다 — 굵기 한 단을 위계에
|
|
59
|
+
* 더 쓰면 스케일 밖의 다섯 번째 값이 생긴다. 위계는 배치가 지고 단은 용도가 진다.
|
|
60
|
+
*/
|
|
56
61
|
.label {
|
|
57
62
|
flex: 1;
|
|
58
|
-
font-size:
|
|
59
|
-
line-height:
|
|
60
|
-
font-weight: 600;
|
|
63
|
+
font-size: var(--u-text-label-size, 13px);
|
|
64
|
+
line-height: var(--u-text-label-leading, 1.5);
|
|
65
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
61
66
|
overflow: hidden;
|
|
62
67
|
white-space: nowrap;
|
|
63
68
|
text-overflow: ellipsis;
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
2
|
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
|
|
5
|
+
import { styles as i } from "./SidebarLink.styles.js";
|
|
6
|
+
import { html as a } from "lit";
|
|
7
|
+
import { customElement as o, property as s } from "lit/decorators.js";
|
|
7
8
|
import "@iyulab/components/dist/components/icon/UIcon.js";
|
|
8
9
|
//#region src/components/SidebarLink.ts
|
|
9
|
-
var
|
|
10
|
+
var c = class extends n {
|
|
10
11
|
constructor(...e) {
|
|
11
12
|
super(...e), this.selected = !1, this.compact = !1;
|
|
12
13
|
}
|
|
13
14
|
static {
|
|
14
|
-
this.styles = [super.styles,
|
|
15
|
+
this.styles = [super.styles, i];
|
|
15
16
|
}
|
|
16
17
|
render() {
|
|
17
|
-
return
|
|
18
|
+
return a`
|
|
18
19
|
<u-link .href=${this.href || "#"}>
|
|
19
20
|
<div class="container" part="base" ?compact=${this.compact}>
|
|
20
|
-
<u-icon part="icon"
|
|
21
|
+
<u-icon part="icon"
|
|
21
22
|
.name=${this.icon}
|
|
23
|
+
.fallback=${r}
|
|
22
24
|
></u-icon>
|
|
23
25
|
<span part="label" ?hidden=${this.compact}>
|
|
24
26
|
${this.label}
|
|
@@ -28,9 +30,9 @@ var s = class extends n {
|
|
|
28
30
|
`;
|
|
29
31
|
}
|
|
30
32
|
};
|
|
31
|
-
t([
|
|
33
|
+
t([s({
|
|
32
34
|
type: Boolean,
|
|
33
35
|
reflect: !0
|
|
34
|
-
}), e("design:type", Object)],
|
|
36
|
+
}), e("design:type", Object)], c.prototype, "selected", void 0), t([s({ type: Boolean }), e("design:type", Object)], c.prototype, "compact", 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), t([s({ type: String }), e("design:type", String)], c.prototype, "href", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "pattern", void 0), c = t([o("u-sidebar-link")], c);
|
|
35
37
|
//#endregion
|
|
36
|
-
export {
|
|
38
|
+
export { c as SidebarLink };
|
|
@@ -50,11 +50,16 @@ var t = e`
|
|
|
50
50
|
font-size: 20px;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/*
|
|
54
|
+
* ★내비 항목은 «대상의 짧은 이름» = label 단이다(용도 배정 — DL-157-4).
|
|
55
|
+
* ⚠행 높이는 바뀌지 않는다: 13 x 1.5 = 19.5px < 아이콘 20px 이라 행을 잡는 것은
|
|
56
|
+
* 여전히 아이콘이다. 접힌 사이드바(아이콘만)와 펼친 사이드바의 행 높이가 같아야 한다.
|
|
57
|
+
*/
|
|
53
58
|
span {
|
|
54
59
|
flex: 1;
|
|
55
|
-
font-size:
|
|
56
|
-
line-height:
|
|
57
|
-
font-weight:
|
|
60
|
+
font-size: var(--u-text-label-size, 13px);
|
|
61
|
+
line-height: var(--u-text-label-leading, 1.5);
|
|
62
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
58
63
|
white-space: nowrap;
|
|
59
64
|
overflow: hidden;
|
|
60
65
|
text-overflow: ellipsis;
|
|
@@ -19,20 +19,28 @@ var t = e`
|
|
|
19
19
|
텍스트 단은 --u-txt-color-weak 하나뿐이고 그 값은 흰 배경에서 2.68:1 이라
|
|
20
20
|
WCAG AA(4.5:1)에 미달한다. 현행 두 값은 6.19:1 · 4.61:1 로 통과한다.
|
|
21
21
|
⇒ 역할 층에 AA 를 넘는 보조 텍스트 단이 생기면 그때 옮긴다. */
|
|
22
|
+
/*
|
|
23
|
+
* ★크기·굵기·자간은 스케일의 단으로 옮겼다(용도 배정 — DL-157-4). 위 주석은 **색**에
|
|
24
|
+
* 대한 것이고 그대로 유효하다: 두 색은 여전히 팔레트를 직접 읽는다.
|
|
25
|
+
* ⚠ overline 에 대문자 변환을 붙이지 않는다 — 이 줄에 ellipsis 가 있어 잘림이 늘고,
|
|
26
|
+
* 섹션 이름은 소비자가 쓴 글자다(CJK 에는 효과도 없다).
|
|
27
|
+
*/
|
|
22
28
|
.title {
|
|
23
29
|
color: var(--u-neutral-700, #616161);
|
|
24
|
-
font-size:
|
|
25
|
-
font-weight: 700;
|
|
26
|
-
|
|
30
|
+
font-size: var(--u-text-overline-size, 11px);
|
|
31
|
+
font-weight: var(--u-text-overline-weight, 700);
|
|
32
|
+
line-height: var(--u-text-overline-leading, 1.45);
|
|
33
|
+
letter-spacing: var(--u-text-overline-tracking, 0.06em);
|
|
27
34
|
white-space: nowrap;
|
|
28
35
|
overflow: hidden;
|
|
29
36
|
text-overflow: ellipsis;
|
|
30
37
|
}
|
|
31
|
-
|
|
38
|
+
|
|
32
39
|
.subtitle {
|
|
33
40
|
color: var(--u-neutral-600, #757575);
|
|
34
|
-
font-size:
|
|
35
|
-
font-weight:
|
|
41
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
42
|
+
font-weight: var(--u-text-caption-weight, 400);
|
|
43
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
.items {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 내비게이션 항목의 **기본 아이콘** — 아이콘이 없거나 해석에 실패했을 때 대신 그린다.
|
|
3
|
+
*
|
|
4
|
+
* 🔴**왜 필요한가 — «안 보인다»가 «누를 수 없다»가 되는 자리다.**
|
|
5
|
+
* 사이드바가 접히면(`compact`) 라벨이 숨고 아이콘만 남는다. 그 아이콘이 없으면 그 메뉴는
|
|
6
|
+
* **높이만 있고 아무것도 없는 빈 줄**이 되어, 사용자가 그 화면으로 갈 수단이 통째로 사라진다.
|
|
7
|
+
* 실측으로 밟았다 — 샘플의 `Items` 메뉴가 `icon: 'table'` 을 선언했는데 그 파일이 없어
|
|
8
|
+
* 아이콘이 그려지지 않았고, 접으면 고를 수 없었다.
|
|
9
|
+
*
|
|
10
|
+
* ⚠**«이름이 없을 때»만 막으면 부족하다.** 실제 사례는 *이름은 있는데 해석이 실패한* 쪽이었다
|
|
11
|
+
* (404 · 파싱 실패). 그래서 폴백은 `u-icon` 의 **해석 실패 지점**에 걸려 있다(`fallback` 속성).
|
|
12
|
+
*
|
|
13
|
+
* ⚠**이 SVG 는 인라인이다 — 파일을 받아오지 않는다.** 폴백이 다시 네트워크에 의존하면
|
|
14
|
+
* *"아이콘 폴더에 아무것도 없는 소비자"* 에게 같은 실패가 그대로 재현된다.
|
|
15
|
+
*
|
|
16
|
+
* 모양은 **큐브**다 — 특정 의미를 주장하지 않으면서 «항목»을 뜻하는 중립 기호이고,
|
|
17
|
+
* 이 리포의 다른 아이콘과 같은 계열(Bootstrap Icons, 16 viewBox, `currentColor` fill)이다.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_NAV_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path d=\"M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5 8 5.961 14.154 3.5zM15 4.239l-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z\"/></svg>";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/internals/nav-icon.ts
|
|
2
|
+
var e = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path d=\"M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5 8 5.961 14.154 3.5zM15 4.239l-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z\"/></svg>";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { e as DEFAULT_NAV_ICON };
|
|
@@ -29,12 +29,19 @@ var t = e`
|
|
|
29
29
|
object-fit: contain;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/*
|
|
33
|
+
* ★단 배정은 «관측값 근사»가 아니라 «용도»로 한다(DL-157-4) — 이 자리는 앱의 제목이다.
|
|
34
|
+
* 종전 18px 은 스케일에 없는 값이었고, 같은 판정을 이미 u-dialog·u-drawer 제목에
|
|
35
|
+
* 내린 적이 있다(L2-3, 18px → subtitle).
|
|
36
|
+
* ⚠줄 박스는 바뀌지 않는다: 16 x 1.5 = 24px = 종전 line-height. 헤더 높이 불변.
|
|
37
|
+
*/
|
|
32
38
|
.title {
|
|
33
39
|
flex: 1;
|
|
34
40
|
color: var(--u-txt-color, #212121);
|
|
35
|
-
font-size:
|
|
36
|
-
line-height:
|
|
37
|
-
font-weight:
|
|
41
|
+
font-size: var(--u-text-subtitle-size, 16px);
|
|
42
|
+
line-height: var(--u-text-subtitle-leading, 1.5);
|
|
43
|
+
font-weight: var(--u-text-subtitle-weight, 600);
|
|
44
|
+
letter-spacing: var(--u-text-subtitle-tracking, 0);
|
|
38
45
|
white-space: nowrap;
|
|
39
46
|
overflow: hidden;
|
|
40
47
|
text-overflow: ellipsis;
|
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.11.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -37,11 +37,12 @@
|
|
|
37
37
|
"test": "vitest run",
|
|
38
38
|
"test:watch": "vitest",
|
|
39
39
|
"start": "vite",
|
|
40
|
-
"build": "vite build",
|
|
41
|
-
"test:browser": "vitest run --project=browser"
|
|
40
|
+
"build": "npm run typecheck && vite build",
|
|
41
|
+
"test:browser": "vitest run --project=browser",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@iyulab/components": "^1.
|
|
45
|
+
"@iyulab/components": "^1.24.0",
|
|
45
46
|
"@iyulab/router": "^0.10.2",
|
|
46
47
|
"i18next": "^26.3.6",
|
|
47
48
|
"lit": "^3.3.3"
|
|
@@ -214,6 +214,20 @@ Sidebar states: `'default'` | `'slim'` | `'modal'` | `'mobile'` | `'mobile-open'
|
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
+
## LOB primitives
|
|
218
|
+
|
|
219
|
+
Screen-building components this package registers. They exist so the same skeleton is not
|
|
220
|
+
re-assembled per screen — that is where a product stops looking like one product.
|
|
221
|
+
|
|
222
|
+
- [`u-page-header`](./references/components/page-header.md) — Screen header: back link, title, subtitle, status and action slots
|
|
223
|
+
- [`u-group-box`](./references/components/group-box.md) — Titled card with trailing header actions; the unit detail screens are built from
|
|
224
|
+
- [`u-info-section`](./references/components/info-section.md) — Responsive grid for label/value pairs; container width picks the column count
|
|
225
|
+
- [`u-info-field`](./references/components/info-field.md) — Read-only label/value pair that keeps "not set" and "zero" distinct
|
|
226
|
+
- [`u-empty-state`](./references/components/empty-state.md) — Empty list or empty search, worded differently because they are different facts
|
|
227
|
+
- [`u-action-bar`](./references/components/action-bar.md) — Closing action row with a separated destructive group
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
217
231
|
## Full `AppConfig` reference
|
|
218
232
|
|
|
219
233
|
See [references/api.md](./references/api.md) for all TypeScript interfaces.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# u-action-bar
|
|
2
|
+
|
|
3
|
+
**Tag:** `u-action-bar`
|
|
4
|
+
|
|
5
|
+
The row of actions that closes a form or a detail screen: primary actions on the leading edge,
|
|
6
|
+
destructive ones separated on the trailing edge.
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
<u-action-bar sticky>
|
|
10
|
+
<u-button variant="solid">Save</u-button>
|
|
11
|
+
<u-button variant="outline">Cancel</u-button>
|
|
12
|
+
<u-button slot="danger" color="danger">Delete</u-button>
|
|
13
|
+
</u-action-bar>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Slots
|
|
17
|
+
|
|
18
|
+
| Name | Description |
|
|
19
|
+
|------|-------------|
|
|
20
|
+
| *(default)* | Primary actions |
|
|
21
|
+
| `danger` | Destructive actions, pushed to the trailing edge |
|
|
22
|
+
|
|
23
|
+
## Properties
|
|
24
|
+
|
|
25
|
+
| Property | Type | Default | Reflect | Description |
|
|
26
|
+
|----------|------|---------|---------|-------------|
|
|
27
|
+
| `sticky` | `boolean` | `false` | ✓ | Pin to the bottom of the scroll container |
|
|
28
|
+
|
|
29
|
+
## CSS Parts
|
|
30
|
+
|
|
31
|
+
| Part | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `main` | Primary action group |
|
|
34
|
+
| `danger` | Destructive action group |
|
|
35
|
+
|
|
36
|
+
⚠ `sticky` adds **vertical** padding only. It deliberately does not stretch to the container
|
|
37
|
+
edges with negative margins — that would assume the parent has exactly that much horizontal
|
|
38
|
+
padding, and when the assumption is wrong the bar overflows and clips its own buttons.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# u-empty-state
|
|
2
|
+
|
|
3
|
+
**Tag:** `u-empty-state`
|
|
4
|
+
|
|
5
|
+
Shown when a list or a search comes back with nothing.
|
|
6
|
+
|
|
7
|
+
🔴 **"there is no data" and "no results matched" are different facts.** The first means
|
|
8
|
+
*nothing has been created yet* and the next action is **create**. The second means *nothing
|
|
9
|
+
matches these conditions* and the next action is **change the filter**. Show the same wording
|
|
10
|
+
for both and a user with a filter still applied reads it as *"my data disappeared"*.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<u-empty-state variant="no-data" title="No orders yet">
|
|
14
|
+
<u-button slot="actions">New order</u-button>
|
|
15
|
+
</u-empty-state>
|
|
16
|
+
|
|
17
|
+
<u-empty-state variant="no-results"></u-empty-state>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Slots
|
|
21
|
+
|
|
22
|
+
| Name | Description |
|
|
23
|
+
|------|-------------|
|
|
24
|
+
| `icon` | Replaces the built-in icon |
|
|
25
|
+
| `actions` | Next-step buttons |
|
|
26
|
+
|
|
27
|
+
## Properties
|
|
28
|
+
|
|
29
|
+
| Property | Type | Default | Reflect | Description |
|
|
30
|
+
|----------|------|---------|---------|-------------|
|
|
31
|
+
| `variant` | `'no-data'\|'no-results'` | `'no-data'` | | Which fact is being shown; changes the default wording |
|
|
32
|
+
| `title` | `string` | `''` | | Override the default title |
|
|
33
|
+
| `description` | `string` | `''` | | Override the default description |
|
|
34
|
+
| `locale` | `string` | `''` | | Locale tag override for built-in strings |
|
|
35
|
+
|
|
36
|
+
## CSS Parts
|
|
37
|
+
|
|
38
|
+
| Part | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `icon` · `title` · `description` · `actions` | The four regions |
|
|
41
|
+
|
|
42
|
+
⚠ Default wording is **English** — register others with `registerLocale('ko', …)`, or pass
|
|
43
|
+
`title`/`description` per screen.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# u-group-box
|
|
2
|
+
|
|
3
|
+
**Tag:** `u-group-box`
|
|
4
|
+
|
|
5
|
+
A titled card — the unit LOB detail screens are built from.
|
|
6
|
+
|
|
7
|
+
`u-card` gives you a surface. What actually repeats on these screens is *surface + title +
|
|
8
|
+
trailing actions*, and assembling that by hand is how title sizes, padding, and dividers end
|
|
9
|
+
up different on every screen.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<u-group-box title="Receivables">
|
|
13
|
+
<a slot="actions" href="/receivables">Payments</a>
|
|
14
|
+
…body…
|
|
15
|
+
</u-group-box>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The body is a flex column with token spacing, so block children get vertical rhythm and share
|
|
19
|
+
a left edge without any consumer CSS.
|
|
20
|
+
|
|
21
|
+
## Slots
|
|
22
|
+
|
|
23
|
+
| Name | Description |
|
|
24
|
+
|------|-------------|
|
|
25
|
+
| *(default)* | Body content |
|
|
26
|
+
| `actions` | Trailing controls in the header |
|
|
27
|
+
|
|
28
|
+
## Properties
|
|
29
|
+
|
|
30
|
+
| Property | Type | Default | Reflect | Description |
|
|
31
|
+
|----------|------|---------|---------|-------------|
|
|
32
|
+
| `title` | `string` | `''` | | Header title |
|
|
33
|
+
| `divider` | `boolean` | `false` | | Rule between header and body. Off by default — many rules make a screen noisy |
|
|
34
|
+
| `flush` | `boolean` | `false` | | Remove body padding, for tables and lists that draw their own edges |
|
|
35
|
+
|
|
36
|
+
## CSS Parts
|
|
37
|
+
|
|
38
|
+
| Part | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `header` · `title` · `actions` | Header row |
|
|
41
|
+
| `body` | Body wrapper |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# u-info-field
|
|
2
|
+
|
|
3
|
+
**Tag:** `u-info-field`
|
|
4
|
+
|
|
5
|
+
A read-only label/value pair.
|
|
6
|
+
|
|
7
|
+
🔴 **"not set yet" and "zero" are different facts.**
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
null · undefined · '' → blank placeholder (—)
|
|
11
|
+
0 · false · '0' → rendered as-is
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Leaving that rule to whoever writes the screen guarantees it drifts. It has: an order with
|
|
15
|
+
*quantity 0* and an order whose *quantity had not been decided* rendered identically as `—`,
|
|
16
|
+
and those are different states of the business. So the component owns the rule.
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<u-info-field label="Quantity" .value=${order.quantity} numeric></u-info-field>
|
|
20
|
+
<u-info-field label="Customer">Acme Printing</u-info-field> <!-- slot wins over value -->
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Slots
|
|
24
|
+
|
|
25
|
+
| Name | Description |
|
|
26
|
+
|------|-------------|
|
|
27
|
+
| *(default)* | Value content; takes precedence over `value` |
|
|
28
|
+
|
|
29
|
+
## Properties
|
|
30
|
+
|
|
31
|
+
| Property | Type | Default | Reflect | Description |
|
|
32
|
+
|----------|------|---------|---------|-------------|
|
|
33
|
+
| `label` | `string` | `''` | | Field label |
|
|
34
|
+
| `value` | `unknown` | — | | Value; property only (`attribute: false`) |
|
|
35
|
+
| `blank` | `string` | `'—'` | | Placeholder for null/undefined/empty — **not** for `0` or `false` |
|
|
36
|
+
| `numeric` | `boolean` | `false` | | Right-align with tabular figures so digits line up vertically |
|
|
37
|
+
|
|
38
|
+
⚠ `numeric` earns its keep in **tables**, where columns of figures are compared by eye. On a
|
|
39
|
+
lone field it just pushes the value away from its label.
|
|
40
|
+
|
|
41
|
+
## CSS Parts
|
|
42
|
+
|
|
43
|
+
| Part | Description |
|
|
44
|
+
|------|-------------|
|
|
45
|
+
| `label` · `value` | The two elements |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# u-info-section
|
|
2
|
+
|
|
3
|
+
**Tag:** `u-info-section`
|
|
4
|
+
|
|
5
|
+
Responsive grid for `u-info-field` pairs.
|
|
6
|
+
|
|
7
|
+
**You do not count the columns.** Hand-built label/value grids usually hardcode a column count
|
|
8
|
+
per screen, which then breaks only at tablet width — on a screen nobody opens, so it stays
|
|
9
|
+
broken. Here the **container width** decides (`auto-fit`, not media queries), so the same
|
|
10
|
+
markup works inside a side panel.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<u-info-section>
|
|
14
|
+
<u-info-field label="Part" value="Standard"></u-info-field>
|
|
15
|
+
<u-info-field label="Quantity" .value=${0} numeric></u-info-field>
|
|
16
|
+
</u-info-section>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Slots
|
|
20
|
+
|
|
21
|
+
| Name | Description |
|
|
22
|
+
|------|-------------|
|
|
23
|
+
| *(default)* | `u-info-field` elements |
|
|
24
|
+
|
|
25
|
+
## Properties
|
|
26
|
+
|
|
27
|
+
| Property | Type | Default | Reflect | Description |
|
|
28
|
+
|----------|------|---------|---------|-------------|
|
|
29
|
+
| `min` | `number` | `160` | | Minimum column width in px. Raise it for domains with long labels |
|
|
30
|
+
|
|
31
|
+
## CSS Parts
|
|
32
|
+
|
|
33
|
+
| Part | Description |
|
|
34
|
+
|------|-------------|
|
|
35
|
+
| `grid` | Grid container |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# u-page-header
|
|
2
|
+
|
|
3
|
+
**Tag:** `u-page-header`
|
|
4
|
+
|
|
5
|
+
The same skeleton at the top of every LOB screen: optional back link, title, subtitle,
|
|
6
|
+
a status slot, and an actions slot.
|
|
7
|
+
|
|
8
|
+
Hand-rolling this per screen is where a product stops looking like one product — title sizes
|
|
9
|
+
drift, action alignment goes slightly off, and the narrow-viewport rule gets forgotten on the
|
|
10
|
+
screens nobody opens.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<u-page-header title="Order G-2026-I-0629" subtitle="Received 2026-02-24" back="/orders">
|
|
14
|
+
<span slot="status"><u-tag color="success">Confirmed</u-tag></span>
|
|
15
|
+
<span slot="actions"><u-button>Edit</u-button></span>
|
|
16
|
+
</u-page-header>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Slots
|
|
20
|
+
|
|
21
|
+
| Name | Description |
|
|
22
|
+
|------|-------------|
|
|
23
|
+
| `status` | Badge or tag beside the title |
|
|
24
|
+
| `actions` | Buttons on the trailing edge |
|
|
25
|
+
|
|
26
|
+
## Properties
|
|
27
|
+
|
|
28
|
+
| Property | Type | Default | Reflect | Description |
|
|
29
|
+
|----------|------|---------|---------|-------------|
|
|
30
|
+
| `title` | `string` | `''` | | Page title |
|
|
31
|
+
| `subtitle` | `string` | — | | Secondary line under the title |
|
|
32
|
+
| `back` | `string` | — | | Back-link href; renders a back affordance before the title |
|
|
33
|
+
| `backLabel` | `string` | `''` | | Back-link text (`back-label`). Empty falls back to the locale registry, then English |
|
|
34
|
+
| `locale` | `string` | `''` | | Locale tag override for built-in strings |
|
|
35
|
+
|
|
36
|
+
## CSS Parts
|
|
37
|
+
|
|
38
|
+
| Part | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `back` | Back link |
|
|
41
|
+
| `heading` | Title + subtitle wrapper |
|
|
42
|
+
| `title` · `subtitle` | Text elements |
|
|
43
|
+
| `status` · `actions` | Slot wrappers |
|
|
44
|
+
|
|
45
|
+
⚠ Built-in strings default to **English** — this package is a generic layer and cannot pick a
|
|
46
|
+
language. Register others with `registerLocale('ko', { back: '뒤로' })`.
|