@iyulab/modern-app 0.8.0 → 0.9.0
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 +122 -0
- package/dist/components/EmptyState.d.ts +46 -0
- package/dist/components/EmptyState.js +47 -0
- package/dist/components/EmptyState.styles.d.ts +1 -0
- package/dist/components/EmptyState.styles.js +51 -0
- package/dist/components/GroupBox.d.ts +43 -0
- package/dist/components/GroupBox.js +33 -0
- package/dist/components/GroupBox.styles.d.ts +1 -0
- package/dist/components/GroupBox.styles.js +73 -0
- package/dist/components/InfoField.d.ts +45 -0
- package/dist/components/InfoField.js +33 -0
- package/dist/components/InfoField.styles.d.ts +1 -0
- package/dist/components/InfoField.styles.js +40 -0
- package/dist/components/InfoSection.d.ts +31 -0
- package/dist/components/InfoSection.js +23 -0
- package/dist/components/InfoSection.styles.d.ts +1 -0
- package/dist/components/InfoSection.styles.js +18 -0
- package/dist/components/PageHeader.d.ts +53 -0
- package/dist/components/PageHeader.js +44 -0
- package/dist/components/PageHeader.styles.d.ts +1 -0
- package/dist/components/PageHeader.styles.js +89 -0
- package/dist/components/SidebarButton.styles.js +1 -1
- package/dist/components/SidebarGroup.styles.js +2 -2
- package/dist/components/SidebarLink.styles.js +2 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -2
- package/dist/internals/locale.d.ts +46 -0
- package/dist/internals/locale.js +29 -0
- package/dist/internals/slotted.d.ts +13 -0
- package/dist/internals/slotted.js +6 -0
- package/dist/layouts/SidebarLayout.js +6 -6
- package/dist/layouts/SidebarLayout.styles.js +13 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,127 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.0] - 2026-08-03
|
|
4
|
+
|
|
5
|
+
> **LOB 레이아웃 프리미티브 신설.** 이 패키지는 종전에 **셸 바깥 껍데기**(사이드바·라우팅·
|
|
6
|
+
> 테마·토스트)만 갖고 있었고 **셸 안쪽(페이지 본문)에 대한 제공물은 0개**였다. 그래서 소비앱이
|
|
7
|
+
> 화면마다 같은 구조를 손으로 다시 짰다 — 실측된 형태로 **라벨-값 그리드 41곳 · 카드 30여 곳**.
|
|
8
|
+
> 손으로 짜면 제목 크기·여백·열 수가 화면마다 조금씩 달라지고, 그 차이는 화면을 옮겨 다니는
|
|
9
|
+
> 사용자에게 *"제품이 하나로 만들어지지 않았다"* 로 읽힌다.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`u-page-header`** — 제목 + 상태 배지 슬롯 + 우측 액션 슬롯 + 뒤로가기. 좁은 화면에서
|
|
14
|
+
액션이 아래로 내려가며 **왼쪽 정렬**이 된다(오른쪽 유지는 손가락이 닿기 어려운 쪽으로 몰린다).
|
|
15
|
+
- **`u-group-box`** — 제목이 붙은 카드. `u-card` 는 면만 주는데, LOB 에서 반복되는 것은
|
|
16
|
+
*면 + 제목 + 우측 액션* 이라는 **묶음**이다. `divider`·`flush` 속성.
|
|
17
|
+
- **`u-info-section`** / **`u-info-field`** — 읽기 전용 라벨-값 그리드.
|
|
18
|
+
★열 수를 **컨테이너 폭**이 정한다(`auto-fit`). 미디어 쿼리로 고정하면 사이드 패널 같은
|
|
19
|
+
좁은 컨테이너 안에서 화면 폭 기준으로 계산돼 칸이 짓눌린다.
|
|
20
|
+
🔴**"아직 없음"과 "0"은 다른 사실이다** — `null`·`undefined`·빈 문자열은 `—`, `0`·`false`·`'0'`
|
|
21
|
+
은 값 그대로. 규칙을 사람이 기억하는 방식으로 두면 반드시 어긋난다(실제로 *"부수 0인 주문"*
|
|
22
|
+
과 *"부수가 아직 안 정해진 주문"* 이 똑같이 `—` 로 보인 소비앱이 있었다).
|
|
23
|
+
숫자는 `numeric` 으로 우정렬 + `tabular-nums` — 자릿수가 세로로 맞아야 크기 비교가 눈으로 된다.
|
|
24
|
+
- **`u-empty-state`** — 🔴**`no-data`(아직 없음)와 `no-results`(조건에 안 맞음)를 가른다.**
|
|
25
|
+
같은 문구로 보여 주면 사용자는 필터가 걸려 있는 줄 모르고 *"데이터가 사라졌다"* 로 읽는다.
|
|
26
|
+
`--empty-state-icon-size` 훅.
|
|
27
|
+
|
|
28
|
+
- 🆕**로케일 레지스트리** — `registerLocale` · `setDefaultLocale` · `getLocaleStrings`.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
registerLocale('ko', { back: '뒤로', noResultsTitle: '조건에 맞는 결과가 없습니다' });
|
|
32
|
+
setDefaultLocale('ko');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
🔴**기본값은 영어다.** 이 패키지는 3층 구분에서 **「범용」**이다(`components` = 중립
|
|
36
|
+
프리미티브 / **`modern-app` = 범용** / `enterprise` = 이유랩 하우스 스타일).
|
|
37
|
+
범용 층이 특정 언어를 기본값으로 가지면 **그 언어를 쓰지 않는 소비자를 배제**하며,
|
|
38
|
+
그것은 이 리포가 `components` 에 대해 기각한 것과 **같은 형태**다.
|
|
39
|
+
⚠**언어는 미감보다 강하게 배제한다** — 어색한 기본값은 어색할 뿐이지만 읽을 수 없는
|
|
40
|
+
기본값은 쓸 수 없다.
|
|
41
|
+
|
|
42
|
+
★**초판(미게시)의 `u-empty-state`·`u-page-header` 는 한국어 기본값을 갖고 있었다.**
|
|
43
|
+
로케일 표준(*영어 기본 + 레지스트리*)은 이 리포가 **채택했지만 강제한 적이 없었고**,
|
|
44
|
+
그래서 채택 이후에 만들어진 컴포넌트가 그대로 들어왔다. 회귀 테스트가
|
|
45
|
+
**`src/**` 문자열 리터럴에 한글 0건**을 지킨다(주석·JSDoc 은 대상 아님 — 이 리포의
|
|
46
|
+
내부 문서 언어는 한국어다). 부분 등록은 영어와 병합되고, `ko` 등록이 `ko-KR` 에도
|
|
47
|
+
걸린다(소비자는 `ko` 를 등록하고 브라우저는 `ko-KR` 을 준다).
|
|
48
|
+
|
|
49
|
+
### Docs
|
|
50
|
+
|
|
51
|
+
- 🆕`docs/lob-primitives.md` — 카탈로그 · 오버라이드 경로 · **아직 만들지 않은 것과 그 이유**.
|
|
52
|
+
- ★**등급 정책을 명시했다.** 이 패키지에는 `enterprise` 같은 카탈로그·등급 문서가 없었다.
|
|
53
|
+
프리미티브를 다섯 개 얹으면서 **정책을 조용히 만들어 버리는 것**을 피하려고 적었다 —
|
|
54
|
+
*"현대적 앱이 보통 제공하는 것"* 이면 들어오되, **오버라이드 경로(`part`+slot)와 토큰 전용**
|
|
55
|
+
둘을 요구한다.
|
|
56
|
+
- ➡**`EmptyState` 소유권을 `@iyulab/enterprise` 카탈로그에서 이관받았다.** 빈 상태는 프레임워크
|
|
57
|
+
중립이 맞고, 그쪽 L2 는 React 전용이다. 같은 표면을 두 패키지가 각각 만들면 **소비자가 둘 중
|
|
58
|
+
무엇을 쓸지 매번 고르게 되고, 그 비용은 만드는 쪽이 아니라 쓰는 쪽이 낸다.**
|
|
59
|
+
|
|
60
|
+
### 🔴 실브라우저 검증에서 잡힌 것 — 소스는 멀쩡했다
|
|
61
|
+
|
|
62
|
+
- **`:not(:has(*))` 로 빈 슬롯 래퍼를 접으려던 것이 동작하지 않았다.** `<slot>` **자신이
|
|
63
|
+
자식 요소**라 `:has(*)` 가 항상 참이다. 소스 검사도 jsdom 도 통과시킨다 — 브라우저에서만
|
|
64
|
+
드러난다. ⇒ 배정 상태를 `slotchange` 로 추적한다(`internals/slotted.ts`).
|
|
65
|
+
- **`render()` 안의 `this.querySelector()`** 는 **자식이 늦게 붙으면 못 본다.** 소비자가
|
|
66
|
+
`requestUpdate()` 를 부를 이유가 없으므로 헤더가 사라진 채로 남는다. ⇒ 같은 방식으로 교체.
|
|
67
|
+
헤더는 이제 **DOM 에서 빼지 않고 접는다** — 슬롯이 렌더되지 않으면 `slotchange` 가 영영 오지
|
|
68
|
+
않아 나중에 붙는 자식을 못 보기 때문이다.
|
|
69
|
+
|
|
70
|
+
### 계약 테스트
|
|
71
|
+
|
|
72
|
+
`tests/browser/lob-primitives.browser.test.ts` — **실제로 렌더해** 검증한다(23건).
|
|
73
|
+
`0` 이 `0` 으로 · 빈 값이 `—` 로 DOM 에 나오는가, 빈 슬롯 래퍼가 정말 접히는가,
|
|
74
|
+
좁은/넓은 컨테이너에서 열 수가 바뀌는가. ★`isBlank()` 단위 테스트는 **술어**를 증명할 뿐
|
|
75
|
+
컴포넌트를 증명하지 않는다 — 이 파일이 그 간격을 메운다.
|
|
76
|
+
|
|
77
|
+
`tests/lob-primitives.test.ts` — 색·크기 리터럴 0(예외는 헤어라인 `1px solid` 하나, 이유 명시) ·
|
|
78
|
+
`part` 노출 · `u-` 접두어 · export · **빈 값 대 0** · 빈 상태 두 variant 의 문구 분리.
|
|
79
|
+
|
|
80
|
+
### 요구 사항
|
|
81
|
+
|
|
82
|
+
`@iyulab/components` **1.21.0 이상**(타입 스케일 · 여백/반경 상단 축) — `dependencies` 상향.
|
|
83
|
+
⚠**폴백 리터럴은 아직 1.20.0 시트 값이다.** `tests/token-fallbacks.test.ts` 는 **설치된 시트**와
|
|
84
|
+
대조하므로, 1.21.0 을 설치하면 그 테스트가 빨개진다 — **회귀가 아니라 갱신 부채**다.
|
|
85
|
+
업그레이드 시 `node scripts/token-fallbacks.mjs` 로 다시 굽는다.
|
|
86
|
+
⚠**1.20.0 이하에서는 폴백 값으로 렌더된다** — 깨지지는 않지만 *"조용히 예전 모양"* 이 된다.
|
|
87
|
+
시트 부재 내성과 같은 기제다.
|
|
88
|
+
|
|
89
|
+
## [0.8.2] - 2026-08-03
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- **`SidebarLayout` 토글 버튼에 테두리가 남던 문제** — 토글러는 `<u-button>` 을 variant 없이
|
|
94
|
+
쓰고 있었고, `@iyulab/components` 의 기본값은 `solid` 이라 내부 버튼이
|
|
95
|
+
`--btn-border-color: var(--btn-color)` 로 **자기 테두리를 그렸다.**
|
|
96
|
+
|
|
97
|
+
⚠**시트의 `.toggler { border: none }` 으로는 지워지지 않는다** — 그 규칙은 `<u-button>`
|
|
98
|
+
**호스트**에만 닿고, 선을 그리는 것은 그 섀도 루트 안의 `<button>` 이다. 그래서 호스트를
|
|
99
|
+
재면 `0px` 로 보이고(테스트도 그렇게 통과한다) 화면에는 선이 남는다.
|
|
100
|
+
|
|
101
|
+
⇒ 마크업에 `variant="ghost"` 를 명시해 컴포넌트가 스스로 투명하게 그리도록 했다
|
|
102
|
+
(실측: 내부 버튼 테두리 `1px solid <주색>` → `1px solid transparent`).
|
|
103
|
+
중복이 된 `.toggler` 의 `border`·`background` 선언은 걷어냈다. `color: inherit` 는
|
|
104
|
+
헤더 색을 따르게 하려는 의도이므로 유지한다.
|
|
105
|
+
|
|
106
|
+
주색을 진하게 잡은 소비자일수록 눈에 띄던 자리다 — 테두리가 그 주색으로 그려졌다.
|
|
107
|
+
|
|
108
|
+
## [0.8.1] - 2026-08-02
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- **토큰 폴백 리터럴 7곳이 정본 시트와 어긋나 있던 문제** — `--u-txt-color-hover`(5곳) ·
|
|
113
|
+
`--u-primary-color`(2곳). `@iyulab/components` 1.16.0 이 텍스트 용도를 `-strong` 으로
|
|
114
|
+
라우팅하면서 값이 바뀌었다.
|
|
115
|
+
|
|
116
|
+
### Changed
|
|
117
|
+
|
|
118
|
+
- 셸 활성 메뉴 색이 주색을 따라 한 단 진해진다(`#1E88E5` → `#1976D2`, hover 파생
|
|
119
|
+
`#1A74C3` → `#1564B3`). 업스트림의 AA 수정을 그대로 따르는 것이며 이 패키지의 결정이 아니다.
|
|
120
|
+
|
|
121
|
+
⚠**이 결함은 개발 환경에서 절대 드러나지 않는다.** 토큰 시트가 있으면 폴백은 아예
|
|
122
|
+
평가되지 않는다 — 깨지는 곳은 시트를 로드하지 않은 소비자의 화면이고, 우리가 보지
|
|
123
|
+
못하는 자리다. 모노레포 루트의 `npm run tokens:sync` 가 이제 정본 시트와 대조한다.
|
|
124
|
+
|
|
3
125
|
## [0.8.0] - 2026-08-01
|
|
4
126
|
|
|
5
127
|
### Added
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
|
+
type ElementParts = 'host' | 'icon' | 'title' | 'description' | 'actions';
|
|
3
|
+
/**
|
|
4
|
+
* 빈 상태 — 목록·검색 결과가 비었을 때.
|
|
5
|
+
*
|
|
6
|
+
* 🔴★**"데이터가 없다"와 "검색 결과가 없다"는 다른 사실이다.**
|
|
7
|
+
* 전자는 *"아직 만들지 않았다"* 이고 다음 행동은 **만들기**다.
|
|
8
|
+
* 후자는 *"조건에 맞는 것이 없다"* 이고 다음 행동은 **조건 바꾸기**다.
|
|
9
|
+
* 같은 문구로 보여 주면 사용자는 필터가 걸려 있는 줄 모르고 *"데이터가 사라졌다"* 로 읽는다.
|
|
10
|
+
* ⇒ `variant` 로 가르고, 기본 문구가 각각 다르다.
|
|
11
|
+
*
|
|
12
|
+
* ```html
|
|
13
|
+
* <u-empty-state variant="no-data" title="아직 주문이 없습니다">
|
|
14
|
+
* <u-button slot="actions">주문 등록</u-button>
|
|
15
|
+
* </u-empty-state>
|
|
16
|
+
*
|
|
17
|
+
* <u-empty-state variant="no-results"></u-empty-state>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* ⚠기본 문구는 한국어다 — 다국어 소비자는 `title`·`description` 을 준다.
|
|
21
|
+
*/
|
|
22
|
+
export declare class EmptyState extends StyledElement<ElementParts> {
|
|
23
|
+
static styles: import('lit').CSSResultGroup[];
|
|
24
|
+
/** `no-data` = 아직 없음 / `no-results` = 조건에 맞는 것이 없음. */
|
|
25
|
+
variant: 'no-data' | 'no-results';
|
|
26
|
+
/** 제목. 비우면 variant 기본 문구. */
|
|
27
|
+
title: string;
|
|
28
|
+
/** 보조 설명. 비우면 variant 기본 문구. */
|
|
29
|
+
description: string;
|
|
30
|
+
/**
|
|
31
|
+
* 언어 태그. 비우면 `setDefaultLocale()` 값, 그것도 없으면 **영어**.
|
|
32
|
+
* ⚠기본 문구는 영어다 — 이 패키지는 범용 층이라 특정 언어를 기본값으로 가질 수 없다.
|
|
33
|
+
* 한국어는 `registerLocale('ko', …)` 로 소비자가 등록한다.
|
|
34
|
+
*/
|
|
35
|
+
locale: string;
|
|
36
|
+
/** 액션 슬롯 배정 상태 — CSS `:has()` 로는 알 수 없다(`internals/slotted.ts` 참조). */
|
|
37
|
+
private hasActions;
|
|
38
|
+
private get defaults();
|
|
39
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
40
|
+
}
|
|
41
|
+
declare global {
|
|
42
|
+
interface HTMLElementTagNameMap {
|
|
43
|
+
'u-empty-state': EmptyState;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
|
+
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
+
import { getLocaleStrings as r } from "../internals/locale.js";
|
|
5
|
+
import { slotHasContent as i } from "../internals/slotted.js";
|
|
6
|
+
import { styles as a } from "./EmptyState.styles.js";
|
|
7
|
+
import { html as o, nothing as s } from "lit";
|
|
8
|
+
import { customElement as c, property as l, state as u } from "lit/decorators.js";
|
|
9
|
+
//#region src/components/EmptyState.ts
|
|
10
|
+
var d = class extends n {
|
|
11
|
+
constructor(...e) {
|
|
12
|
+
super(...e), this.variant = "no-data", this.title = "", this.description = "", this.locale = "", this.hasActions = !1;
|
|
13
|
+
}
|
|
14
|
+
static {
|
|
15
|
+
this.styles = [super.styles, a];
|
|
16
|
+
}
|
|
17
|
+
get defaults() {
|
|
18
|
+
let e = r(this.locale || void 0);
|
|
19
|
+
return this.variant === "no-results" ? {
|
|
20
|
+
icon: "🔍",
|
|
21
|
+
title: e.noResultsTitle,
|
|
22
|
+
description: e.noResultsDescription
|
|
23
|
+
} : {
|
|
24
|
+
icon: "📄",
|
|
25
|
+
title: e.noDataTitle,
|
|
26
|
+
description: e.noDataDescription
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
render() {
|
|
30
|
+
let e = this.defaults, t = this.description || e.description;
|
|
31
|
+
return o`
|
|
32
|
+
<div class="icon" part="icon" aria-hidden="true">
|
|
33
|
+
<slot name="icon">${e.icon}</slot>
|
|
34
|
+
</div>
|
|
35
|
+
<p class="title" part="title">${this.title || e.title}</p>
|
|
36
|
+
${t ? o`<p class="description" part="description">${t}</p>` : s}
|
|
37
|
+
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
38
|
+
<slot name="actions"
|
|
39
|
+
@slotchange=${(e) => this.hasActions = i(e.target)}
|
|
40
|
+
></slot>
|
|
41
|
+
</div>
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
t([l({ type: String }), e("design:type", String)], d.prototype, "variant", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "title", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "description", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "locale", void 0), t([u(), e("design:type", Object)], d.prototype, "hasActions", void 0), d = t([c("u-empty-state")], d);
|
|
46
|
+
//#endregion
|
|
47
|
+
export { d as EmptyState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { css as e } from "lit";
|
|
2
|
+
//#region src/components/EmptyState.styles.ts
|
|
3
|
+
var t = e`
|
|
4
|
+
:host {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
text-align: center;
|
|
9
|
+
padding: var(--u-space-4xl, 40px) var(--u-space-xl, 20px);
|
|
10
|
+
color: var(--u-txt-color-weak, #757575);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.icon {
|
|
14
|
+
/* 타입 스케일 밖이다 — 글자가 아니라 장식이라서. 대신 소비자 훅을 연다
|
|
15
|
+
(아이콘을 SVG 로 갈아 끼우는 소비자가 크기를 맞출 수 있어야 한다). */
|
|
16
|
+
font-size: var(--empty-state-icon-size, 32px);
|
|
17
|
+
line-height: 1;
|
|
18
|
+
margin-bottom: var(--u-space-md, 12px);
|
|
19
|
+
opacity: 0.7;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.title {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-size: var(--u-text-subtitle-size, 16px);
|
|
25
|
+
font-weight: var(--u-text-subtitle-weight, 600);
|
|
26
|
+
line-height: var(--u-text-subtitle-leading, 1.5);
|
|
27
|
+
/* 제목은 본문 색이다 — 빈 상태 전체를 흐리게 만들면 "로딩 중"으로 오독된다. */
|
|
28
|
+
color: var(--u-txt-color, #212121);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.description {
|
|
32
|
+
margin: var(--u-space-2xs, 4px) 0 0;
|
|
33
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
34
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.actions {
|
|
38
|
+
display: flex;
|
|
39
|
+
gap: var(--u-space-sm, 8px);
|
|
40
|
+
margin-top: var(--u-space-lg, 16px);
|
|
41
|
+
}
|
|
42
|
+
/* 빈 슬롯 래퍼는 접는다.
|
|
43
|
+
⚠**CSS ':has()' 로는 못 한다** — <slot> 자신이 자식 요소라 ':has(*)' 가 항상 참이다
|
|
44
|
+
(실브라우저 테스트로 확인했다. 소스 검사와 jsdom 은 둘 다 통과시킨다).
|
|
45
|
+
배정 상태는 'slotchange' 로 추적해 '.empty' 클래스로 내려온다 — internals/slotted.ts */
|
|
46
|
+
.actions.empty {
|
|
47
|
+
display: none;
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { t as styles };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
|
+
type ElementParts = 'host' | 'header' | 'title' | 'actions' | 'body';
|
|
3
|
+
/**
|
|
4
|
+
* 그룹 박스 — 제목이 붙은 카드. LOB 상세 화면의 기본 단위다.
|
|
5
|
+
*
|
|
6
|
+
* ★**왜 `u-card` 가 아니라 이것인가**: `u-card` 는 면(surface)만 준다. LOB 화면에서 반복되는
|
|
7
|
+
* 것은 *면 + 제목 + 우측 액션 슬롯* 이라는 **묶음**이고, 그 묶음을 소비자가 매번 조립하면
|
|
8
|
+
* 제목 크기·여백·구분선이 화면마다 달라진다. 카드 30여 곳을 손으로 그리던 소비앱에서
|
|
9
|
+
* 실제로 그렇게 갈라졌다.
|
|
10
|
+
*
|
|
11
|
+
* ```html
|
|
12
|
+
* <u-group-box title="수금">
|
|
13
|
+
* <a slot="actions" href="/receivables">미수금·결제</a>
|
|
14
|
+
* …본문…
|
|
15
|
+
* </u-group-box>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* 오버라이드: `part`(host·header·title·actions·body) + slot 치환.
|
|
19
|
+
* `divider` 속성으로 제목과 본문 사이 구분선을 켠다(기본 꺼짐 — 선이 많으면 화면이 시끄럽다).
|
|
20
|
+
*/
|
|
21
|
+
export declare class GroupBox extends StyledElement<ElementParts> {
|
|
22
|
+
static styles: import('lit').CSSResultGroup[];
|
|
23
|
+
/** 카드 제목. 비우면 헤더 자체를 렌더하지 않는다. */
|
|
24
|
+
title: string;
|
|
25
|
+
/** 제목과 본문 사이에 구분선을 넣는다. */
|
|
26
|
+
divider: boolean;
|
|
27
|
+
/** 본문 여백을 없앤다 — 표를 카드 가장자리까지 붙일 때. */
|
|
28
|
+
flush: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 액션 슬롯 배정 상태.
|
|
31
|
+
* ★종전에는 'render()' 안에서 'this.querySelector()' 로 라이트 DOM 을 읽었다. 그것은
|
|
32
|
+
* **자식이 늦게 붙으면 못 본다** — 소비자가 'requestUpdate()' 를 부르지 않는 한
|
|
33
|
+
* 헤더가 통째로 사라진 채로 남는다. 슬롯 배정은 'slotchange' 가 알려 준다.
|
|
34
|
+
*/
|
|
35
|
+
private hasActions;
|
|
36
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
37
|
+
}
|
|
38
|
+
declare global {
|
|
39
|
+
interface HTMLElementTagNameMap {
|
|
40
|
+
'u-group-box': GroupBox;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
|
+
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
+
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
|
+
import { styles as i } from "./GroupBox.styles.js";
|
|
6
|
+
import { html as a } from "lit";
|
|
7
|
+
import { customElement as o, property as s, state as c } from "lit/decorators.js";
|
|
8
|
+
//#region src/components/GroupBox.ts
|
|
9
|
+
var l = class extends n {
|
|
10
|
+
constructor(...e) {
|
|
11
|
+
super(...e), this.title = "", this.divider = !1, this.flush = !1, this.hasActions = !1;
|
|
12
|
+
}
|
|
13
|
+
static {
|
|
14
|
+
this.styles = [super.styles, i];
|
|
15
|
+
}
|
|
16
|
+
render() {
|
|
17
|
+
let e = !!this.title || this.hasActions;
|
|
18
|
+
return a`
|
|
19
|
+
<div class="header ${this.divider ? "divider" : ""} ${e ? "" : "empty"}" part="header">
|
|
20
|
+
<h3 class="title" part="title">${this.title}</h3>
|
|
21
|
+
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
22
|
+
<slot name="actions"
|
|
23
|
+
@slotchange=${(e) => this.hasActions = r(e.target)}
|
|
24
|
+
></slot>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="body ${this.flush ? "flush" : ""}" part="body"><slot></slot></div>
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
t([s({ type: String }), e("design:type", Object)], l.prototype, "title", void 0), t([s({ type: Boolean }), e("design:type", Object)], l.prototype, "divider", void 0), t([s({ type: Boolean }), e("design:type", Object)], l.prototype, "flush", void 0), t([c(), e("design:type", Object)], l.prototype, "hasActions", void 0), l = t([o("u-group-box")], l);
|
|
32
|
+
//#endregion
|
|
33
|
+
export { l as GroupBox };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { css as e } from "lit";
|
|
2
|
+
//#region src/components/GroupBox.styles.ts
|
|
3
|
+
var t = e`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
background-color: var(--u-panel-bg-color, #FFFFFF);
|
|
7
|
+
border: 1px solid var(--u-border-color-weak, #EEEEEE);
|
|
8
|
+
/* 면(surface) 단 — 컨트롤과 같은 반경을 쓰면 큰 사각형이 각져 보인다. */
|
|
9
|
+
border-radius: var(--u-radius-2xl, 12px);
|
|
10
|
+
box-shadow: var(--u-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 1px rgba(0, 0, 0, 0.04));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.header {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: var(--u-space-sm, 8px);
|
|
17
|
+
padding: var(--u-space-xl, 20px) var(--u-space-xl, 20px) 0;
|
|
18
|
+
}
|
|
19
|
+
/* 제목도 액션도 없으면 헤더를 접는다. ★DOM 에서 빼지 않고 접는 이유 =
|
|
20
|
+
슬롯 배정을 'slotchange' 로 알아야 하는데, 슬롯이 렌더되지 않으면 그 이벤트가
|
|
21
|
+
영영 오지 않는다(자식을 나중에 붙이는 소비자가 헤더를 못 얻는다). */
|
|
22
|
+
.header.empty {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.header.divider {
|
|
27
|
+
padding-bottom: var(--u-space-md, 12px);
|
|
28
|
+
border-bottom: 1px solid var(--u-border-color-weak, #EEEEEE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.title {
|
|
32
|
+
margin: 0;
|
|
33
|
+
flex: 1 1 auto;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
font-size: var(--u-text-subtitle-size, 16px);
|
|
36
|
+
font-weight: var(--u-text-subtitle-weight, 600);
|
|
37
|
+
line-height: var(--u-text-subtitle-leading, 1.5);
|
|
38
|
+
letter-spacing: var(--u-text-subtitle-tracking, 0);
|
|
39
|
+
color: var(--u-txt-color, #212121);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.actions {
|
|
43
|
+
flex: 0 0 auto;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(--u-space-sm, 8px);
|
|
47
|
+
font-size: var(--u-text-label-size, 13px);
|
|
48
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
49
|
+
}
|
|
50
|
+
/* 빈 슬롯 래퍼는 접는다.
|
|
51
|
+
⚠**CSS ':has()' 로는 못 한다** — <slot> 자신이 자식 요소라 ':has(*)' 가 항상 참이다
|
|
52
|
+
(실브라우저 테스트로 확인했다. 소스 검사와 jsdom 은 둘 다 통과시킨다).
|
|
53
|
+
배정 상태는 'slotchange' 로 추적해 '.empty' 클래스로 내려온다 — internals/slotted.ts */
|
|
54
|
+
.actions.empty {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.body {
|
|
59
|
+
padding: var(--u-space-xl, 20px);
|
|
60
|
+
}
|
|
61
|
+
/* 표를 카드 가장자리까지 붙일 때. 헤더가 있으면 위 여백만 남긴다 —
|
|
62
|
+
0 으로 만들면 제목과 표가 붙어 읽기 어려워진다. */
|
|
63
|
+
.body.flush {
|
|
64
|
+
padding: 0;
|
|
65
|
+
}
|
|
66
|
+
/* 헤더가 접혀 있으면 위 여백도 없다 — 인접 선택자는 'display:none' 을 보지 못하므로
|
|
67
|
+
클래스로 갈라야 한다. */
|
|
68
|
+
.header:not(.empty) + .body.flush {
|
|
69
|
+
padding-top: var(--u-space-md, 12px);
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { t as styles };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
|
+
type ElementParts = 'host' | 'label' | 'value';
|
|
3
|
+
/** 값이 "아직 없음"인가 — `0` 과 `false` 는 **값이다**. */
|
|
4
|
+
export declare function isBlank(v: unknown): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* 정보 필드 — 읽기 전용 라벨-값 한 쌍.
|
|
7
|
+
*
|
|
8
|
+
* 🔴★**"아직 없음"과 "0"은 다른 사실이다.**
|
|
9
|
+
* `null`·`undefined`·빈 문자열 → **`—`**
|
|
10
|
+
* `0`·`false`·`'0'` → **그 값 그대로**
|
|
11
|
+
*
|
|
12
|
+
* ⚠이 규칙을 사람이 기억하는 방식으로 두면 **반드시 어긋난다.** 실제로 한 소비앱에서
|
|
13
|
+
* *"부수가 0인 주문"* 과 *"부수가 아직 안 정해진 주문"* 이 화면에서 똑같이 `—` 로 보였고,
|
|
14
|
+
* 그 둘은 업무적으로 전혀 다른 상태였다. 그래서 규칙을 컴포넌트가 소유한다.
|
|
15
|
+
*
|
|
16
|
+
* ```html
|
|
17
|
+
* <u-info-field label="부수" .value=${order.quantity} numeric></u-info-field>
|
|
18
|
+
* <u-info-field label="거래처">동서인쇄</u-info-field> <!-- 슬롯이 value 를 이긴다 -->
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class InfoField extends StyledElement<ElementParts> {
|
|
22
|
+
static styles: import('lit').CSSResultGroup[];
|
|
23
|
+
/** 필드 이름. */
|
|
24
|
+
label: string;
|
|
25
|
+
/**
|
|
26
|
+
* 값. `null`/`undefined`/빈 문자열이면 `blank` 문구로 대체된다.
|
|
27
|
+
* ⚠`0`·`false` 는 **대체되지 않는다** — 값이기 때문이다.
|
|
28
|
+
*/
|
|
29
|
+
value?: unknown;
|
|
30
|
+
/** "아직 없음"을 나타낼 문구. */
|
|
31
|
+
blank: string;
|
|
32
|
+
/**
|
|
33
|
+
* 숫자 값 — 우정렬 + 고정폭 숫자(`tabular-nums`).
|
|
34
|
+
* ★자릿수가 세로로 맞아야 크기 비교가 눈으로 된다. LOB 화면은 금액·수량이 절반이다.
|
|
35
|
+
*/
|
|
36
|
+
numeric: boolean;
|
|
37
|
+
private get hasSlotted();
|
|
38
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
39
|
+
}
|
|
40
|
+
declare global {
|
|
41
|
+
interface HTMLElementTagNameMap {
|
|
42
|
+
'u-info-field': InfoField;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
|
+
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
+
import { styles as r } from "./InfoField.styles.js";
|
|
5
|
+
import { html as i } from "lit";
|
|
6
|
+
import { customElement as a, property as o } from "lit/decorators.js";
|
|
7
|
+
//#region src/components/InfoField.ts
|
|
8
|
+
function s(e) {
|
|
9
|
+
return e == null || typeof e == "string" && e.trim() === "";
|
|
10
|
+
}
|
|
11
|
+
var c = class extends n {
|
|
12
|
+
constructor(...e) {
|
|
13
|
+
super(...e), this.label = "", this.blank = "—", this.numeric = !1;
|
|
14
|
+
}
|
|
15
|
+
static {
|
|
16
|
+
this.styles = [super.styles, r];
|
|
17
|
+
}
|
|
18
|
+
get hasSlotted() {
|
|
19
|
+
return this.childNodes.length > 0 && [...this.childNodes].some((e) => e.nodeType !== Node.TEXT_NODE || (e.textContent ?? "").trim() !== "");
|
|
20
|
+
}
|
|
21
|
+
render() {
|
|
22
|
+
let e = !this.hasSlotted && s(this.value);
|
|
23
|
+
return i`
|
|
24
|
+
<div class="label" part="label">${this.label}</div>
|
|
25
|
+
<div class="value ${this.numeric ? "numeric" : ""} ${e ? "blank" : ""}" part="value">
|
|
26
|
+
${this.hasSlotted ? i`<slot></slot>` : e ? this.blank : String(this.value)}
|
|
27
|
+
</div>
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
t([o({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([o({ attribute: !1 }), e("design:type", Object)], c.prototype, "value", void 0), t([o({ type: String }), e("design:type", Object)], c.prototype, "blank", void 0), t([o({ type: Boolean }), e("design:type", Object)], c.prototype, "numeric", void 0), c = t([a("u-info-field")], c);
|
|
32
|
+
//#endregion
|
|
33
|
+
export { c as InfoField, s as isBlank };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { css as e } from "lit";
|
|
2
|
+
//#region src/components/InfoField.styles.ts
|
|
3
|
+
var t = e`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.label {
|
|
10
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
11
|
+
font-weight: var(--u-text-caption-weight, 400);
|
|
12
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
13
|
+
color: var(--u-txt-color-weak, #757575);
|
|
14
|
+
margin-bottom: var(--u-space-3xs, 2px);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.value {
|
|
18
|
+
font-size: var(--u-text-body-size, 14px);
|
|
19
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
20
|
+
line-height: var(--u-text-body-leading, 1.6);
|
|
21
|
+
color: var(--u-txt-color, #212121);
|
|
22
|
+
/* 긴 값(주소·비고)이 그리드 열을 밀어내지 않게 한다. */
|
|
23
|
+
overflow-wrap: anywhere;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.value.numeric {
|
|
27
|
+
text-align: right;
|
|
28
|
+
font-variant-numeric: tabular-nums;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* "아직 없음"은 값보다 약하게 — 다만 **읽을 수 있어야 한다.**
|
|
32
|
+
⚠더 흐리게 만들고 싶은 유혹이 있는데, 이 자리가 안 읽히면 사용자는 빈 칸인지
|
|
33
|
+
로딩 중인지 구별하지 못한다. */
|
|
34
|
+
.value.blank {
|
|
35
|
+
color: var(--u-txt-color-weak, #757575);
|
|
36
|
+
font-weight: var(--u-text-body-weight, 400);
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { t as styles };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
|
+
type ElementParts = 'host' | 'grid';
|
|
3
|
+
/**
|
|
4
|
+
* 정보 섹션 — `u-info-field` 들의 반응형 그리드.
|
|
5
|
+
*
|
|
6
|
+
* ★**열 수를 소비자가 세지 않는다.** 손으로 짠 라벨-값 그리드의 실패는 대부분
|
|
7
|
+
* *"열 수를 화면마다 다르게 하드코딩"* 이다 — 그러면 태블릿에서만 칸이 어긋나고,
|
|
8
|
+
* 그 화면을 아무도 안 열어 봐서 오래 남는다. 여기서는 **컨테이너 폭**이 열 수를 정한다
|
|
9
|
+
* (미디어 쿼리가 아니라 `auto-fit` — 사이드 패널 안에 들어가도 맞는다).
|
|
10
|
+
*
|
|
11
|
+
* ```html
|
|
12
|
+
* <u-info-section>
|
|
13
|
+
* <u-info-field label="파트" value="일반"></u-info-field>
|
|
14
|
+
* <u-info-field label="부수" .value=${0} numeric></u-info-field>
|
|
15
|
+
* </u-info-section>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* `min` 으로 한 칸의 최소 폭을 조절한다(기본 160px). 라벨이 긴 도메인은 값을 올린다.
|
|
19
|
+
*/
|
|
20
|
+
export declare class InfoSection extends StyledElement<ElementParts> {
|
|
21
|
+
static styles: import('lit').CSSResultGroup[];
|
|
22
|
+
/** 한 칸의 최소 폭(px). 이 값보다 좁아지면 열 수가 줄어든다. */
|
|
23
|
+
min: number;
|
|
24
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
25
|
+
}
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'u-info-section': InfoSection;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
|
+
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
+
import { styles as r } from "./InfoSection.styles.js";
|
|
5
|
+
import { html as i } from "lit";
|
|
6
|
+
import { customElement as a, property as o } from "lit/decorators.js";
|
|
7
|
+
//#region src/components/InfoSection.ts
|
|
8
|
+
var s = class extends n {
|
|
9
|
+
constructor(...e) {
|
|
10
|
+
super(...e), this.min = 160;
|
|
11
|
+
}
|
|
12
|
+
static {
|
|
13
|
+
this.styles = [super.styles, r];
|
|
14
|
+
}
|
|
15
|
+
render() {
|
|
16
|
+
return i`
|
|
17
|
+
<div class="grid" part="grid" style="--info-min: ${this.min}px"><slot></slot></div>
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
t([o({ type: Number }), e("design:type", Object)], s.prototype, "min", void 0), s = t([a("u-info-section")], s);
|
|
22
|
+
//#endregion
|
|
23
|
+
export { s as InfoSection };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { css as e } from "lit";
|
|
2
|
+
//#region src/components/InfoSection.styles.ts
|
|
3
|
+
var t = e`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* ★'auto-fit' + 'minmax' — **컨테이너 폭**이 열 수를 정한다.
|
|
9
|
+
미디어 쿼리로 열 수를 고정하면 사이드 패널(좁은 컨테이너) 안에서 화면 폭 기준으로
|
|
10
|
+
계산돼 칸이 짓눌린다. 이 방식은 어디에 놓여도 자기 폭을 본다. */
|
|
11
|
+
.grid {
|
|
12
|
+
display: grid;
|
|
13
|
+
grid-template-columns: repeat(auto-fit, minmax(var(--info-min, 160px), 1fr));
|
|
14
|
+
gap: var(--u-space-lg, 16px) var(--u-space-xl, 20px);
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { t as styles };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
|
+
type ElementParts = 'host' | 'back' | 'heading' | 'title' | 'subtitle' | 'status' | 'actions';
|
|
3
|
+
/**
|
|
4
|
+
* 페이지 헤더 — 모든 LOB 화면 최상단의 같은 골격.
|
|
5
|
+
*
|
|
6
|
+
* ★**왜 이것이 라이브러리에 있나**: 이 골격은 화면마다 손으로 다시 짜인다. 손으로 짜면
|
|
7
|
+
* ⑴제목 크기가 화면마다 달라지고 ⑵액션 정렬이 미묘하게 어긋나고 ⑶좁은 화면 규칙이
|
|
8
|
+
* 빠진다. 셋 다 개별로는 사소해 보이지만, 화면을 옮겨 다니는 사용자에게는 *"제품이
|
|
9
|
+
* 하나로 만들어지지 않았다"* 로 읽힌다.
|
|
10
|
+
*
|
|
11
|
+
* ```html
|
|
12
|
+
* <u-page-header title="주문 G-2026-I-0629" subtitle="2026-02-24 접수" back="/orders">
|
|
13
|
+
* <span slot="status"><!-- 배지 --></span>
|
|
14
|
+
* <span slot="actions"><!-- 버튼 --></span>
|
|
15
|
+
* </u-page-header>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* 오버라이드: `part`(host·back·heading·title·subtitle·status·actions) + slot 치환.
|
|
19
|
+
* 값은 전부 토큰을 경유한다 — 리터럴을 두면 소비자가 밀도를 바꿀 수 없다.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PageHeader extends StyledElement<ElementParts> {
|
|
22
|
+
static styles: import('lit').CSSResultGroup[];
|
|
23
|
+
/** 페이지 제목. 타입 스케일의 `display` 단을 쓴다. */
|
|
24
|
+
title: string;
|
|
25
|
+
/** 제목 아래 보조 설명. 없으면 렌더하지 않는다. */
|
|
26
|
+
subtitle?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 뒤로가기 링크 주소. 주면 제목 왼쪽에 `← 목록` 형태로 나온다.
|
|
29
|
+
* ⚠텍스트는 `backLabel` 또는 locale 레지스트리로 바꾼다.
|
|
30
|
+
*/
|
|
31
|
+
back?: string;
|
|
32
|
+
/**
|
|
33
|
+
* 뒤로가기 링크 문구. 비우면 locale 레지스트리 값, 그것도 없으면 **영어**(`Back`).
|
|
34
|
+
* ⚠이 패키지는 범용 층이라 특정 언어를 기본값으로 가질 수 없다 —
|
|
35
|
+
* 한국어는 `registerLocale('ko', { back: '뒤로' })` 로 소비자가 등록한다.
|
|
36
|
+
*/
|
|
37
|
+
backLabel: string;
|
|
38
|
+
/** 언어 태그. 비우면 `setDefaultLocale()` 값, 그것도 없으면 영어. */
|
|
39
|
+
locale: string;
|
|
40
|
+
/**
|
|
41
|
+
* 슬롯 배정 상태. ★CSS 로는 알 수 없다 — `<slot>` 자신이 자식이라 `:has(*)` 가
|
|
42
|
+
* 항상 참이다(실브라우저로 확인). 빈 배지 자리가 남으면 제목 위치가 화면마다 달라진다.
|
|
43
|
+
*/
|
|
44
|
+
private hasStatus;
|
|
45
|
+
private hasActions;
|
|
46
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
47
|
+
}
|
|
48
|
+
declare global {
|
|
49
|
+
interface HTMLElementTagNameMap {
|
|
50
|
+
'u-page-header': PageHeader;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
3
|
+
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
|
+
import { getLocaleStrings as r } from "../internals/locale.js";
|
|
5
|
+
import { slotHasContent as i } from "../internals/slotted.js";
|
|
6
|
+
import { styles as a } from "./PageHeader.styles.js";
|
|
7
|
+
import { html as o, nothing as s } from "lit";
|
|
8
|
+
import { customElement as c, property as l, state as u } from "lit/decorators.js";
|
|
9
|
+
//#region src/components/PageHeader.ts
|
|
10
|
+
var d = class extends n {
|
|
11
|
+
constructor(...e) {
|
|
12
|
+
super(...e), this.title = "", this.backLabel = "", this.locale = "", this.hasStatus = !1, this.hasActions = !1;
|
|
13
|
+
}
|
|
14
|
+
static {
|
|
15
|
+
this.styles = [super.styles, a];
|
|
16
|
+
}
|
|
17
|
+
render() {
|
|
18
|
+
return o`
|
|
19
|
+
${this.back ? o`<a class="back" part="back" href=${this.back}>${this.backLabel || r(this.locale || void 0).back}</a>` : s}
|
|
20
|
+
<div class="heading" part="heading">
|
|
21
|
+
<div class="title-row">
|
|
22
|
+
<h1 class="title" part="title">${this.title}</h1>
|
|
23
|
+
<span class="status ${this.hasStatus ? "" : "empty"}" part="status">
|
|
24
|
+
<slot name="status"
|
|
25
|
+
@slotchange=${(e) => this.hasStatus = i(e.target)}
|
|
26
|
+
></slot>
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
${this.subtitle ? o`<p class="subtitle" part="subtitle">${this.subtitle}</p>` : s}
|
|
30
|
+
</div>
|
|
31
|
+
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
32
|
+
<slot name="actions"
|
|
33
|
+
@slotchange=${(e) => this.hasActions = i(e.target)}
|
|
34
|
+
></slot>
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
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({
|
|
40
|
+
type: String,
|
|
41
|
+
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);
|
|
43
|
+
//#endregion
|
|
44
|
+
export { d as PageHeader };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { css as e } from "lit";
|
|
2
|
+
//#region src/components/PageHeader.styles.ts
|
|
3
|
+
var t = e`
|
|
4
|
+
:host {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: flex-start;
|
|
7
|
+
gap: var(--u-space-md, 12px);
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
margin-bottom: var(--u-space-2xl, 24px);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.heading {
|
|
13
|
+
flex: 1 1 auto;
|
|
14
|
+
min-width: 0; /* 긴 제목이 액션을 밀어내지 않게 — 줄바꿈이 아니라 축소가 먼저다 */
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.title-row {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: var(--u-space-sm, 8px);
|
|
21
|
+
flex-wrap: wrap;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.title {
|
|
25
|
+
margin: 0;
|
|
26
|
+
font-size: var(--u-text-display-size, 26px);
|
|
27
|
+
font-weight: var(--u-text-display-weight, 700);
|
|
28
|
+
line-height: var(--u-text-display-leading, 1.4);
|
|
29
|
+
letter-spacing: var(--u-text-display-tracking, -0.02em);
|
|
30
|
+
color: var(--u-txt-color, #212121);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.subtitle {
|
|
34
|
+
margin: var(--u-space-2xs, 4px) 0 0;
|
|
35
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
36
|
+
font-weight: var(--u-text-caption-weight, 400);
|
|
37
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
38
|
+
color: var(--u-txt-color-weak, #757575);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.back {
|
|
42
|
+
flex: 0 0 auto;
|
|
43
|
+
align-self: center;
|
|
44
|
+
font-size: var(--u-text-label-size, 13px);
|
|
45
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
46
|
+
color: var(--u-link-txt-color, #1565C0);
|
|
47
|
+
text-decoration: none;
|
|
48
|
+
}
|
|
49
|
+
.back::before {
|
|
50
|
+
content: '←';
|
|
51
|
+
margin-right: var(--u-space-3xs, 2px);
|
|
52
|
+
}
|
|
53
|
+
.back:hover {
|
|
54
|
+
text-decoration: underline;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* 상태 슬롯이 비어 있으면 자리를 차지하지 않는다 — 빈 배지 자리가 제목을 밀면
|
|
58
|
+
같은 화면인데 제목 위치가 달라 보인다. */
|
|
59
|
+
/* 빈 슬롯 래퍼는 접는다.
|
|
60
|
+
⚠**CSS ':has()' 로는 못 한다** — <slot> 자신이 자식 요소라 ':has(*)' 가 항상 참이다
|
|
61
|
+
(실브라우저 테스트로 확인했다. 소스 검사와 jsdom 은 둘 다 통과시킨다).
|
|
62
|
+
배정 상태는 'slotchange' 로 추적해 '.empty' 클래스로 내려온다 — internals/slotted.ts */
|
|
63
|
+
.status.empty {
|
|
64
|
+
display: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.actions {
|
|
68
|
+
flex: 0 0 auto;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: var(--u-space-sm, 8px);
|
|
72
|
+
margin-left: auto;
|
|
73
|
+
}
|
|
74
|
+
/* 빈 액션 래퍼는 접는다(슬롯 배정은 'slotchange' 로 추적 — internals/slotted.ts). */
|
|
75
|
+
.actions.empty {
|
|
76
|
+
display: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* 좁은 화면: 액션이 제목 아래로 내려가고 **왼쪽 정렬**이 된다.
|
|
80
|
+
★오른쪽 정렬을 유지하면 손가락이 닿기 어려운 쪽으로 몰린다. */
|
|
81
|
+
@media (max-width: 640px) {
|
|
82
|
+
.actions {
|
|
83
|
+
margin-left: 0;
|
|
84
|
+
width: 100%;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { t as styles };
|
|
@@ -3,7 +3,7 @@ import { css as e } from "lit";
|
|
|
3
3
|
var t = e`
|
|
4
4
|
:host {
|
|
5
5
|
/* SidebarLink 과 같은 원천 — 그룹 강조는 활성 링크와 같은 색 계열이어야 한다 */
|
|
6
|
-
--group-accent: var(--app-sidebar-active-bg, var(--u-primary-color, #
|
|
6
|
+
--group-accent: var(--app-sidebar-active-bg, var(--u-primary-color, #1976D2));
|
|
7
7
|
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
@@ -36,7 +36,7 @@ var t = e`
|
|
|
36
36
|
color: color-mix(in srgb, var(--group-accent) 85%, black);
|
|
37
37
|
}
|
|
38
38
|
button:hover {
|
|
39
|
-
color: var(--u-txt-color-hover, #
|
|
39
|
+
color: var(--u-txt-color-hover, #1565C0);
|
|
40
40
|
background-color: var(--u-bg-color-hover, #F5F5F5);
|
|
41
41
|
}
|
|
42
42
|
button:active {
|
|
@@ -6,7 +6,7 @@ var t = e`
|
|
|
6
6
|
역할 토큰(--u-primary-color) 어느 쪽을 덮어도 hover 파생까지 함께 따라온다.
|
|
7
7
|
팔레트(--u-blue-600)를 직접 읽으면 브랜드를 바꾸려는 소비자가 "진짜 파랑"이
|
|
8
8
|
필요한 배지·차트까지 함께 오염시키는 길밖에 없다. */
|
|
9
|
-
--link-active-bg: var(--app-sidebar-active-bg, var(--u-primary-color, #
|
|
9
|
+
--link-active-bg: var(--app-sidebar-active-bg, var(--u-primary-color, #1976D2));
|
|
10
10
|
|
|
11
11
|
display: block;
|
|
12
12
|
color: var(--app-sidebar-fg, var(--u-txt-color, #212121));
|
|
@@ -16,7 +16,7 @@ var t = e`
|
|
|
16
16
|
cursor: pointer;
|
|
17
17
|
}
|
|
18
18
|
:host(:hover) {
|
|
19
|
-
color: var(--u-txt-color-hover, #
|
|
19
|
+
color: var(--u-txt-color-hover, #1565C0);
|
|
20
20
|
background-color: var(--u-bg-color-hover, #F5F5F5);
|
|
21
21
|
}
|
|
22
22
|
:host([selected]) {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,12 @@ export type * from './types/AppOptions';
|
|
|
4
4
|
export type * from './types/AuthConfig';
|
|
5
5
|
export type { SidebarPermissionGuard } from './layouts/SidebarPermission';
|
|
6
6
|
export { filterSidebarItems } from './layouts/filterSidebarItems.js';
|
|
7
|
+
export { PageHeader } from './components/PageHeader.js';
|
|
8
|
+
export { GroupBox } from './components/GroupBox.js';
|
|
9
|
+
export { InfoSection } from './components/InfoSection.js';
|
|
10
|
+
export { InfoField, isBlank } from './components/InfoField.js';
|
|
11
|
+
export { EmptyState } from './components/EmptyState.js';
|
|
12
|
+
export { registerLocale, setDefaultLocale, getLocaleStrings, getDefaultLocale, } from './internals/locale.js';
|
|
13
|
+
export type { ModernAppLocaleStrings } from './internals/locale.js';
|
|
7
14
|
export { app };
|
|
8
15
|
export default app;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { app as e } from "./App.js";
|
|
2
2
|
import { filterSidebarItems as t } from "./layouts/filterSidebarItems.js";
|
|
3
|
+
import { getDefaultLocale as n, getLocaleStrings as r, registerLocale as i, setDefaultLocale as a } from "./internals/locale.js";
|
|
4
|
+
import { PageHeader as o } from "./components/PageHeader.js";
|
|
5
|
+
import { GroupBox as s } from "./components/GroupBox.js";
|
|
6
|
+
import { InfoSection as c } from "./components/InfoSection.js";
|
|
7
|
+
import { InfoField as l, isBlank as u } from "./components/InfoField.js";
|
|
8
|
+
import { EmptyState as d } from "./components/EmptyState.js";
|
|
3
9
|
//#region src/index.ts
|
|
4
|
-
var
|
|
10
|
+
var f = e;
|
|
5
11
|
//#endregion
|
|
6
|
-
export { e as app,
|
|
12
|
+
export { d as EmptyState, s as GroupBox, l as InfoField, c as InfoSection, o as PageHeader, e as app, f as default, t as filterSidebarItems, n as getDefaultLocale, r as getLocaleStrings, u as isBlank, i as registerLocale, a as setDefaultLocale };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight locale registry for modern-app chrome strings.
|
|
3
|
+
*
|
|
4
|
+
* Scope: library-internal chrome strings only (empty-state copy, back label).
|
|
5
|
+
* NOT a general i18n framework — the app's own content is the app's business.
|
|
6
|
+
*
|
|
7
|
+
* ★**English is the built-in default.** This package sits in the *generic* layer
|
|
8
|
+
* (see the three-layer split: `components` = neutral primitives, `modern-app` =
|
|
9
|
+
* what a modern app usually provides, `enterprise` = iyulab house style).
|
|
10
|
+
* A generic layer that ships Korean defaults excludes every non-Korean consumer —
|
|
11
|
+
* the same objection this repo raised against baking a house aesthetic into
|
|
12
|
+
* `components`. Language excludes harder than aesthetics: an odd-looking default
|
|
13
|
+
* is awkward, an unreadable one is unusable.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors the registry already established in `u-widgets` — one pattern, not two.
|
|
16
|
+
*
|
|
17
|
+
* registerLocale('ko', { back: '뒤로', noDataTitle: '아직 등록된 항목이 없습니다' });
|
|
18
|
+
* setDefaultLocale('ko');
|
|
19
|
+
*/
|
|
20
|
+
export interface ModernAppLocaleStrings {
|
|
21
|
+
/** Back button label in the page header. */
|
|
22
|
+
back: string;
|
|
23
|
+
/** Empty state — nothing exists yet. */
|
|
24
|
+
noDataTitle: string;
|
|
25
|
+
noDataDescription: string;
|
|
26
|
+
/** Empty state — records exist but the current query matched none. */
|
|
27
|
+
noResultsTitle: string;
|
|
28
|
+
noResultsDescription: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register a locale. Partial overrides are merged with the English defaults,
|
|
32
|
+
* so a consumer can translate one string without restating the rest.
|
|
33
|
+
*/
|
|
34
|
+
export declare function registerLocale(lang: string, strings: Partial<ModernAppLocaleStrings>): void;
|
|
35
|
+
/** Set the locale used when a component does not specify one. */
|
|
36
|
+
export declare function setDefaultLocale(lang: string | undefined): void;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve strings for a language tag.
|
|
39
|
+
*
|
|
40
|
+
* Order: exact match (`ko-KR`) → base language (`ko`) → English.
|
|
41
|
+
* ⚠The base-language step matters: consumers register `ko` but browsers report
|
|
42
|
+
* `ko-KR`, and without it every registration would silently miss.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getLocaleStrings(lang?: string): ModernAppLocaleStrings;
|
|
45
|
+
/** The English defaults — for tests and reference. */
|
|
46
|
+
export declare function getDefaultLocale(): ModernAppLocaleStrings;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/internals/locale.ts
|
|
2
|
+
var e = {
|
|
3
|
+
back: "Back",
|
|
4
|
+
noDataTitle: "Nothing here yet",
|
|
5
|
+
noDataDescription: "",
|
|
6
|
+
noResultsTitle: "No matching results",
|
|
7
|
+
noResultsDescription: "Try changing your search terms or filters."
|
|
8
|
+
}, t = /* @__PURE__ */ new Map(), n;
|
|
9
|
+
function r(n, r) {
|
|
10
|
+
t.set(n.toLowerCase(), {
|
|
11
|
+
...e,
|
|
12
|
+
...r
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function i(e) {
|
|
16
|
+
n = e;
|
|
17
|
+
}
|
|
18
|
+
function a(r) {
|
|
19
|
+
let i = (r ?? n)?.toLowerCase();
|
|
20
|
+
if (!i) return e;
|
|
21
|
+
if (t.has(i)) return t.get(i);
|
|
22
|
+
let a = i.split("-")[0];
|
|
23
|
+
return a !== i && t.has(a) ? t.get(a) : e;
|
|
24
|
+
}
|
|
25
|
+
function o() {
|
|
26
|
+
return { ...e };
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { o as getDefaultLocale, a as getLocaleStrings, r as registerLocale, i as setDefaultLocale };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 슬롯에 **실제로 배정된 내용이 있는가**.
|
|
3
|
+
*
|
|
4
|
+
* ★**CSS 로는 알 수 없다.** `.wrapper:not(:has(*))` 로 빈 슬롯 래퍼를 접으려는 시도는
|
|
5
|
+
* 실패한다 — `<slot>` **자신이 자식 요소**이므로 `:has(*)` 가 항상 참이기 때문이다.
|
|
6
|
+
* 실브라우저 테스트로 확인했다(소스 검사와 jsdom 은 둘 다 통과시킨다).
|
|
7
|
+
* `::slotted()` 는 배정된 자식을 칠할 수는 있어도 **부모를 선택하지 못한다.**
|
|
8
|
+
* ⇒ 배정 상태는 `slotchange` 로 추적하는 수밖에 없다.
|
|
9
|
+
*
|
|
10
|
+
* 빈 텍스트 노드는 내용으로 치지 않는다 — 마크업을 여러 줄로 쓰면 공백 텍스트 노드가
|
|
11
|
+
* 생기고, 그것을 내용으로 세면 **줄바꿈 여부에 따라 레이아웃이 달라진다.**
|
|
12
|
+
*/
|
|
13
|
+
export declare function slotHasContent(slot: HTMLSlotElement | null | undefined): boolean;
|
|
@@ -97,10 +97,10 @@ var g = class extends i {
|
|
|
97
97
|
<span class="title">
|
|
98
98
|
${this.config.title}
|
|
99
99
|
</span>
|
|
100
|
-
<u-button class="toggler"
|
|
100
|
+
<u-button class="toggler" variant="ghost"
|
|
101
101
|
@click=${this.handleToggleButtonClick}>
|
|
102
|
-
<u-icon
|
|
103
|
-
lib="bootstrap"
|
|
102
|
+
<u-icon
|
|
103
|
+
lib="bootstrap"
|
|
104
104
|
name=${this.state === "mobile-open" ? "x-lg" : "list"}
|
|
105
105
|
></u-icon>
|
|
106
106
|
</u-button>
|
|
@@ -114,10 +114,10 @@ var g = class extends i {
|
|
|
114
114
|
<span class="title" ?hidden=${this.state === "slim"}>
|
|
115
115
|
${this.config.title}
|
|
116
116
|
</span>
|
|
117
|
-
<u-button class="toggler"
|
|
117
|
+
<u-button class="toggler" variant="ghost"
|
|
118
118
|
@click=${this.handleToggleButtonClick}>
|
|
119
|
-
<u-icon
|
|
120
|
-
lib="bootstrap"
|
|
119
|
+
<u-icon
|
|
120
|
+
lib="bootstrap"
|
|
121
121
|
name="layout-sidebar"
|
|
122
122
|
></u-icon>
|
|
123
123
|
</u-button>
|
|
@@ -21,7 +21,7 @@ var t = e`
|
|
|
21
21
|
cursor: pointer;
|
|
22
22
|
}
|
|
23
23
|
.logo:hover {
|
|
24
|
-
color: var(--u-txt-color-hover, #
|
|
24
|
+
color: var(--u-txt-color-hover, #1565C0);
|
|
25
25
|
}
|
|
26
26
|
img.logo {
|
|
27
27
|
height: 24px;
|
|
@@ -40,15 +40,24 @@ var t = e`
|
|
|
40
40
|
text-overflow: ellipsis;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/*
|
|
44
|
+
* 토글러의 "테두리·배경 없음"은 마크업의 variant="ghost" 가 낸다.
|
|
45
|
+
*
|
|
46
|
+
* ★여기에 border/background 를 적는 것으로는 부족했다 — 이 규칙은 <u-button> **호스트**에만
|
|
47
|
+
* 닿고, 테두리를 실제로 그리는 것은 그 섀도 루트 안의 <button> 이다(solid 기본값이
|
|
48
|
+
* --btn-border-color: var(--btn-color) 로 그린다). 호스트만 재면 0px 이라 보이지도 않았다.
|
|
49
|
+
* variant 를 명시해 컴포넌트가 스스로 투명하게 그리도록 한다.
|
|
50
|
+
*
|
|
51
|
+
* color: inherit 는 유지한다 — 헤더 색을 따라가게 하려는 의도이고 ghost 의 기본 전경
|
|
52
|
+
* (--u-txt-color)보다 이 레이아웃의 뜻이 앞선다.
|
|
53
|
+
*/
|
|
43
54
|
.toggler {
|
|
44
55
|
color: inherit;
|
|
45
56
|
font-size: 20px;
|
|
46
57
|
padding: 0px;
|
|
47
|
-
border: none;
|
|
48
|
-
background: transparent;
|
|
49
58
|
}
|
|
50
59
|
.toggler:hover {
|
|
51
|
-
color: var(--u-txt-color-hover, #
|
|
60
|
+
color: var(--u-txt-color-hover, #1565C0);
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
/* Sidebar Container */
|
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.9.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"test:browser": "vitest run --project=browser"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@iyulab/components": "^1.
|
|
44
|
+
"@iyulab/components": "^1.21.0",
|
|
45
45
|
"@iyulab/router": "^0.10.2",
|
|
46
46
|
"i18next": "^26.3.6",
|
|
47
47
|
"lit": "^3.3.3"
|