@iyulab/components 1.23.0 → 1.25.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 +81 -0
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/components/field/UField.d.ts +8 -1
- package/dist/components/field/UField.js +4 -1
- package/dist/components/field/UField.styles.js +15 -0
- package/dist/components/icon/UIcon.d.ts +17 -4
- package/dist/components/icon/UIcon.js +9 -6
- package/dist/components/text/UText.d.ts +38 -0
- package/dist/components/text/UText.js +45 -0
- package/dist/components/text/UText.styles.d.ts +1 -0
- package/dist/components/text/UText.styles.js +91 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/react/UText.d.ts +12 -0
- package/dist/react/UText.js +10 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/utilities/Theme.d.ts +13 -0
- package/dist/utilities/Theme.js +13 -0
- package/package.json +11 -3
- package/skills/iyulab-components/SKILL.md +2 -0
- package/skills/iyulab-components/references/components/checkbox.md +1 -1
- package/skills/iyulab-components/references/components/copy-button.md +80 -0
- package/skills/iyulab-components/references/components/icon.md +12 -0
- package/skills/iyulab-components/references/components/spinner.md +1 -1
- package/skills/iyulab-components/references/components/text.md +79 -0
- package/skills/iyulab-components/references/usage.md +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.25.0] - 2026-08-05
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- 🔴**`u-field`: the documented `validation-message` attribute never applied.** Lit derives
|
|
8
|
+
the default attribute name by **lowercasing** the property name — not by kebab-casing it —
|
|
9
|
+
so `validationMessage` was only reachable as `validationmessage`. Every usage in the
|
|
10
|
+
documentation and examples wrote `validation-message`, which set nothing and left the
|
|
11
|
+
error text invisible. The attribute name is now declared explicitly, matching the
|
|
12
|
+
convention already used elsewhere (`show-delay`, `hide-delay`).
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<u-field label="Name" invalid validation-message="Name is required.">
|
|
16
|
+
<u-input></u-input>
|
|
17
|
+
</u-field>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Property bindings (`.validationMessage=${…}`) are unaffected.
|
|
21
|
+
|
|
22
|
+
## [1.24.0] - 2026-08-04
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- 🔴**`u-text` — 의미 타이포그래피 스케일(7단)을 마크업에서 쓰는 자리.**
|
|
27
|
+
시트는 `1.21.0` 부터 단마다 **크기·굵기·행간·자간** 네 값을 갖고 있었지만 **그 단을
|
|
28
|
+
적용하는 컴포넌트가 없었다** — 소비자가 스케일을 쓰려면 자기 CSS 에서 토큰을 직접
|
|
29
|
+
참조해야 했고, 그러면 «컴포넌트만으로 화면을 짓는다»가 그 자리에서 깨졌다.
|
|
30
|
+
⇒ 우리가 준 것은 **값**이고 필요했던 것은 **자리**다.
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<u-text level="1" variant="display">문서 제목</u-text>
|
|
34
|
+
<u-text variant="subtitle" tone="weak">한 줄 설명</u-text>
|
|
35
|
+
<u-text>본문</u-text>
|
|
36
|
+
<u-text variant="caption" tone="weak">보조 문구</u-text>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
★**시각 축(`variant`)과 의미 축(`level`)이 독립**이다 — `level` 을 주면 섀도 루트에 실제
|
|
40
|
+
`<h1>`~`<h6>` 을 렌더해 heading 으로 읽히고(슬롯 텍스트가 접근가능 이름), 주지 않으면
|
|
41
|
+
`<p>` 다. 그래서 *"페이지의 두 번째 제목이 시각적으로 가장 큰"* 배치가 마크업을
|
|
42
|
+
거짓말시키지 않고 표현된다.
|
|
43
|
+
|
|
44
|
+
⚠**`tone` 은 중립 강조 축이다**(`default`·`weak`·`strong`·`inverse`) — 역할색이
|
|
45
|
+
아니다. 소비 화면 실측에서 나온 색 다섯 중 셋이 이 축이었고 나머지 둘은 **링크**와
|
|
46
|
+
**콜아웃 박스**라 이 컴포넌트의 축이 아니었다.
|
|
47
|
+
|
|
48
|
+
⚠**값을 새로 정의하지 않는다** — 7단 × 4속성을 시트에서 읽기만 한다. 회귀 12건이
|
|
49
|
+
*"어떤 크기인가"* 가 아니라 ***"시트가 말한 그 값을 쓰는가"*** 를 잰다.
|
|
50
|
+
같은 이유로 `overline` 에 `text-transform: uppercase` 를 **붙이지 않았다** — 관습이긴 하나
|
|
51
|
+
소비자가 쓴 글자를 바꾸고, CJK 에는 효과가 없어 **같은 단이 언어에 따라 다르게 보인다.**
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- 🔴**`u-field` 안의 컨트롤이 필드 폭을 채우지 않아 폼 격자가 어긋났다.** 실측: 같은 231px
|
|
56
|
+
칸 안에서 `u-input` 202 · `u-select` 71/92/108 · `u-textarea` 168 — **오른쪽 끝이 다섯
|
|
57
|
+
군데에서 다 달랐다.** 컨트롤 자신의 기본값(`inline-block`)은 옳지만, `u-field` 로 감싼
|
|
58
|
+
순간 그것은 **폼의 한 칸**이다 ⇒ 라벨·설명·검증 문구의 배치를 책임지는 이 컴포넌트가
|
|
59
|
+
폭도 함께 정한다(`::slotted(*) { width: 100% }`).
|
|
60
|
+
⚠소비자가 인라인 `style` 로 폭을 주면 그쪽이 이긴다.
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- **`u-icon[fallback]`** — 이름·URL 이 해석되지 않을 때 대신 그릴 SVG 원문.
|
|
65
|
+
«아이콘이 안 보인다»가 **«누를 것이 없다»** 가 되는 자리가 있다(접힌 사이드바의 메뉴 항목).
|
|
66
|
+
해석 실패는 이름 미지정 · 404 · SVG 파싱 실패를 **모두** 포함한다.
|
|
67
|
+
|
|
68
|
+
### Documentation
|
|
69
|
+
|
|
70
|
+
- 🔴**`Theme.accent(seed)` 가 `primary` 역할 토큰 **7종 중 6종**만 세팅한다는 것을 명시했다.**
|
|
71
|
+
`--u-primary-bg-color`(`u-tag` 의 `--tag-hue-surface` 가 읽는 «글자를 얹는 옅은 면»)는
|
|
72
|
+
시트 기본값으로 남으므로, **시드만 넣으면 태그·선택된 행 같은 옅은 면이 파랑으로 남는다.**
|
|
73
|
+
실측된 증상이 *"버튼은 브랜드인데 선택된 표 행은 파랑"* 이다.
|
|
74
|
+
⇒ `usage.md` 브랜드 절에 **7종 표**와 한 줄 해법을, `Theme.accent` JSDoc 에 같은 내용을 적었다.
|
|
75
|
+
|
|
76
|
+
⚠**파생을 붙이지 않은 이유**: 이 토큰은 `info`·`success`·`danger`·`warning` 과 한 세트로
|
|
77
|
+
**5계열 × 2테마 = 10개 값이 손으로 짝지어져** 있고, 두 테마가 같은 계약을 쓰지 않는다
|
|
78
|
+
(바탕 대비비 실측 — 라이트 **1.142** · 다크 **1.030**). 도출식으로 바꾸는 것은 팔레트
|
|
79
|
+
값 결정이라 별도 판단이 필요하다.
|
|
80
|
+
|
|
81
|
+
브라우저 회귀 2건이 이 문서가 참인지 잰다 — **결손을 고정하는 것이 아니라**, 나중에 파생이
|
|
82
|
+
붙으면 그 테스트가 뒤집히면서 문서도 고치라고 알린다.
|
|
83
|
+
|
|
3
84
|
## [1.23.0] - 2026-08-04
|
|
4
85
|
|
|
5
86
|
### Added
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ npx skills add ./node_modules/@iyulab/components
|
|
|
76
76
|
|
|
77
77
|
**Navigation** — `u-menu`, `u-menu-item`, `u-tab-panel`, `u-breadcrumb`, `u-breadcrumb-item`, `u-tree`, `u-tree-item`
|
|
78
78
|
|
|
79
|
-
**Layout & Display** — `u-avatar`, `u-card`, `u-carousel`, `u-divider`, `u-icon`, `u-panel`, `u-split-panel`, `u-tag`
|
|
79
|
+
**Layout & Display** — `u-avatar`, `u-card`, `u-carousel`, `u-divider`, `u-icon`, `u-panel`, `u-split-panel`, `u-tag`, `u-text`
|
|
80
80
|
|
|
81
81
|
## Theming
|
|
82
82
|
|
|
@@ -18,7 +18,14 @@ export declare class UField extends UElement {
|
|
|
18
18
|
label?: string;
|
|
19
19
|
/** 하단 설명 텍스트 */
|
|
20
20
|
description?: string;
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* 유효성 검사 실패 시 표시할 메시지.
|
|
23
|
+
*
|
|
24
|
+
* ⚠**속성 이름을 명시한다.** Lit 의 기본 속성명은 프로퍼티명을 **소문자화**한 것이라
|
|
25
|
+
* (kebab 이 아니다) 그대로 두면 `validationmessage` 가 되는데, 문서·샘플·소비 코드가
|
|
26
|
+
* 전부 `validation-message` 로 적고 있었고 그 형태는 **아무것도 설정하지 않았다**.
|
|
27
|
+
* 형제 프로퍼티들도 명시형을 쓴다(`show-delay`·`hide-delay`).
|
|
28
|
+
*/
|
|
22
29
|
validationMessage?: string;
|
|
23
30
|
render(): import('lit-html').TemplateResult<1>;
|
|
24
31
|
private renderFooter;
|
|
@@ -58,7 +58,10 @@ __decorate([property({
|
|
|
58
58
|
}), __decorateMetadata("design:type", Boolean)], UField.prototype, "invalid", void 0);
|
|
59
59
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UField.prototype, "label", void 0);
|
|
60
60
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UField.prototype, "description", void 0);
|
|
61
|
-
__decorate([property({
|
|
61
|
+
__decorate([property({
|
|
62
|
+
type: String,
|
|
63
|
+
attribute: "validation-message"
|
|
64
|
+
}), __decorateMetadata("design:type", String)], UField.prototype, "validationMessage", void 0);
|
|
62
65
|
UField = __decorate([customElement("u-field")], UField);
|
|
63
66
|
//#endregion
|
|
64
67
|
export { UField };
|
|
@@ -15,6 +15,21 @@ var styles = css`
|
|
|
15
15
|
color: var(--u-danger-color-strong, #C62828);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/* 🔴**라벨을 단 컨트롤은 필드 폭을 채운다.**
|
|
19
|
+
컨트롤 자신의 기본값은 inline-block(자기 내용만큼)이 옳다 — 문장 안에 놓이는 경우가
|
|
20
|
+
있기 때문이다. 그러나 u-field 로 감싼 순간 그것은 **폼의 한 칸**이고, 칸마다 컨트롤이
|
|
21
|
+
제각각의 폭을 가지면 격자가 너덜너덜해진다. 실측: 같은 231px 칸 안에서 input 202 ·
|
|
22
|
+
select 71/92/108 · textarea 168 — **오른쪽 끝이 다섯 군데에서 다 달랐다.**
|
|
23
|
+
⇒ 라벨·설명·검증 문구의 배치를 책임지는 이 컴포넌트가 폭도 함께 정한다.
|
|
24
|
+
|
|
25
|
+
⚠소비자가 인라인 style 로 폭을 주면 그쪽이 이긴다(인라인 > ::slotted).
|
|
26
|
+
⚠이 주석에 백틱을 쓰지 말 것 — css 태그드 템플릿을 그 자리에서 끝낸다(이 파일이 실제로
|
|
27
|
+
그렇게 깨졌고, cycle-217 이 만든 타입 게이트가 잡았다). */
|
|
28
|
+
::slotted(*) {
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
.header {
|
|
19
34
|
display: flex;
|
|
20
35
|
flex-direction: row;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { nothing } from 'lit';
|
|
2
1
|
import { UElement } from '../UElement.js';
|
|
3
2
|
export type IconLibrary = (string & {}) | "internal" | "tabler" | "heroicons" | "lucide" | "bootstrap";
|
|
4
3
|
/**
|
|
@@ -14,9 +13,23 @@ export declare class UIcon extends UElement {
|
|
|
14
13
|
lib?: IconLibrary;
|
|
15
14
|
/** 사용할 아이콘 이름을 지정합니다. */
|
|
16
15
|
name?: string;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 이름·URL 이 **해석되지 않을 때** 대신 그릴 SVG 원문.
|
|
18
|
+
*
|
|
19
|
+
* ★**«아이콘이 안 보인다»가 «자리 자체가 사라진다»가 되는 곳이 있다.** 사이드바가 접히면
|
|
20
|
+
* 라벨이 숨고 아이콘만 남는데, 그 아이콘이 해석에 실패하면 그 메뉴는 **누를 것이 없는
|
|
21
|
+
* 빈 줄**이 된다 — 이름이 잘못됐는지, 파일이 없는지, 아이콘을 안 준 것인지 화면에서
|
|
22
|
+
* 구분되지 않고 **탐색 수단이 통째로 사라진다.**
|
|
23
|
+
*
|
|
24
|
+
* ⇒ 그런 자리는 «없으면 안 그린다»가 아니라 **«없으면 대신 그린다»** 여야 한다.
|
|
25
|
+
* 해석 실패는 `name` 미지정 · 파일 없음(404) · SVG 파싱 실패를 **모두** 포함한다.
|
|
26
|
+
*
|
|
27
|
+
* ⚠소비자가 주는 값은 `sanitize()` 를 거친다(`name` 경로와 같은 처리).
|
|
28
|
+
*/
|
|
29
|
+
fallback?: string;
|
|
30
|
+
render(): import('lit-html/directive.js').DirectiveResult<typeof import('lit-html/directives/unsafe-html.js').UnsafeHTMLDirective>;
|
|
31
|
+
/** 해석 결과가 없으면 `fallback` 으로 대신한다. 둘 다 없을 때만 아무것도 그리지 않는다. */
|
|
32
|
+
private paint;
|
|
20
33
|
private resolve;
|
|
21
34
|
private sanitize;
|
|
22
35
|
}
|
|
@@ -14,13 +14,15 @@ var UIcon = class UIcon extends UElement {
|
|
|
14
14
|
}
|
|
15
15
|
render() {
|
|
16
16
|
if (this.src) return until(IconRegistry.resolveUrl(this.src).then((html) => {
|
|
17
|
-
|
|
18
|
-
return sanitized ? unsafeHTML(sanitized) : nothing;
|
|
17
|
+
return this.paint(this.sanitize(html));
|
|
19
18
|
}), nothing);
|
|
20
|
-
if (this.name) return until(this.resolve(this.name).then((html) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if (this.name) return until(this.resolve(this.name).then((html) => this.paint(html)), nothing);
|
|
20
|
+
return this.paint(void 0);
|
|
21
|
+
}
|
|
22
|
+
/** 해석 결과가 없으면 `fallback` 으로 대신한다. 둘 다 없을 때만 아무것도 그리지 않는다. */
|
|
23
|
+
paint(html) {
|
|
24
|
+
const resolved = html ?? this.sanitize(this.fallback);
|
|
25
|
+
return resolved ? unsafeHTML(resolved) : nothing;
|
|
24
26
|
}
|
|
25
27
|
async resolve(name) {
|
|
26
28
|
let html = void 0;
|
|
@@ -50,6 +52,7 @@ var UIcon = class UIcon extends UElement {
|
|
|
50
52
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UIcon.prototype, "src", void 0);
|
|
51
53
|
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], UIcon.prototype, "lib", void 0);
|
|
52
54
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UIcon.prototype, "name", void 0);
|
|
55
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UIcon.prototype, "fallback", void 0);
|
|
53
56
|
UIcon = __decorate([customElement("u-icon")], UIcon);
|
|
54
57
|
//#endregion
|
|
55
58
|
export { UIcon };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { UElement } from '../UElement.js';
|
|
2
|
+
export type TextVariant = "display" | "title" | "subtitle" | "body" | "label" | "caption" | "overline";
|
|
3
|
+
/** 중립 강조 축. **역할색이 아니다** — 역할색이 필요하면 그것은 별도 축이다. */
|
|
4
|
+
export type TextTone = "default" | "weak" | "strong" | "inverse";
|
|
5
|
+
/** 제목 단계. 값이 있으면 heading 으로 읽힌다. */
|
|
6
|
+
export type TextLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
7
|
+
/**
|
|
8
|
+
* 의미 타이포그래피 스케일(7단)을 마크업에서 쓰는 자리입니다.
|
|
9
|
+
*
|
|
10
|
+
* 시트는 단마다 **크기·굵기·행간·자간** 네 값을 갖고 있으나, 그 단을 *적용하는* 것이
|
|
11
|
+
* 없어 소비자는 자기 CSS 에서 타이포 토큰을 직접 참조해야 했습니다.
|
|
12
|
+
* 이 컴포넌트는 그 자리를 대신하며, **값을 새로 정의하지 않고 토큰만 읽습니다.**
|
|
13
|
+
*
|
|
14
|
+
* `level` 을 주면 섀도 루트에 실제 `<h1>`~`<h6>` 을 렌더하므로 heading 으로 읽힙니다
|
|
15
|
+
* (슬롯 텍스트가 접근가능 이름이 됩니다). 주지 않으면 `<p>` 입니다.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ 시각 축(`variant`)과 의미 축(`level`)은 **독립**입니다 — 페이지의 두 번째 제목이
|
|
18
|
+
* 시각적으로 가장 클 수 있습니다(`level="2" variant="display"`).
|
|
19
|
+
*
|
|
20
|
+
* @slot - 텍스트 내용
|
|
21
|
+
*
|
|
22
|
+
* @csspart base - 렌더된 `<p>` 또는 `<h1>`~`<h6>` 요소
|
|
23
|
+
*/
|
|
24
|
+
export declare class UText extends UElement {
|
|
25
|
+
static styles: import('lit').CSSResultGroup[];
|
|
26
|
+
/** 의미 타이포그래피 단계 */
|
|
27
|
+
variant: TextVariant;
|
|
28
|
+
/** 중립 강조 축 (역할색이 아니다) */
|
|
29
|
+
tone: TextTone;
|
|
30
|
+
/** 제목 단계. 지정하면 heading 요소로 렌더된다 */
|
|
31
|
+
level?: TextLevel;
|
|
32
|
+
protected render(): import('lit-html').TemplateResult;
|
|
33
|
+
}
|
|
34
|
+
declare global {
|
|
35
|
+
interface HTMLElementTagNameMap {
|
|
36
|
+
'u-text': UText;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { UElement } from "../UElement.js";
|
|
2
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
|
|
3
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
|
|
4
|
+
import { styles } from "./UText.styles.js";
|
|
5
|
+
import { customElement, property } from "lit/decorators.js";
|
|
6
|
+
import { html, literal } from "lit/static-html.js";
|
|
7
|
+
//#region src/components/text/UText.ts
|
|
8
|
+
var PARAGRAPH = literal`p`;
|
|
9
|
+
var HEADINGS = {
|
|
10
|
+
1: literal`h1`,
|
|
11
|
+
2: literal`h2`,
|
|
12
|
+
3: literal`h3`,
|
|
13
|
+
4: literal`h4`,
|
|
14
|
+
5: literal`h5`,
|
|
15
|
+
6: literal`h6`
|
|
16
|
+
};
|
|
17
|
+
var UText = class UText extends UElement {
|
|
18
|
+
constructor(..._args) {
|
|
19
|
+
super(..._args);
|
|
20
|
+
this.variant = "body";
|
|
21
|
+
this.tone = "default";
|
|
22
|
+
}
|
|
23
|
+
static {
|
|
24
|
+
this.styles = [super.styles, styles];
|
|
25
|
+
}
|
|
26
|
+
render() {
|
|
27
|
+
const tag = this.level && HEADINGS[this.level] ? HEADINGS[this.level] : PARAGRAPH;
|
|
28
|
+
return html`<${tag} class="text" part="base"><slot></slot></${tag}>`;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
__decorate([property({
|
|
32
|
+
type: String,
|
|
33
|
+
reflect: true
|
|
34
|
+
}), __decorateMetadata("design:type", Object)], UText.prototype, "variant", void 0);
|
|
35
|
+
__decorate([property({
|
|
36
|
+
type: String,
|
|
37
|
+
reflect: true
|
|
38
|
+
}), __decorateMetadata("design:type", Object)], UText.prototype, "tone", void 0);
|
|
39
|
+
__decorate([property({
|
|
40
|
+
type: Number,
|
|
41
|
+
reflect: true
|
|
42
|
+
}), __decorateMetadata("design:type", Object)], UText.prototype, "level", void 0);
|
|
43
|
+
UText = __decorate([customElement("u-text")], UText);
|
|
44
|
+
//#endregion
|
|
45
|
+
export { UText };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
//#region src/components/text/UText.styles.ts
|
|
3
|
+
var styles = css`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
color: var(--u-txt-color, #212121);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* 기본은 body 다. :host([variant="body"]) 규칙을 따로 두지 않는 이유는
|
|
11
|
+
* 값이 같기 때문이며, 부수 효과로 **알 수 없는 variant 가 body 로 흐른다**
|
|
12
|
+
* (조용히 폰트가 사라지는 것보다 낫다).
|
|
13
|
+
*/
|
|
14
|
+
.text {
|
|
15
|
+
margin: 0;
|
|
16
|
+
font-family: inherit;
|
|
17
|
+
font-size: var(--u-text-body-size, 14px);
|
|
18
|
+
font-weight: var(--u-text-body-weight, 400);
|
|
19
|
+
line-height: var(--u-text-body-leading, 1.6);
|
|
20
|
+
letter-spacing: var(--u-text-body-tracking, 0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:host([variant="display"]) .text {
|
|
24
|
+
font-size: var(--u-text-display-size, 26px);
|
|
25
|
+
font-weight: var(--u-text-display-weight, 700);
|
|
26
|
+
line-height: var(--u-text-display-leading, 1.4);
|
|
27
|
+
letter-spacing: var(--u-text-display-tracking, -0.02em);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:host([variant="title"]) .text {
|
|
31
|
+
font-size: var(--u-text-title-size, 20px);
|
|
32
|
+
font-weight: var(--u-text-title-weight, 700);
|
|
33
|
+
line-height: var(--u-text-title-leading, 1.45);
|
|
34
|
+
letter-spacing: var(--u-text-title-tracking, -0.01em);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:host([variant="subtitle"]) .text {
|
|
38
|
+
font-size: var(--u-text-subtitle-size, 16px);
|
|
39
|
+
font-weight: var(--u-text-subtitle-weight, 600);
|
|
40
|
+
line-height: var(--u-text-subtitle-leading, 1.5);
|
|
41
|
+
letter-spacing: var(--u-text-subtitle-tracking, 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:host([variant="label"]) .text {
|
|
45
|
+
font-size: var(--u-text-label-size, 13px);
|
|
46
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
47
|
+
line-height: var(--u-text-label-leading, 1.5);
|
|
48
|
+
letter-spacing: var(--u-text-label-tracking, 0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:host([variant="caption"]) .text {
|
|
52
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
53
|
+
font-weight: var(--u-text-caption-weight, 400);
|
|
54
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
55
|
+
letter-spacing: var(--u-text-caption-tracking, 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* ⚠ overline 에 text-transform: uppercase 를 붙이지 않는다.
|
|
60
|
+
*
|
|
61
|
+
* 눈썹 텍스트의 관습이긴 하나 ⑴시트가 정의하는 것은 네 속성뿐이라 다섯 번째를 여기서
|
|
62
|
+
* 정하면 이 컴포넌트가 «두 번째 정의처»가 되고 ⑵소비자가 쓴 글자를 조용히 바꾸며
|
|
63
|
+
* ⑶CJK 에는 아무 효과가 없어 **같은 단이 언어에 따라 다르게 보인다.**
|
|
64
|
+
* 대문자가 필요한 자리는 그 자리가 정한다.
|
|
65
|
+
*/
|
|
66
|
+
:host([variant="overline"]) .text {
|
|
67
|
+
font-size: var(--u-text-overline-size, 11px);
|
|
68
|
+
font-weight: var(--u-text-overline-weight, 700);
|
|
69
|
+
line-height: var(--u-text-overline-leading, 1.45);
|
|
70
|
+
letter-spacing: var(--u-text-overline-tracking, 0.06em);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* tone 은 **중립 강조 축**이다 — 역할색(info/success/danger…)이 아니다.
|
|
75
|
+
* 소비앱 실측에서 나온 색 다섯 중 셋이 이 축이었고, 나머지 둘은 링크와
|
|
76
|
+
* 콜아웃 박스라 u-text 의 문제가 아니었다.
|
|
77
|
+
*/
|
|
78
|
+
:host([tone="weak"]) {
|
|
79
|
+
color: var(--u-txt-color-weak, #757575);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:host([tone="strong"]) {
|
|
83
|
+
color: var(--u-txt-color-strong, #000000);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:host([tone="inverse"]) {
|
|
87
|
+
color: var(--u-txt-color-inverse, #FFFFFF);
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
//#endregion
|
|
91
|
+
export { styles };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export * from './components/switch/USwitch.js';
|
|
|
41
41
|
export * from './components/tab/UTab.js';
|
|
42
42
|
export * from './components/tab-panel/UTabPanel.js';
|
|
43
43
|
export * from './components/tag/UTag.js';
|
|
44
|
+
export * from './components/text/UText.js';
|
|
44
45
|
export * from './components/textarea/UTextarea.js';
|
|
45
46
|
export * from './components/tooltip/UTooltip.js';
|
|
46
47
|
export * from './components/tree/UTree.js';
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@ import { USplitPanel } from "./components/split-panel/USplitPanel.js";
|
|
|
47
47
|
import { USwitch } from "./components/switch/USwitch.js";
|
|
48
48
|
import { UTab } from "./components/tab/UTab.js";
|
|
49
49
|
import { UTabPanel } from "./components/tab-panel/UTabPanel.js";
|
|
50
|
+
import { UText } from "./components/text/UText.js";
|
|
50
51
|
import { UTextarea } from "./components/textarea/UTextarea.js";
|
|
51
52
|
import { UTreeItem } from "./components/tree-item/UTreeItem.js";
|
|
52
53
|
import { UTree } from "./components/tree/UTree.js";
|
|
@@ -54,4 +55,4 @@ import { BrowserStorage } from "./utilities/BrowserStorage.js";
|
|
|
54
55
|
import { Dialog } from "./utilities/Dialog.js";
|
|
55
56
|
import { Theme } from "./utilities/Theme.js";
|
|
56
57
|
import { Toast } from "./utilities/Toast.js";
|
|
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 };
|
|
58
|
+
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, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, getDefaultBaseUrl, getParentElement, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { UText as UTextElement } from '../components/text/UText';
|
|
3
|
+
|
|
4
|
+
export declare const UText: React.ForwardRefExoticComponent<
|
|
5
|
+
Omit<Partial<UTextElement>, keyof React.HTMLAttributes<UTextElement>>
|
|
6
|
+
& Omit<React.HTMLAttributes<UTextElement>, never>
|
|
7
|
+
& React.RefAttributes<UTextElement>
|
|
8
|
+
& {
|
|
9
|
+
}
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
export type UTextProps = React.ComponentProps<typeof UText>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createComponent } from '@lit/react';
|
|
3
|
+
import { UText as UTextElement } from '../components/text/UText.js';
|
|
4
|
+
|
|
5
|
+
export const UText = createComponent({
|
|
6
|
+
react: React,
|
|
7
|
+
tagName: 'u-text',
|
|
8
|
+
elementClass: UTextElement,
|
|
9
|
+
events: {},
|
|
10
|
+
});
|
package/dist/react/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { UTreeItem, UTreeItemProps } from './UTreeItem';
|
|
|
2
2
|
export { UTree, UTreeProps } from './UTree';
|
|
3
3
|
export { UTooltip, UTooltipProps } from './UTooltip';
|
|
4
4
|
export { UTextarea, UTextareaProps } from './UTextarea';
|
|
5
|
+
export { UText, UTextProps } from './UText';
|
|
5
6
|
export { UTag, UTagProps } from './UTag';
|
|
6
7
|
export { UTabPanel, UTabPanelProps } from './UTabPanel';
|
|
7
8
|
export { UTab, UTabProps } from './UTab';
|
package/dist/react/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { UTreeItem } from './UTreeItem.js';
|
|
|
2
2
|
export { UTree } from './UTree.js';
|
|
3
3
|
export { UTooltip } from './UTooltip.js';
|
|
4
4
|
export { UTextarea } from './UTextarea.js';
|
|
5
|
+
export { UText } from './UText.js';
|
|
5
6
|
export { UTag } from './UTag.js';
|
|
6
7
|
export { UTabPanel } from './UTabPanel.js';
|
|
7
8
|
export { UTab } from './UTab.js';
|
|
@@ -85,6 +85,19 @@ export declare class Theme {
|
|
|
85
85
|
*
|
|
86
86
|
* ⚠**변수는 `documentElement` 의 인라인 스타일로 들어간다** — 시트보다 우선하므로
|
|
87
87
|
* 로드 순서에 기대지 않는다.
|
|
88
|
+
*
|
|
89
|
+
* 🔴**`--u-primary-bg-color` 는 세팅하지 않는다 — 이 자가 덮는 것은 7종 중 6종이다.**
|
|
90
|
+
* 그 토큰은 «글자를 얹는 옅은 면»(`u-tag` 의 `--tag-hue-surface`)이라 램프의 어느 단과도
|
|
91
|
+
* 성질이 다르고, 시트는 그 자리를 **5계열 × 2테마 = 10개 값으로 손수 짝지어** 두었다
|
|
92
|
+
* (라이트/다크가 같은 대비비를 쓰지 않는다 — 실측 1.14 대 1.03). 도출식으로 바꾸는 것은
|
|
93
|
+
* 팔레트 값 결정이라 사람 판단이 필요하다.
|
|
94
|
+
*
|
|
95
|
+
* ⇒ **시드만 넣으면 태그·선택된 행 같은 옅은 면이 파랑으로 남는다.** 함께 세팅할 것:
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* Theme.accent('#7c3aed');
|
|
99
|
+
* document.documentElement.style.setProperty('--u-primary-bg-color', '#f3e8ff');
|
|
100
|
+
* ```
|
|
88
101
|
*/
|
|
89
102
|
static accent(seed: string | null): void;
|
|
90
103
|
/** 현재 시드를 현재 테마의 바탕에 맞춰 다시 계산해 적용한다. */
|
package/dist/utilities/Theme.js
CHANGED
|
@@ -186,6 +186,19 @@ var Theme = class {
|
|
|
186
186
|
*
|
|
187
187
|
* ⚠**변수는 `documentElement` 의 인라인 스타일로 들어간다** — 시트보다 우선하므로
|
|
188
188
|
* 로드 순서에 기대지 않는다.
|
|
189
|
+
*
|
|
190
|
+
* 🔴**`--u-primary-bg-color` 는 세팅하지 않는다 — 이 자가 덮는 것은 7종 중 6종이다.**
|
|
191
|
+
* 그 토큰은 «글자를 얹는 옅은 면»(`u-tag` 의 `--tag-hue-surface`)이라 램프의 어느 단과도
|
|
192
|
+
* 성질이 다르고, 시트는 그 자리를 **5계열 × 2테마 = 10개 값으로 손수 짝지어** 두었다
|
|
193
|
+
* (라이트/다크가 같은 대비비를 쓰지 않는다 — 실측 1.14 대 1.03). 도출식으로 바꾸는 것은
|
|
194
|
+
* 팔레트 값 결정이라 사람 판단이 필요하다.
|
|
195
|
+
*
|
|
196
|
+
* ⇒ **시드만 넣으면 태그·선택된 행 같은 옅은 면이 파랑으로 남는다.** 함께 세팅할 것:
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* Theme.accent('#7c3aed');
|
|
200
|
+
* document.documentElement.style.setProperty('--u-primary-bg-color', '#f3e8ff');
|
|
201
|
+
* ```
|
|
189
202
|
*/
|
|
190
203
|
static accent(seed) {
|
|
191
204
|
this.accentSeed = seed;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/components",
|
|
3
3
|
"description": "web-components library based on lit-element made by iyulab",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.25.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"components",
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
],
|
|
26
26
|
"type": "module",
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": [
|
|
32
|
+
"./src/components/*/U*.ts",
|
|
33
|
+
"./dist/components/*/U*.js"
|
|
34
|
+
],
|
|
28
35
|
"exports": {
|
|
29
36
|
".": {
|
|
30
37
|
"types": "./dist/index.d.ts",
|
|
@@ -42,14 +49,15 @@
|
|
|
42
49
|
"test:browser": "vitest run --project=browser",
|
|
43
50
|
"lint": "eslint src/",
|
|
44
51
|
"lint:fix": "eslint src/ --fix",
|
|
45
|
-
"build": "eslint && npm run typecheck:plugins && vite build && npm run build:plugins && npm run test:react-types",
|
|
52
|
+
"build": "npm run typecheck && eslint && npm run typecheck:plugins && vite build && npm run build:plugins && npm run test:react-types",
|
|
46
53
|
"test:react-types": "tsc -p tsconfig.react-smoke.json",
|
|
47
54
|
"typecheck:plugins": "tsc -p plugins/tsconfig.json",
|
|
48
55
|
"docs:cssprops": "node scripts/cssprops-doc.mjs --write",
|
|
49
56
|
"docs:react-events": "node scripts/react-events-doc.mjs --write",
|
|
50
57
|
"docs:tokens": "node scripts/design-tokens-doc.mjs --write",
|
|
51
58
|
"build:plugins": "tsc -p plugins/tsconfig.build.json",
|
|
52
|
-
"probe:seed-ramp": "node scripts/seed-ramp.mjs"
|
|
59
|
+
"probe:seed-ramp": "node scripts/seed-ramp.mjs",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
53
61
|
},
|
|
54
62
|
"dependencies": {
|
|
55
63
|
"@floating-ui/dom": "^1.8.0",
|
|
@@ -59,6 +59,7 @@ import { UButton, UInput } from '@iyulab/components/react';
|
|
|
59
59
|
|
|
60
60
|
- [`u-button`](./references/components/button.md) — Versatile button with multiple variants; renders as `<a>` when `href` is set
|
|
61
61
|
- [`u-icon-button`](./references/components/icon-button.md) — Square icon-only button with built-in tooltip
|
|
62
|
+
- [`u-copy-button`](./references/components/copy-button.md) — Copies text to the clipboard with a transient confirmation
|
|
62
63
|
- [`u-button-group`](./references/components/button-group.md) — Groups buttons with shared variant and disabled state
|
|
63
64
|
|
|
64
65
|
### Form Controls
|
|
@@ -109,6 +110,7 @@ import { UButton, UInput } from '@iyulab/components/react';
|
|
|
109
110
|
- [`u-chip`](./references/components/chip.md) — Selectable/removable chip tag
|
|
110
111
|
- [`u-tag`](./references/components/tag.md) — Non-interactive label tag
|
|
111
112
|
- [`u-icon`](./references/components/icon.md) — SVG icon from built-in or external icon library
|
|
113
|
+
- [`u-text`](./references/components/text.md) — Semantic typography step; optional heading level
|
|
112
114
|
|
|
113
115
|
---
|
|
114
116
|
|
|
@@ -34,7 +34,7 @@ Checkbox with `indeterminate` state support. Form-associated (`formAssociated =
|
|
|
34
34
|
| `checked` | `boolean` | `false` | ✓ | Checked state |
|
|
35
35
|
| `indeterminate` | `boolean` | `false` | ✓ | Partial / indeterminate state |
|
|
36
36
|
| `variant` | `'filled'\|'outline'` | `'filled'` | ✓ | Visual style |
|
|
37
|
-
| `color` | `'blue'\|'green'\|'red'\|'orange'\|'teal'\|'cyan'\|'purple'\|'pink'\|'neutral'` | `'blue'` | ✓ | Accent color |
|
|
37
|
+
| `color` | `'primary'\|'info'\|'success'\|'warning'\|'danger'\|'blue'\|'green'\|'red'\|'orange'\|'teal'\|'cyan'\|'purple'\|'pink'\|'neutral'` | `'blue'` | ✓ | Accent color — role axis (`primary`…`danger`, follows rebranding) and decorative axis (`blue`…, immune to it) |
|
|
38
38
|
| `disabled` | `boolean` | `false` | ✓ | Disable the control |
|
|
39
39
|
| `readonly` | `boolean` | `false` | ✓ | Read-only |
|
|
40
40
|
| `required` | `boolean` | `false` | ✓ | Required field |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# u-copy-button
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import '@iyulab/components/dist/components/copy-button/UCopyButton.js';
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
**Tag:** `u-copy-button`
|
|
8
|
+
|
|
9
|
+
Copies text to the clipboard and shows a transient confirmation by swapping its icon.
|
|
10
|
+
|
|
11
|
+
Two shapes:
|
|
12
|
+
|
|
13
|
+
- **Icon only** (default) — the default slot becomes the tooltip.
|
|
14
|
+
- **Icon + label** — set `label` to render visible text beside the icon.
|
|
15
|
+
|
|
16
|
+
The clipboard logic is identical in both.
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!-- Icon only; slot content is the tooltip -->
|
|
20
|
+
<u-copy-button value="npm i @iyulab/components">Copy install command</u-copy-button>
|
|
21
|
+
|
|
22
|
+
<!-- Icon + label -->
|
|
23
|
+
<u-copy-button value="RESULT-1024" label="Copy result"></u-copy-button>
|
|
24
|
+
|
|
25
|
+
<!-- Stays in the copied state until reset explicitly -->
|
|
26
|
+
<u-copy-button value="token" .delay=${0}></u-copy-button>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Changing or cancelling what gets copied
|
|
30
|
+
|
|
31
|
+
The `copy` event fires **before** the write, so a handler can rewrite the payload or cancel:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
button.addEventListener('copy', (e: ClipboardEvent) => {
|
|
35
|
+
e.clipboardData?.setData('text/plain', transform(button.value));
|
|
36
|
+
e.preventDefault(); // cancel the built-in write
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Slots
|
|
43
|
+
|
|
44
|
+
| Name | Description |
|
|
45
|
+
|------|-------------|
|
|
46
|
+
| *(default)* | Tooltip content, icon-only shape. Unused when `label` is set |
|
|
47
|
+
|
|
48
|
+
## Properties
|
|
49
|
+
|
|
50
|
+
| Property | Type | Default | Reflect | Description |
|
|
51
|
+
|----------|------|---------|---------|-------------|
|
|
52
|
+
| `variant` | `'solid'\|'outline'\|'ghost'\|'link'` | `'ghost'` | ✓ | Button style |
|
|
53
|
+
| `rounded` | `boolean` | `false` | ✓ | Circular shape |
|
|
54
|
+
| `disabled` | `boolean` | `false` | ✓ | Disabled state |
|
|
55
|
+
| `copied` | `boolean` | `false` | ✓ | Currently showing the copied state |
|
|
56
|
+
| `tooltipPlacement` | `Placement` | `"top"` | | Tooltip placement (`tooltip-placement`) |
|
|
57
|
+
| `tooltipOffset` | `OffsetOptions` | `4` | | Tooltip distance (`tooltip-offset`) |
|
|
58
|
+
| `delay` | `number` | `1_000` | | Milliseconds before leaving the copied state; `0` or less keeps it |
|
|
59
|
+
| `value` | `string` | — | | Text to copy |
|
|
60
|
+
| `label` | `string` | — | | Inline label; switches to the icon + label shape |
|
|
61
|
+
|
|
62
|
+
## Events
|
|
63
|
+
|
|
64
|
+
| Event | Detail | Description |
|
|
65
|
+
|-------|--------|-------------|
|
|
66
|
+
| `copy` | native `ClipboardEvent` | Fires before the write. `preventDefault()` cancels it; `clipboardData.setData` replaces the payload |
|
|
67
|
+
|
|
68
|
+
## CSS Parts
|
|
69
|
+
|
|
70
|
+
| Part | Description |
|
|
71
|
+
|------|-------------|
|
|
72
|
+
| `button` | Inner button element |
|
|
73
|
+
| `icon` | Icon element |
|
|
74
|
+
| `tooltip` | Tooltip element (icon-only shape) |
|
|
75
|
+
|
|
76
|
+
## Sizing
|
|
77
|
+
|
|
78
|
+
This component keeps a **fixed** font size (18px) rather than inheriting the surrounding
|
|
79
|
+
typography — it reads as an affordance next to text of any size. See
|
|
80
|
+
[design-tokens.md](../../../../docs/design-tokens.md) for the four components that do this.
|
|
@@ -30,8 +30,19 @@ Renders an SVG icon by name from a registered icon library. Inline `src` also ac
|
|
|
30
30
|
<u-icon src='<svg ...>...</svg>'></u-icon>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
```html
|
|
34
|
+
<!-- Fallback: drawn when the name does not resolve -->
|
|
35
|
+
<u-icon name="might-not-exist" fallback='<svg viewBox="0 0 16 16">...</svg>'></u-icon>
|
|
36
|
+
```
|
|
37
|
+
|
|
33
38
|
For registering custom libraries, see [icons.md](../utilities/icons.md).
|
|
34
39
|
|
|
40
|
+
> **When you need `fallback`.** An icon that fails to resolve renders nothing. That is usually
|
|
41
|
+
> harmless — but not where the icon is the only hit target. A collapsed sidebar hides labels, so
|
|
42
|
+
> a nav item with no visible icon becomes an **empty row the user cannot click**: the screen
|
|
43
|
+
> becomes unreachable. `fallback` covers all three failure modes (no `name`, 404, unparseable SVG).
|
|
44
|
+
> Keep the fallback **inline**; a fallback that fetches reproduces the very failure it covers.
|
|
45
|
+
|
|
35
46
|
---
|
|
36
47
|
|
|
37
48
|
## Properties
|
|
@@ -41,6 +52,7 @@ For registering custom libraries, see [icons.md](../utilities/icons.md).
|
|
|
41
52
|
| `lib` | `'internal'\|'tabler'\|'heroicons'\|'lucide'\|'bootstrap'\|string` | — | Icon library identifier |
|
|
42
53
|
| `name` | `string` | — | Icon name (library-specific; append `:filled` / `:solid` for filled variants) |
|
|
43
54
|
| `src` | `string` | — | Raw SVG string (overrides `lib`/`name`) |
|
|
55
|
+
| `fallback` | `string` | — | Raw SVG drawn when `name`/`src` cannot be resolved (missing, 404, or unparseable) |
|
|
44
56
|
|
|
45
57
|
## CSS Parts
|
|
46
58
|
|
|
@@ -28,7 +28,7 @@ Animated circular spinner for loading states. Optional label below the spinner v
|
|
|
28
28
|
|
|
29
29
|
| Property | Type | Default | Reflect | Description |
|
|
30
30
|
|----------|------|---------|---------|-------------|
|
|
31
|
-
| `color` | `'neutral'\|'blue'\|'green'\|'yellow'\|'red'\|'orange'\|'teal'\|'cyan'\|'purple'\|'pink'` | — | ✓ | Spinner color preset |
|
|
31
|
+
| `color` | `'neutral'\|'primary'\|'info'\|'success'\|'warning'\|'danger'\|'blue'\|'green'\|'yellow'\|'red'\|'orange'\|'teal'\|'cyan'\|'purple'\|'pink'` | — | ✓ | Spinner color preset — role axis (`primary`…`danger`, follows rebranding) and decorative axis (`blue`…, immune to it) |
|
|
32
32
|
|
|
33
33
|
## CSS Parts
|
|
34
34
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# u-text
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import '@iyulab/components/dist/components/text/UText.js';
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
**Tag:** `u-text`
|
|
8
|
+
|
|
9
|
+
Semantic typography. Applies one of the seven design-token type steps
|
|
10
|
+
(`display` → `overline`) so markup can use the scale without any consumer CSS.
|
|
11
|
+
The component reads the token sheet — it never defines its own type values.
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<u-text level="1" variant="display">Document title</u-text>
|
|
15
|
+
<u-text variant="subtitle" tone="weak">One-line description</u-text>
|
|
16
|
+
|
|
17
|
+
<u-text>Body copy is the default step.</u-text>
|
|
18
|
+
<u-text variant="caption" tone="weak">Helper text</u-text>
|
|
19
|
+
|
|
20
|
+
<u-text variant="overline">Section</u-text>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Visual step and heading level are independent
|
|
24
|
+
|
|
25
|
+
`variant` chooses how the text *looks*; `level` chooses what it *is*. When
|
|
26
|
+
`level` is set the component renders a real `<h1>`–`<h6>` in its shadow root, so
|
|
27
|
+
assistive technology reads it as a heading whose accessible name is the slotted
|
|
28
|
+
text. Without `level` it renders a `<p>`.
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- Second-level heading that is visually the largest thing on the page -->
|
|
32
|
+
<u-text level="2" variant="display">Overview</u-text>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This keeps the document outline honest while leaving the visual hierarchy free.
|
|
36
|
+
|
|
37
|
+
> **Do not nest a heading inside the slot** — `<u-text><h2>…</h2></u-text>` makes
|
|
38
|
+
> the UA rule `h2 { font-size: 1.5em }` multiply the step's font size. Use
|
|
39
|
+
> `level` instead. Wrapping the other way (`<h2><u-text>…</u-text></h2>`) is safe
|
|
40
|
+
> because the steps are absolute sizes, but the outer heading keeps its UA
|
|
41
|
+
> margin, so `level` is still the simpler choice.
|
|
42
|
+
|
|
43
|
+
## Colour
|
|
44
|
+
|
|
45
|
+
`tone` is a **neutral emphasis axis**, not a role-colour axis. Semantic status
|
|
46
|
+
colour belongs to the component that owns the status (`u-alert`, `u-tag`,
|
|
47
|
+
`u-badge`), and link colour belongs to the link.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Slots
|
|
52
|
+
|
|
53
|
+
| Name | Description |
|
|
54
|
+
|------|-------------|
|
|
55
|
+
| *(default)* | Text content |
|
|
56
|
+
|
|
57
|
+
## Properties
|
|
58
|
+
|
|
59
|
+
| Property | Type | Default | Reflect | Description |
|
|
60
|
+
|----------|------|---------|---------|-------------|
|
|
61
|
+
| `variant` | `'display'\|'title'\|'subtitle'\|'body'\|'label'\|'caption'\|'overline'` | `'body'` | ✓ | Semantic type step |
|
|
62
|
+
| `tone` | `'default'\|'weak'\|'strong'\|'inverse'` | `'default'` | ✓ | Neutral emphasis axis |
|
|
63
|
+
| `level` | `1\|2\|3\|4\|5\|6` | — | ✓ | Heading level; renders `<h1>`–`<h6>` |
|
|
64
|
+
|
|
65
|
+
## CSS Parts
|
|
66
|
+
|
|
67
|
+
| Part | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `base` | The rendered `<p>` or `<h1>`–`<h6>` element |
|
|
70
|
+
|
|
71
|
+
## Design tokens read
|
|
72
|
+
|
|
73
|
+
| Step | Tokens |
|
|
74
|
+
|------|--------|
|
|
75
|
+
| `display` · `title` · `subtitle` · `body` · `label` · `caption` · `overline` | `--u-text-{step}-size`, `--u-text-{step}-weight`, `--u-text-{step}-leading`, `--u-text-{step}-tracking` |
|
|
76
|
+
| `tone` | `--u-txt-color`, `--u-txt-color-weak`, `--u-txt-color-strong`, `--u-txt-color-inverse` |
|
|
77
|
+
|
|
78
|
+
Spacing between blocks is **not** this component's concern — it has no margin.
|
|
79
|
+
The surrounding layout decides rhythm (`u-group-box`, `u-card`, a flex column).
|
|
@@ -78,6 +78,32 @@ accent surface ≥ 4.5:1, accent text on the page background ≥ 4.5:1, `-strong
|
|
|
78
78
|
from `-color`, and `-weak` usable as a non-text graphic (≥ 3:1). It is **recalculated when the
|
|
79
79
|
theme changes**, because those targets are relative to the page background.
|
|
80
80
|
|
|
81
|
+
#### How many tokens does the `primary` role have? **Seven.**
|
|
82
|
+
|
|
83
|
+
`Theme.accent()` sets **six** of them:
|
|
84
|
+
|
|
85
|
+
| Token | `Theme.accent()` | What reads it |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `--u-primary-color` | ✅ | accent surface — 21 source files |
|
|
88
|
+
| `--u-primary-color-strong` | ✅ | text/icons on the page background; hover · active · link · focus ring — 14 files |
|
|
89
|
+
| `--u-primary-color-weak` | ✅ | non-text graphics |
|
|
90
|
+
| `--u-primary-color-weaker` | ✅ | decorative |
|
|
91
|
+
| `--u-primary-color-weakest` | ✅ | decorative |
|
|
92
|
+
| `--u-primary-txt-color` | ✅ | text on the accent surface |
|
|
93
|
+
| **`--u-primary-bg-color`** | 🔴**no** | tinted **surface behind text** — `u-tag`'s `--tag-hue-surface` |
|
|
94
|
+
|
|
95
|
+
🔴 **`Theme.accent()` does not touch `--u-primary-bg-color`.** It stays on the sheet default
|
|
96
|
+
(a blue tint), so a seeded brand leaves tinted surfaces blue. Set it yourself alongside the seed:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
Theme.accent('#7c3aed');
|
|
100
|
+
document.documentElement.style.setProperty('--u-primary-bg-color', '#f3e8ff');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Pick a tint that keeps your body text readable on it — the built-in sheet values sit at roughly
|
|
104
|
+
1.14:1 (light) and 1.03:1 (dark) against the page background. Deriving this step automatically is
|
|
105
|
+
tracked as open work, because the sheet pairs it by hand across five colour roles and two themes.
|
|
106
|
+
|
|
81
107
|
**Manual override** — you must set the steps you use, not just one:
|
|
82
108
|
|
|
83
109
|
```css
|
|
@@ -86,11 +112,36 @@ theme changes**, because those targets are relative to the page background.
|
|
|
86
112
|
--u-primary-color: #7c3aed; /* accent surface */
|
|
87
113
|
--u-primary-color-strong: #5b21b6; /* text/icons on the page background */
|
|
88
114
|
--u-primary-txt-color: #ffffff; /* text on the accent surface */
|
|
115
|
+
--u-primary-bg-color: #f3e8ff; /* tinted surface behind text */
|
|
89
116
|
}
|
|
90
117
|
```
|
|
91
118
|
|
|
92
119
|
⚠ Setting `--u-primary-color` alone is **not enough**: hover/focus/link colors resolve from
|
|
93
120
|
`--u-primary-color-strong`, so they stay on the default ramp and your brand looks half-applied.
|
|
121
|
+
The measured symptom is a *selected table row* or *tag* that stays blue while buttons turn brand —
|
|
122
|
+
that one is `--u-primary-bg-color`.
|
|
123
|
+
|
|
124
|
+
### Typography
|
|
125
|
+
|
|
126
|
+
The sheet defines seven semantic steps — `display`, `title`, `subtitle`, `body`, `label`,
|
|
127
|
+
`caption`, `overline` — each with `-size`, `-weight`, `-leading`, `-tracking`. Use them from
|
|
128
|
+
markup with [`u-text`](./components/text.md) instead of writing your own CSS:
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<u-text level="1" variant="display">Document title</u-text>
|
|
132
|
+
<u-text variant="subtitle" tone="weak">One-line description</u-text>
|
|
133
|
+
<u-text>Body copy</u-text>
|
|
134
|
+
<u-text variant="caption" tone="weak">Helper text</u-text>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`variant` is the visual step and `level` is the document level — they are independent, and
|
|
138
|
+
`level` renders a real `<h1>`–`<h6>` so the heading is read as one.
|
|
139
|
+
|
|
140
|
+
To rebrand typography, override the tokens rather than the screens:
|
|
141
|
+
|
|
142
|
+
```css
|
|
143
|
+
:root { --u-text-title-size: 22px; --u-text-title-weight: 800; }
|
|
144
|
+
```
|
|
94
145
|
|
|
95
146
|
---
|
|
96
147
|
|