@iyulab/components 1.22.0 → 1.23.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 +180 -0
- package/README.md +32 -0
- package/dist/components/UOverlayElement.d.ts +18 -0
- package/dist/components/UOverlayElement.js +24 -0
- package/dist/components/badge/UBadge.d.ts +5 -0
- package/dist/components/badge/UBadge.js +9 -0
- package/dist/components/badge/UBadge.styles.js +6 -0
- package/dist/components/dialog/UDialog.styles.js +1 -1
- package/dist/components/drawer/UDrawer.styles.js +1 -1
- package/dist/components/expander/UExpander.d.ts +38 -0
- package/dist/components/expander/UExpander.js +78 -0
- package/dist/components/expander/UExpander.styles.d.ts +1 -0
- package/dist/components/expander/UExpander.styles.js +95 -0
- package/dist/components/panel/UPanel.d.ts +0 -2
- package/dist/components/panel/UPanel.js +0 -5
- package/dist/components/skeleton/USkeleton.d.ts +7 -0
- package/dist/components/skeleton/USkeleton.js +7 -1
- package/dist/components/skeleton/USkeleton.styles.js +64 -0
- package/dist/components/tab-panel/UTabPanel.d.ts +4 -2
- package/dist/components/tab-panel/UTabPanel.js +0 -5
- package/dist/components/tag/UTag.d.ts +7 -0
- package/dist/components/tag/UTag.js +10 -1
- package/dist/components/tag/UTag.styles.js +4 -0
- package/dist/components/tree/UTree.d.ts +4 -2
- package/dist/components/tree/UTree.js +0 -5
- package/dist/components/tree-item/UTreeItem.d.ts +4 -1
- package/dist/components/tree-item/UTreeItem.js +0 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/react/UExpander.d.ts +16 -0
- package/dist/react/UExpander.js +13 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/utilities/Dialog.js +1 -1
- package/dist/utilities/Locale.d.ts +34 -0
- package/dist/utilities/Locale.js +43 -6
- package/dist/utilities/Theme.d.ts +24 -0
- package/dist/utilities/Theme.js +52 -0
- package/dist/utilities/accent.d.ts +52 -0
- package/dist/utilities/accent.js +127 -0
- package/dist/utilities/icons.js +1 -1
- package/dist/utilities/statusIcon.d.ts +31 -0
- package/dist/utilities/statusIcon.js +34 -0
- package/package.json +3 -2
- package/skills/iyulab-components/SKILL.md +2 -1
- package/skills/iyulab-components/references/components/badge.md +2 -1
- package/skills/iyulab-components/references/components/button.md +3 -1
- package/skills/iyulab-components/references/components/drawer.md +30 -0
- package/skills/iyulab-components/references/components/expander.md +70 -0
- package/skills/iyulab-components/references/components/input.md +6 -0
- package/skills/iyulab-components/references/components/panel.md +6 -4
- package/skills/iyulab-components/references/components/skeleton.md +12 -0
- package/skills/iyulab-components/references/components/tab-panel.md +4 -5
- package/skills/iyulab-components/references/components/tag.md +18 -1
- package/skills/iyulab-components/references/components/textarea.md +5 -0
- package/skills/iyulab-components/references/components/tree.md +2 -4
- package/skills/iyulab-components/references/usage.md +22 -2
- package/skills/iyulab-components/references/utilities/theme.md +23 -4
|
@@ -52,6 +52,70 @@ var styles = css`
|
|
|
52
52
|
transform: translateX(-100%);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/* === 여러 줄(lines) ===
|
|
56
|
+
기본(단일 막대)에서는 :host 자신이 막대다 — 그 모양을 바꾸지 않는다. lines 가 붙었을
|
|
57
|
+
때만 호스트가 «막대들의 통»이 되고, 색·모양·효과는 각 .line 이 받는다.
|
|
58
|
+
⚠마지막 줄을 짧게 만드는 것은 장식이 아니라 신호다 — 문단의 끝을 그렇게 읽는다. */
|
|
59
|
+
:host([lines]) {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: var(--skeleton-line-gap, 0.5em);
|
|
63
|
+
height: auto;
|
|
64
|
+
background-color: transparent;
|
|
65
|
+
animation: none;
|
|
66
|
+
overflow: visible;
|
|
67
|
+
}
|
|
68
|
+
:host([lines])::after {
|
|
69
|
+
content: none;
|
|
70
|
+
}
|
|
71
|
+
:host([lines]) .line {
|
|
72
|
+
height: var(--skeleton-height);
|
|
73
|
+
background-color: var(--skeleton-color);
|
|
74
|
+
border-radius: inherit;
|
|
75
|
+
}
|
|
76
|
+
:host([lines]) .line:last-child {
|
|
77
|
+
width: var(--skeleton-last-line-width, 60%);
|
|
78
|
+
}
|
|
79
|
+
:host([lines][effect="pulse"]) .line {
|
|
80
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
81
|
+
}
|
|
82
|
+
:host([lines][effect="shimmer"]) .line {
|
|
83
|
+
position: relative;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
:host([lines][effect="shimmer"]) .line::after {
|
|
87
|
+
content: '';
|
|
88
|
+
position: absolute;
|
|
89
|
+
inset: 0;
|
|
90
|
+
background: linear-gradient(
|
|
91
|
+
90deg,
|
|
92
|
+
transparent,
|
|
93
|
+
var(--skeleton-shimmer-color),
|
|
94
|
+
transparent
|
|
95
|
+
);
|
|
96
|
+
animation: shimmer 1.5s infinite;
|
|
97
|
+
transform: translateX(-100%);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ★**장식만 멈춘다.**
|
|
101
|
+
시트의 reduce 규칙은 지속시간 축(--u-duration-*)을 0 으로 누르는 방식이라 animation
|
|
102
|
+
속성을 직접 쓰는 이 자리에는 닿지 않는다 — 그 규칙이 animation: none 으로 강제하지
|
|
103
|
+
않는 것은 의도다(로딩 스피너처럼 «회전 자체가 신호»인 움직임까지 죽이면 진행 여부를
|
|
104
|
+
알 수 없다).
|
|
105
|
+
|
|
106
|
+
스켈레톤은 다르다: **기본값이 이미 animation: none** 이고, 정지한 회색 블록이
|
|
107
|
+
«로딩 중»을 그대로 나른다. 즉 pulse/shimmer 는 **opt-in 장식**이고, WCAG 2.2.2 의
|
|
108
|
+
«움직임이 본질인 것» 예외에 해당하지 않는다.
|
|
109
|
+
⇒ 여기서만 멈춘다. u-spinner 는 건드리지 않는다. */
|
|
110
|
+
@media (prefers-reduced-motion: reduce) {
|
|
111
|
+
:host([effect="pulse"]),
|
|
112
|
+
:host([effect="shimmer"])::after,
|
|
113
|
+
:host([lines][effect="pulse"]) .line,
|
|
114
|
+
:host([lines][effect="shimmer"]) .line::after {
|
|
115
|
+
animation: none;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
55
119
|
@keyframes pulse {
|
|
56
120
|
0%, 100% {
|
|
57
121
|
opacity: 1;
|
|
@@ -29,8 +29,10 @@ export declare class UTabPanel extends UElement {
|
|
|
29
29
|
placement: TabPanelPlacement;
|
|
30
30
|
/** 선택된 탭값 */
|
|
31
31
|
value: string;
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* 네이티브 전역 속성. ⚠**이 컴포넌트에 탭 재정렬 기능은 없다** — 브라우저의 드래그를
|
|
34
|
+
* 켤 뿐이고, 놓았을 때 순서를 바꾸는 코드는 존재한 적이 없다.
|
|
35
|
+
*/
|
|
34
36
|
draggable: boolean;
|
|
35
37
|
private tabs;
|
|
36
38
|
private panels;
|
|
@@ -14,7 +14,6 @@ var UTabPanel = class UTabPanel extends UElement {
|
|
|
14
14
|
this.variant = "line";
|
|
15
15
|
this.placement = "top";
|
|
16
16
|
this.value = "";
|
|
17
|
-
this.editable = false;
|
|
18
17
|
this.draggable = false;
|
|
19
18
|
this.tabs = [];
|
|
20
19
|
this.panels = [];
|
|
@@ -144,10 +143,6 @@ __decorate([property({
|
|
|
144
143
|
type: String,
|
|
145
144
|
reflect: true
|
|
146
145
|
}), __decorateMetadata("design:type", Object)], UTabPanel.prototype, "value", void 0);
|
|
147
|
-
__decorate([property({
|
|
148
|
-
type: Boolean,
|
|
149
|
-
reflect: true
|
|
150
|
-
}), __decorateMetadata("design:type", Object)], UTabPanel.prototype, "editable", void 0);
|
|
151
146
|
__decorate([property({
|
|
152
147
|
type: Boolean,
|
|
153
148
|
reflect: true
|
|
@@ -14,6 +14,7 @@ export type TagColor = "neutral" | "primary" | "info" | "success" | "warning" |
|
|
|
14
14
|
* @slot suffix - 태그 뒤에 표시할 콘텐츠
|
|
15
15
|
*
|
|
16
16
|
* @csspart content - 콘텐츠 영역
|
|
17
|
+
* @csspart icon - 상태 아이콘 (`icon` + 역할 색일 때만 렌더된다)
|
|
17
18
|
*
|
|
18
19
|
* @cssprop --tag-color - 텍스트 색상
|
|
19
20
|
* @cssprop --tag-bg-color - 배경 색상
|
|
@@ -31,6 +32,12 @@ export declare class UTag extends UElement {
|
|
|
31
32
|
color: TagColor;
|
|
32
33
|
/** 태그를 둥글게 표시합니다. */
|
|
33
34
|
rounded: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 상태를 **색과 무관하게** 나르는 아이콘을 붙입니다(`info`·`success`·`warning`·`danger`).
|
|
37
|
+
* 색각 이상·흑백 인쇄에서 상태가 갈리게 하는 축입니다.
|
|
38
|
+
* 의미가 없는 색(`neutral`·`primary`·장식 축)에서는 아무것도 그리지 않습니다.
|
|
39
|
+
*/
|
|
40
|
+
icon: boolean;
|
|
34
41
|
render(): import('lit-html').TemplateResult<1>;
|
|
35
42
|
}
|
|
36
43
|
declare global {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { UElement } from "../UElement.js";
|
|
2
2
|
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
4
|
+
import "../icon/UIcon.js";
|
|
5
|
+
import { statusIcon } from "../../utilities/statusIcon.js";
|
|
4
6
|
import { styles } from "./UTag.styles.js";
|
|
5
|
-
import { html } from "lit";
|
|
7
|
+
import { html, nothing } from "lit";
|
|
6
8
|
import { customElement, property } from "lit/decorators.js";
|
|
7
9
|
//#region src/components/tag/UTag.ts
|
|
8
10
|
var UTag = class UTag extends UElement {
|
|
@@ -11,13 +13,16 @@ var UTag = class UTag extends UElement {
|
|
|
11
13
|
this.variant = "filled";
|
|
12
14
|
this.color = "neutral";
|
|
13
15
|
this.rounded = false;
|
|
16
|
+
this.icon = false;
|
|
14
17
|
}
|
|
15
18
|
static {
|
|
16
19
|
this.styles = [super.styles, styles];
|
|
17
20
|
}
|
|
18
21
|
render() {
|
|
22
|
+
const iconName = this.icon ? statusIcon(this.color) : void 0;
|
|
19
23
|
return html`
|
|
20
24
|
<div class="base" part="base">
|
|
25
|
+
${iconName ? html`<u-icon class="icon" part="icon" lib="internal" name=${iconName}></u-icon>` : nothing}
|
|
21
26
|
<slot name="prefix"></slot>
|
|
22
27
|
<span class="content" part="content">
|
|
23
28
|
<slot></slot>
|
|
@@ -39,6 +44,10 @@ __decorate([property({
|
|
|
39
44
|
type: Boolean,
|
|
40
45
|
reflect: true
|
|
41
46
|
}), __decorateMetadata("design:type", Object)], UTag.prototype, "rounded", void 0);
|
|
47
|
+
__decorate([property({
|
|
48
|
+
type: Boolean,
|
|
49
|
+
reflect: true
|
|
50
|
+
}), __decorateMetadata("design:type", Object)], UTag.prototype, "icon", void 0);
|
|
42
51
|
UTag = __decorate([customElement("u-tag")], UTag);
|
|
43
52
|
//#endregion
|
|
44
53
|
export { UTag };
|
|
@@ -189,6 +189,10 @@ var styles = css`
|
|
|
189
189
|
border: 1px solid var(--tag-border-color);
|
|
190
190
|
border-radius: inherit;
|
|
191
191
|
}
|
|
192
|
+
/* 상태 아이콘 — .base 의 gap 이 간격을 정한다. */
|
|
193
|
+
.icon {
|
|
194
|
+
flex-shrink: 0;
|
|
195
|
+
}
|
|
192
196
|
:host([rounded]) {
|
|
193
197
|
border-radius: var(--u-radius-pill, 9999px);
|
|
194
198
|
}
|
|
@@ -34,9 +34,11 @@ export declare class UTree extends UElement {
|
|
|
34
34
|
checkCascade: boolean;
|
|
35
35
|
/** 트리 펼치기 트리거 방식 */
|
|
36
36
|
trigger: TreeItemTrigger;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* 네이티브 전역 속성. ⚠**이 컴포넌트에 드래그 앤드 드롭 구현은 없다** — 브라우저의
|
|
39
|
+
* 드래그를 켤 뿐이고, 드롭을 받아 트리를 재배치하는 코드는 존재한 적이 없다.
|
|
40
|
+
*/
|
|
38
41
|
draggable: boolean;
|
|
39
|
-
droppable: boolean;
|
|
40
42
|
private expandIcon?;
|
|
41
43
|
private collapseIcon?;
|
|
42
44
|
private anchor;
|
|
@@ -16,7 +16,6 @@ var UTree = class UTree extends UElement {
|
|
|
16
16
|
this.checkCascade = false;
|
|
17
17
|
this.trigger = "item";
|
|
18
18
|
this.draggable = false;
|
|
19
|
-
this.droppable = false;
|
|
20
19
|
this.anchor = null;
|
|
21
20
|
this._items = [];
|
|
22
21
|
this.handleSlotChange = (e) => {
|
|
@@ -300,10 +299,6 @@ __decorate([property({
|
|
|
300
299
|
type: Boolean,
|
|
301
300
|
reflect: true
|
|
302
301
|
}), __decorateMetadata("design:type", Boolean)], UTree.prototype, "draggable", void 0);
|
|
303
|
-
__decorate([property({
|
|
304
|
-
type: Boolean,
|
|
305
|
-
reflect: true
|
|
306
|
-
}), __decorateMetadata("design:type", Boolean)], UTree.prototype, "droppable", void 0);
|
|
307
302
|
__decorate([state(), __decorateMetadata("design:type", typeof Node === "undefined" ? Object : Node)], UTree.prototype, "expandIcon", void 0);
|
|
308
303
|
__decorate([state(), __decorateMetadata("design:type", typeof Node === "undefined" ? Object : Node)], UTree.prototype, "collapseIcon", void 0);
|
|
309
304
|
UTree = __decorate([customElement("u-tree")], UTree);
|
|
@@ -35,8 +35,11 @@ export declare class UTreeItem extends UElement {
|
|
|
35
35
|
trigger: TreeItemTrigger;
|
|
36
36
|
/** 아이템의 고유값 */
|
|
37
37
|
value: string;
|
|
38
|
+
/**
|
|
39
|
+
* 네이티브 전역 속성. ⚠**드롭을 받아 트리를 재배치하는 구현은 없다** — `UTree` 의 같은
|
|
40
|
+
* 속성과 같은 상태다.
|
|
41
|
+
*/
|
|
38
42
|
draggable: boolean;
|
|
39
|
-
droppable: boolean;
|
|
40
43
|
leaf: boolean;
|
|
41
44
|
depth: number;
|
|
42
45
|
selected: boolean;
|
|
@@ -22,7 +22,6 @@ var UTreeItem = class UTreeItem extends UElement {
|
|
|
22
22
|
this.trigger = "item";
|
|
23
23
|
this.value = "";
|
|
24
24
|
this.draggable = false;
|
|
25
|
-
this.droppable = false;
|
|
26
25
|
this.leaf = true;
|
|
27
26
|
this.depth = 0;
|
|
28
27
|
this.selected = false;
|
|
@@ -185,10 +184,6 @@ __decorate([property({
|
|
|
185
184
|
type: Boolean,
|
|
186
185
|
reflect: true
|
|
187
186
|
}), __decorateMetadata("design:type", Boolean)], UTreeItem.prototype, "draggable", void 0);
|
|
188
|
-
__decorate([property({
|
|
189
|
-
type: Boolean,
|
|
190
|
-
reflect: true
|
|
191
|
-
}), __decorateMetadata("design:type", Boolean)], UTreeItem.prototype, "droppable", void 0);
|
|
192
187
|
__decorate([state(), __decorateMetadata("design:type", Boolean)], UTreeItem.prototype, "leaf", void 0);
|
|
193
188
|
__decorate([state(), __decorateMetadata("design:type", Number)], UTreeItem.prototype, "depth", void 0);
|
|
194
189
|
__decorate([state(), __decorateMetadata("design:type", Boolean)], UTreeItem.prototype, "selected", void 0);
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from './components/copy-button/UCopyButton.js';
|
|
|
17
17
|
export * from './components/dialog/UDialog.js';
|
|
18
18
|
export * from './components/divider/UDivider.js';
|
|
19
19
|
export * from './components/drawer/UDrawer.js';
|
|
20
|
+
export * from './components/expander/UExpander.js';
|
|
20
21
|
export * from './components/field/UField.js';
|
|
21
22
|
export * from './components/form/UForm.js';
|
|
22
23
|
export * from './components/icon/UIcon.js';
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import { UCopyButton } from "./components/copy-button/UCopyButton.js";
|
|
|
27
27
|
import { UDialog } from "./components/dialog/UDialog.js";
|
|
28
28
|
import { UDivider } from "./components/divider/UDivider.js";
|
|
29
29
|
import { UDrawer } from "./components/drawer/UDrawer.js";
|
|
30
|
+
import { UExpander } from "./components/expander/UExpander.js";
|
|
30
31
|
import { UField } from "./components/field/UField.js";
|
|
31
32
|
import { UForm } from "./components/form/UForm.js";
|
|
32
33
|
import { UOption } from "./components/option/UOption.js";
|
|
@@ -53,4 +54,4 @@ import { BrowserStorage } from "./utilities/BrowserStorage.js";
|
|
|
53
54
|
import { Dialog } from "./utilities/Dialog.js";
|
|
54
55
|
import { Theme } from "./utilities/Theme.js";
|
|
55
56
|
import { Toast } from "./utilities/Toast.js";
|
|
56
|
-
export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDialog, UDivider, UDrawer, UElement, UField, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, getDefaultBaseUrl, getParentElement, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
|
|
57
|
+
export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, getDefaultBaseUrl, getParentElement, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { UExpander as UExpanderElement } from '../components/expander/UExpander';
|
|
3
|
+
import { type ExpandEventDetail } from '../events/ExpandEvent';
|
|
4
|
+
import { type CollapseEventDetail } from '../events/CollapseEvent';
|
|
5
|
+
|
|
6
|
+
export declare const UExpander: React.ForwardRefExoticComponent<
|
|
7
|
+
Omit<Partial<UExpanderElement>, keyof React.HTMLAttributes<UExpanderElement>>
|
|
8
|
+
& Omit<React.HTMLAttributes<UExpanderElement>, 'onExpand' | 'onCollapse'>
|
|
9
|
+
& React.RefAttributes<UExpanderElement>
|
|
10
|
+
& {
|
|
11
|
+
onExpand?: (event: CustomEvent<ExpandEventDetail>) => void;
|
|
12
|
+
onCollapse?: (event: CustomEvent<CollapseEventDetail>) => void;
|
|
13
|
+
}
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
export type UExpanderProps = React.ComponentProps<typeof UExpander>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createComponent } from '@lit/react';
|
|
3
|
+
import { UExpander as UExpanderElement } from '../components/expander/UExpander.js';
|
|
4
|
+
|
|
5
|
+
export const UExpander = createComponent({
|
|
6
|
+
react: React,
|
|
7
|
+
tagName: 'u-expander',
|
|
8
|
+
elementClass: UExpanderElement,
|
|
9
|
+
events: {
|
|
10
|
+
onExpand: 'expand',
|
|
11
|
+
onCollapse: 'collapse',
|
|
12
|
+
},
|
|
13
|
+
});
|
package/dist/react/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { UIconButton, UIconButtonProps } from './UIconButton';
|
|
|
25
25
|
export { UIcon, UIconProps } from './UIcon';
|
|
26
26
|
export { UForm, UFormProps } from './UForm';
|
|
27
27
|
export { UField, UFieldProps } from './UField';
|
|
28
|
+
export { UExpander, UExpanderProps } from './UExpander';
|
|
28
29
|
export { UDrawer, UDrawerProps } from './UDrawer';
|
|
29
30
|
export { UDivider, UDividerProps } from './UDivider';
|
|
30
31
|
export { UDialog, UDialogProps } from './UDialog';
|
package/dist/react/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export { UIconButton } from './UIconButton.js';
|
|
|
25
25
|
export { UIcon } from './UIcon.js';
|
|
26
26
|
export { UForm } from './UForm.js';
|
|
27
27
|
export { UField } from './UField.js';
|
|
28
|
+
export { UExpander } from './UExpander.js';
|
|
28
29
|
export { UDrawer } from './UDrawer.js';
|
|
29
30
|
export { UDivider } from './UDivider.js';
|
|
30
31
|
export { UDialog } from './UDialog.js';
|
package/dist/utilities/Dialog.js
CHANGED
|
@@ -14,6 +14,31 @@ export type LocaleTag = SupportedLocale | (string & {});
|
|
|
14
14
|
/** 라이브러리가 제공하는 chrome 문자열 키. 지금은 검증 메시지뿐이지만 이후 다른 UI 문구도 추가될 수 있다. */
|
|
15
15
|
export type LocaleMessageKey = 'valueMissing' | 'badInput' | 'typeMismatch' | 'patternMismatch' | 'rangeUnderflow' | 'rangeOverflow' | 'stepMismatch' | 'tooShort' | 'tooLong';
|
|
16
16
|
type LocaleTable = Record<LocaleMessageKey, string>;
|
|
17
|
+
/**
|
|
18
|
+
* 한 패키지(또는 앱 영역)의 문자열 묶음.
|
|
19
|
+
*
|
|
20
|
+
* 키 유니온을 **소비자가** 정하므로 라이브러리의 키셋은 커지지 않는다:
|
|
21
|
+
* ```ts
|
|
22
|
+
* const t = Locale.namespace<'empty' | 'loading'>('u-data-view');
|
|
23
|
+
* t.register('en', { empty: 'No data', loading: 'Loading…' });
|
|
24
|
+
* t.text('empty');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface LocaleNamespace<K extends string = string> {
|
|
28
|
+
/** 네임스페이스 이름. */
|
|
29
|
+
readonly name: string;
|
|
30
|
+
/**
|
|
31
|
+
* 로케일 하나의 테이블(전체 또는 일부)을 등록한다.
|
|
32
|
+
* 같은 로케일에 반복 호출하면 **병합**된다 — 일부 키만 넘겨도 나머지가 사라지지 않는다.
|
|
33
|
+
*/
|
|
34
|
+
register(locale: LocaleTag, table: Partial<Record<K, string>>): void;
|
|
35
|
+
/**
|
|
36
|
+
* 활성 로케일 기준으로 문자열을 찾는다.
|
|
37
|
+
* 사슬(정확 일치 → base → en)에 없으면 **키 자체를 돌려준다** — 조용히 빈 문자열이
|
|
38
|
+
* 되는 것보다 화면에 드러나는 편이 낫다.
|
|
39
|
+
*/
|
|
40
|
+
text(key: K, params?: Record<string, string | number>): string;
|
|
41
|
+
}
|
|
17
42
|
/**
|
|
18
43
|
* 검증 메시지 로케일을 관리하는 정적 유틸리티입니다.
|
|
19
44
|
*/
|
|
@@ -34,5 +59,14 @@ export declare class Locale {
|
|
|
34
59
|
* `params`가 있으면 템플릿의 `{name}` 자리를 치환합니다.
|
|
35
60
|
*/
|
|
36
61
|
static getValue(key: LocaleMessageKey, params?: Record<string, string | number>): string;
|
|
62
|
+
/**
|
|
63
|
+
* 네임스페이스 핸들을 얻습니다 — 같은 이름이면 **같은 저장소**를 가리킵니다.
|
|
64
|
+
*
|
|
65
|
+
* 상위 패키지가 자기 화면 문자열을 담는 자리입니다. 검증 메시지(`getValue`)의 키셋과
|
|
66
|
+
* 완전히 분리돼 있어, 이 API 를 써도 라이브러리의 `LocaleMessageKey` 는 커지지 않습니다.
|
|
67
|
+
*
|
|
68
|
+
* @param name 네임스페이스 이름. 패키지·컴포넌트 단위를 권장합니다(예: `'u-data-view'`).
|
|
69
|
+
*/
|
|
70
|
+
static namespace<K extends string = string>(name: string): LocaleNamespace<K>;
|
|
37
71
|
}
|
|
38
72
|
export {};
|
package/dist/utilities/Locale.js
CHANGED
|
@@ -39,21 +39,29 @@ function detectLocale() {
|
|
|
39
39
|
return "en";
|
|
40
40
|
}
|
|
41
41
|
var active = detectLocale();
|
|
42
|
-
/** 정확 일치 → base 언어(ko-KR → ko) → en
|
|
43
|
-
function
|
|
42
|
+
/** 정확 일치 → base 언어(ko-KR → ko) → en 순의 조회 사슬. */
|
|
43
|
+
function chainOf(locale) {
|
|
44
44
|
const norm = locale.toLowerCase();
|
|
45
45
|
const base = norm.split("-")[0];
|
|
46
|
-
|
|
46
|
+
return base === norm ? [norm, "en"] : [
|
|
47
47
|
norm,
|
|
48
48
|
base,
|
|
49
49
|
"en"
|
|
50
50
|
];
|
|
51
|
-
|
|
51
|
+
}
|
|
52
|
+
/** 템플릿의 `{name}` 자리를 치환한다. 값이 없는 자리는 그대로 남긴다(디버깅 단서). */
|
|
53
|
+
function interpolate(template, params) {
|
|
54
|
+
return params ? template.replace(/\{(\w+)\}/g, (_, name) => params[name] != null ? String(params[name]) : `{${name}}`) : template;
|
|
55
|
+
}
|
|
56
|
+
function lookup(locale, key) {
|
|
57
|
+
for (const tag of chainOf(locale)) {
|
|
52
58
|
const value = overrides.get(tag)?.[key] ?? builtins.get(tag)?.[key];
|
|
53
59
|
if (value) return value;
|
|
54
60
|
}
|
|
55
61
|
return builtins.get("en")[key];
|
|
56
62
|
}
|
|
63
|
+
/** ns → 정규화 로케일 태그 → 테이블. */
|
|
64
|
+
var namespaces = /* @__PURE__ */ new Map();
|
|
57
65
|
/**
|
|
58
66
|
* 검증 메시지 로케일을 관리하는 정적 유틸리티입니다.
|
|
59
67
|
*/
|
|
@@ -85,8 +93,37 @@ var Locale = class {
|
|
|
85
93
|
* `params`가 있으면 템플릿의 `{name}` 자리를 치환합니다.
|
|
86
94
|
*/
|
|
87
95
|
static getValue(key, params) {
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
return interpolate(lookup(active, key), params);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 네임스페이스 핸들을 얻습니다 — 같은 이름이면 **같은 저장소**를 가리킵니다.
|
|
100
|
+
*
|
|
101
|
+
* 상위 패키지가 자기 화면 문자열을 담는 자리입니다. 검증 메시지(`getValue`)의 키셋과
|
|
102
|
+
* 완전히 분리돼 있어, 이 API 를 써도 라이브러리의 `LocaleMessageKey` 는 커지지 않습니다.
|
|
103
|
+
*
|
|
104
|
+
* @param name 네임스페이스 이름. 패키지·컴포넌트 단위를 권장합니다(예: `'u-data-view'`).
|
|
105
|
+
*/
|
|
106
|
+
static namespace(name) {
|
|
107
|
+
return {
|
|
108
|
+
name,
|
|
109
|
+
register(locale, table) {
|
|
110
|
+
const byLocale = namespaces.get(name) ?? /* @__PURE__ */ new Map();
|
|
111
|
+
const norm = locale.toLowerCase();
|
|
112
|
+
byLocale.set(norm, {
|
|
113
|
+
...byLocale.get(norm),
|
|
114
|
+
...table
|
|
115
|
+
});
|
|
116
|
+
namespaces.set(name, byLocale);
|
|
117
|
+
},
|
|
118
|
+
text(key, params) {
|
|
119
|
+
const byLocale = namespaces.get(name);
|
|
120
|
+
for (const tag of chainOf(active)) {
|
|
121
|
+
const value = byLocale?.get(tag)?.[key];
|
|
122
|
+
if (value) return interpolate(value, params);
|
|
123
|
+
}
|
|
124
|
+
return key;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
90
127
|
}
|
|
91
128
|
};
|
|
92
129
|
//#endregion
|
|
@@ -65,6 +65,30 @@ export declare class Theme {
|
|
|
65
65
|
static set(theme: ThemeType): void;
|
|
66
66
|
/** 시스템 테마 변경을 처리하는 메서드 */
|
|
67
67
|
private static handleSystemThemeChanged;
|
|
68
|
+
/** 마지막으로 지정된 브랜드 시드. 테마가 바뀌면 이 값으로 램프를 다시 계산한다. */
|
|
69
|
+
private static accentSeed;
|
|
70
|
+
/**
|
|
71
|
+
* 브랜드 색 하나로 `--u-primary-*` 램프를 만든다.
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* Theme.accent('#6A1B9A'); // 램프 5단 + 면 위 글자색이 계산된다
|
|
75
|
+
* Theme.accent(null); // 해제 — 시트 기본값으로 되돌아간다
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* 종전에는 소비자가 램프를 **손으로 열 줄 적었고**, 그 값이 대비 계약을 만족하는지는
|
|
79
|
+
* 아무것도 확인하지 않았다. 여기서는 «면 위 글자 4.5 · 바탕 위 글자 4.5 · 단 구분 1.20 ·
|
|
80
|
+
* 그래픽 3.0» 을 만족하는 값을 **계산해서** 넣는다(`utilities/accent.ts`).
|
|
81
|
+
*
|
|
82
|
+
* ⚠**바탕은 계산 시점의 `--u-bg-color` 를 읽는다** — 소비자가 바탕을 덮었으면 그 값을
|
|
83
|
+
* 기준으로 계산되고, 테마가 바뀌면 **다시 계산한다**(라이트/다크가 다른 램프를 갖는다).
|
|
84
|
+
* ⇒ `Theme.set()` · 시스템 테마 변경 뒤에 자동으로 재적용된다.
|
|
85
|
+
*
|
|
86
|
+
* ⚠**변수는 `documentElement` 의 인라인 스타일로 들어간다** — 시트보다 우선하므로
|
|
87
|
+
* 로드 순서에 기대지 않는다.
|
|
88
|
+
*/
|
|
89
|
+
static accent(seed: string | null): void;
|
|
90
|
+
/** 현재 시드를 현재 테마의 바탕에 맞춰 다시 계산해 적용한다. */
|
|
91
|
+
private static applyAccent;
|
|
68
92
|
/** 디버그 모드시 로그 출력 함수 (인스턴스 스코프) */
|
|
69
93
|
private static log;
|
|
70
94
|
}
|
package/dist/utilities/Theme.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { BrowserStorage } from "./BrowserStorage.js";
|
|
2
2
|
import dark_css_default from "../_virtual/_glob-assets_raw/dark.css.e8ac080d.js";
|
|
3
3
|
import light_css_default from "../_virtual/_glob-assets_raw/light.css.637cb2f8.js";
|
|
4
|
+
import { accentCustomProperties, deriveAccentRamp } from "./accent.js";
|
|
4
5
|
//#region src/utilities/Theme.ts
|
|
6
|
+
/** 프로퍼티 «이름»만 필요할 때 쓰는 더미 — 값은 쓰지 않는다. */
|
|
7
|
+
var EMPTY_RAMP = {
|
|
8
|
+
weakest: "#000",
|
|
9
|
+
weaker: "#000",
|
|
10
|
+
weak: "#000",
|
|
11
|
+
color: "#000",
|
|
12
|
+
strong: "#000",
|
|
13
|
+
txt: "#000"
|
|
14
|
+
};
|
|
5
15
|
/**
|
|
6
16
|
* 스타일 시트 번들 로드, 내부 자산에서 CSS를 인라인으로 가져옵니다.
|
|
7
17
|
* 빌드 시점에 정적 파일을 포함시키기 위해 vite의 `import.meta.glob`을 사용합니다.
|
|
@@ -139,6 +149,7 @@ var Theme = class {
|
|
|
139
149
|
} catch (e) {
|
|
140
150
|
this.log("Error applying theme:", e);
|
|
141
151
|
}
|
|
152
|
+
this.applyAccent();
|
|
142
153
|
if (this.storage !== null) {
|
|
143
154
|
this.storage.set(this.STORAGE_THEME_KEY, theme);
|
|
144
155
|
this.log("saved theme to storage", theme);
|
|
@@ -150,9 +161,50 @@ var Theme = class {
|
|
|
150
161
|
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
151
162
|
document.documentElement.setAttribute("theme", isDark ? "dark" : "light");
|
|
152
163
|
this.log("system theme changed, applied", isDark ? "dark" : "light");
|
|
164
|
+
this.applyAccent();
|
|
153
165
|
}
|
|
154
166
|
};
|
|
155
167
|
}
|
|
168
|
+
static {
|
|
169
|
+
this.accentSeed = null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 브랜드 색 하나로 `--u-primary-*` 램프를 만든다.
|
|
173
|
+
*
|
|
174
|
+
* ```ts
|
|
175
|
+
* Theme.accent('#6A1B9A'); // 램프 5단 + 면 위 글자색이 계산된다
|
|
176
|
+
* Theme.accent(null); // 해제 — 시트 기본값으로 되돌아간다
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* 종전에는 소비자가 램프를 **손으로 열 줄 적었고**, 그 값이 대비 계약을 만족하는지는
|
|
180
|
+
* 아무것도 확인하지 않았다. 여기서는 «면 위 글자 4.5 · 바탕 위 글자 4.5 · 단 구분 1.20 ·
|
|
181
|
+
* 그래픽 3.0» 을 만족하는 값을 **계산해서** 넣는다(`utilities/accent.ts`).
|
|
182
|
+
*
|
|
183
|
+
* ⚠**바탕은 계산 시점의 `--u-bg-color` 를 읽는다** — 소비자가 바탕을 덮었으면 그 값을
|
|
184
|
+
* 기준으로 계산되고, 테마가 바뀌면 **다시 계산한다**(라이트/다크가 다른 램프를 갖는다).
|
|
185
|
+
* ⇒ `Theme.set()` · 시스템 테마 변경 뒤에 자동으로 재적용된다.
|
|
186
|
+
*
|
|
187
|
+
* ⚠**변수는 `documentElement` 의 인라인 스타일로 들어간다** — 시트보다 우선하므로
|
|
188
|
+
* 로드 순서에 기대지 않는다.
|
|
189
|
+
*/
|
|
190
|
+
static accent(seed) {
|
|
191
|
+
this.accentSeed = seed;
|
|
192
|
+
this.applyAccent();
|
|
193
|
+
}
|
|
194
|
+
/** 현재 시드를 현재 테마의 바탕에 맞춰 다시 계산해 적용한다. */
|
|
195
|
+
static applyAccent() {
|
|
196
|
+
if (typeof document === "undefined") return;
|
|
197
|
+
const root = document.documentElement;
|
|
198
|
+
const names = Object.keys(accentCustomProperties(EMPTY_RAMP));
|
|
199
|
+
if (!this.accentSeed) {
|
|
200
|
+
for (const n of names) root.style.removeProperty(n);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const bg = getComputedStyle(root).getPropertyValue("--u-bg-color").trim() || "#ffffff";
|
|
204
|
+
const ramp = deriveAccentRamp(this.accentSeed, bg);
|
|
205
|
+
for (const [name, value] of Object.entries(accentCustomProperties(ramp))) root.style.setProperty(name, value);
|
|
206
|
+
this.log("accent applied", this.accentSeed, "→", ramp);
|
|
207
|
+
}
|
|
156
208
|
/** 디버그 모드시 로그 출력 함수 (인스턴스 스코프) */
|
|
157
209
|
static log(...args) {
|
|
158
210
|
if (this._isDebugMode) console.log("[theme]", ...args);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **브랜드 악센트 시드 → 역할 램프 파생.**
|
|
3
|
+
*
|
|
4
|
+
* 소비자가 시드 색 하나를 주면 `--u-primary-*` 5단과 그 면 위의 글자색을 계산한다.
|
|
5
|
+
* 종전에는 소비자가 램프를 **손으로 열 줄 적었고**, 그 값들이 대비 계약을 만족하는지는
|
|
6
|
+
* 아무것도 확인하지 않았다.
|
|
7
|
+
*
|
|
8
|
+
* ## 🔴 왜 CSS 만으로 하지 않는가 — 실측이 정한 것이다
|
|
9
|
+
*
|
|
10
|
+
* `color-mix()` 로 고정 비율만 섞으면 **밝은 시드에서 계약이 깨진다** — 노랑 시드의
|
|
11
|
+
* `-strong` 이 바탕 대비 **2.21**(기준 4.5)이었다. 목표 대비까지 섞으려면 **대비를 계산**
|
|
12
|
+
* 해야 하고 CSS 에는 그 수단이 없다. ⇒ 계산은 JS 가 하고, 결과만 커스텀 프로퍼티로 넣는다.
|
|
13
|
+
*
|
|
14
|
+
* ## ⚠ 두 목표를 «동시에» 건다
|
|
15
|
+
*
|
|
16
|
+
* 대비만 목표로 삼으면 시드가 이미 4.5 를 넘을 때 탐색이 **시드 자신에서 멈춰**
|
|
17
|
+
* `-strong` 과 `-color` 가 같은 값이 된다(실측: 5역할 중 4). 그러면 계약은 만족하는데
|
|
18
|
+
* **위계가 사라진다.** 그래서 «바탕과 4.5» 와 «`-color` 와 1.20» 을 함께 만족시킨다.
|
|
19
|
+
*
|
|
20
|
+
* ## 방향은 «바탕 기준»이다 — 라이트/다크가 같은 코드로 돈다
|
|
21
|
+
*
|
|
22
|
+
* `-strong` 은 바탕에서 **멀어지는** 쪽(라이트면 검정, 다크면 흰색), `-weak` 이하는 바탕에
|
|
23
|
+
* **가까워지는** 쪽이다. 다크 시트가 팔레트를 반전시켜 놓은 것과 같은 방향이다.
|
|
24
|
+
*/
|
|
25
|
+
/** 역할 램프 한 벌. 시트의 `--u-{role}-color-*` 와 이름이 1:1 이다. */
|
|
26
|
+
export interface AccentRamp {
|
|
27
|
+
weakest: string;
|
|
28
|
+
weaker: string;
|
|
29
|
+
weak: string;
|
|
30
|
+
color: string;
|
|
31
|
+
strong: string;
|
|
32
|
+
/** 면(`color`) 위에 놓이는 글자색 — `--u-{role}-txt-color`. */
|
|
33
|
+
txt: string;
|
|
34
|
+
}
|
|
35
|
+
/** 대비 계약의 문턱값 — `tests/build/token-contrast.test.ts` 와 같은 값이다. */
|
|
36
|
+
export declare const AA_TEXT = 4.5;
|
|
37
|
+
export declare const AA_GRAPHIC = 3;
|
|
38
|
+
/** `-strong` 이 `-color` 와 갈려 보이기 위한 최소 대비(2026-08-04 채택). */
|
|
39
|
+
export declare const MIN_STEP_SEPARATION = 1.2;
|
|
40
|
+
/** `#rgb`·`#rrggbb`·`rgb(r g b)` 를 [r,g,b] 로. 해석할 수 없으면 `null`. */
|
|
41
|
+
export declare function parseColor(value: string): [number, number, number] | null;
|
|
42
|
+
export declare function toHex([r, g, b]: [number, number, number]): string;
|
|
43
|
+
export declare function luminance(color: string): number;
|
|
44
|
+
/** WCAG 2.1 명암비. */
|
|
45
|
+
export declare function contrast(a: string, b: string): number;
|
|
46
|
+
/** `color-mix(in srgb, a p%, b)` 와 같은 계산. */
|
|
47
|
+
export declare function mix(a: string, b: string, p: number): string;
|
|
48
|
+
/** 면 위의 글자를 고른다 — 검정/흰색 중 대비가 큰 쪽. */
|
|
49
|
+
export declare function pickOnColor(surface: string): string;
|
|
50
|
+
export declare function deriveAccentRamp(seed: string, bg: string): AccentRamp;
|
|
51
|
+
/** 램프를 커스텀 프로퍼티 이름 → 값 맵으로. */
|
|
52
|
+
export declare function accentCustomProperties(ramp: AccentRamp, role?: string): Record<string, string>;
|