@celestia-island/hikari 0.45.5 → 0.45.6
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/package.json +1 -1
- package/src/components/HkAboutModal.scss +154 -47
- package/src/components/HkAboutModal.test.tsx +67 -10
- package/src/components/HkAboutModal.tsx +90 -55
- package/src/index.ts +5 -1
package/package.json
CHANGED
|
@@ -30,25 +30,39 @@
|
|
|
30
30
|
gap: var(--space-16, 1rem);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/* ── Identity hero: haloed logo → name → version · tagline ─────────────── */
|
|
34
|
+
|
|
33
35
|
.s-about-modal-header {
|
|
34
36
|
display: flex;
|
|
35
37
|
flex-direction: column;
|
|
36
38
|
align-items: center;
|
|
37
|
-
gap: var(--space-
|
|
39
|
+
gap: var(--space-10, 0.625rem);
|
|
38
40
|
text-align: center;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// Framed mark. The app logo is a light-on-light illustration (a white tablet
|
|
44
|
+
// render with its own baked soft shadow), so it needs a tinted, bounded bed:
|
|
45
|
+
// floating on the modal surface it read as a stray smudge, and the same bed
|
|
46
|
+
// gives it contrast on dark themes without hard-coding a mode.
|
|
47
|
+
.s-about-modal-logo-frame {
|
|
48
|
+
display: grid;
|
|
49
|
+
place-items: center;
|
|
50
|
+
width: 5rem;
|
|
51
|
+
height: 5rem;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
border: 1px solid rgb(var(--color-primary) / 14%);
|
|
54
|
+
border-radius: var(--radius-xl, 16px);
|
|
55
|
+
background: linear-gradient(
|
|
56
|
+
155deg,
|
|
57
|
+
rgb(var(--color-primary) / 13%),
|
|
58
|
+
rgb(var(--color-primary) / 4%)
|
|
59
|
+
);
|
|
60
|
+
box-shadow: 0 6px 18px rgb(var(--color-primary) / 12%);
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
.s-about-modal-logo-img {
|
|
48
|
-
width:
|
|
49
|
-
height:
|
|
50
|
-
flex-shrink: 0;
|
|
51
|
-
border-radius: var(--radius-lg, 12px);
|
|
64
|
+
width: 3.75rem;
|
|
65
|
+
height: 3.75rem;
|
|
52
66
|
object-fit: contain;
|
|
53
67
|
user-select: none;
|
|
54
68
|
}
|
|
@@ -57,35 +71,69 @@
|
|
|
57
71
|
display: flex;
|
|
58
72
|
align-items: center;
|
|
59
73
|
justify-content: center;
|
|
60
|
-
width:
|
|
61
|
-
height:
|
|
62
|
-
flex-shrink: 0;
|
|
74
|
+
width: 3.25rem;
|
|
75
|
+
height: 3.25rem;
|
|
63
76
|
border-radius: var(--radius-lg, 12px);
|
|
64
|
-
background:
|
|
77
|
+
background: linear-gradient(
|
|
78
|
+
150deg,
|
|
79
|
+
rgb(var(--color-primary)),
|
|
80
|
+
rgb(var(--color-secondary, var(--color-primary)))
|
|
81
|
+
);
|
|
65
82
|
color: rgb(var(--color-on-solid-text, 255 255 255));
|
|
66
|
-
font-size: var(--text-
|
|
83
|
+
font-size: var(--text-xl, 1.25rem);
|
|
67
84
|
font-weight: 700;
|
|
68
85
|
}
|
|
69
86
|
|
|
87
|
+
.s-about-modal-identity {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: var(--space-2, 0.125rem);
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
.s-about-modal-name {
|
|
71
95
|
margin: 0;
|
|
72
|
-
font-size: var(--text-
|
|
96
|
+
font-size: var(--text-xl, 1.25rem);
|
|
73
97
|
font-weight: 700;
|
|
98
|
+
line-height: 1.3;
|
|
99
|
+
letter-spacing: 0.01em;
|
|
74
100
|
color: rgb(var(--color-text));
|
|
75
101
|
}
|
|
76
102
|
|
|
103
|
+
// Version and tagline share one compact muted line — two stacked lines of
|
|
104
|
+
// equal weight made the hero read as a form.
|
|
105
|
+
.s-about-modal-subtitle {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-wrap: wrap;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
gap: var(--space-6, 0.375rem);
|
|
111
|
+
margin: 0;
|
|
112
|
+
font-size: var(--text-xs, 0.75rem);
|
|
113
|
+
line-height: 1.5;
|
|
114
|
+
color: rgb(var(--color-muted));
|
|
115
|
+
}
|
|
116
|
+
|
|
77
117
|
.s-about-modal-version {
|
|
78
|
-
|
|
79
|
-
|
|
118
|
+
font-variant-numeric: tabular-nums;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.s-about-modal-subtitle-sep {
|
|
122
|
+
color: rgb(var(--color-border));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.s-about-modal-tagline {
|
|
80
126
|
color: rgb(var(--color-muted));
|
|
81
127
|
}
|
|
82
128
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
129
|
+
/* ── Credits: made-by sentence + organization blurb ────────────────────── */
|
|
130
|
+
|
|
131
|
+
.s-about-modal-credits {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: var(--space-4, 0.25rem);
|
|
136
|
+
margin-top: calc(-1 * var(--space-4, 0.25rem));
|
|
89
137
|
}
|
|
90
138
|
|
|
91
139
|
.s-about-modal-made-by {
|
|
@@ -95,10 +143,17 @@
|
|
|
95
143
|
color: rgb(var(--color-text));
|
|
96
144
|
}
|
|
97
145
|
|
|
98
|
-
.s-about-modal-
|
|
99
|
-
margin:
|
|
100
|
-
|
|
101
|
-
|
|
146
|
+
.s-about-modal-made-by .s-about-modal-row-link {
|
|
147
|
+
margin: 0 0.15em;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.s-about-modal-row-link {
|
|
151
|
+
color: rgb(var(--color-primary));
|
|
152
|
+
text-decoration: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.s-about-modal-row-link:hover {
|
|
156
|
+
text-decoration: underline;
|
|
102
157
|
}
|
|
103
158
|
|
|
104
159
|
.s-about-modal-description {
|
|
@@ -106,45 +161,84 @@
|
|
|
106
161
|
font-size: var(--text-xs, 0.75rem);
|
|
107
162
|
line-height: 1.6;
|
|
108
163
|
text-align: center;
|
|
164
|
+
text-wrap: pretty;
|
|
109
165
|
color: rgb(var(--color-muted));
|
|
110
166
|
}
|
|
111
167
|
|
|
168
|
+
/* ── Spec card: component version rows ────────────────────────────────── */
|
|
169
|
+
|
|
112
170
|
.s-about-modal-rows {
|
|
113
171
|
display: flex;
|
|
114
172
|
flex-direction: column;
|
|
115
|
-
|
|
173
|
+
padding: var(--space-2, 0.125rem) var(--space-14, 0.875rem);
|
|
174
|
+
border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
|
|
175
|
+
border-radius: var(--radius-lg, 12px);
|
|
176
|
+
background: rgb(var(--color-primary) / 3%);
|
|
116
177
|
}
|
|
117
178
|
|
|
118
179
|
.s-about-modal-row {
|
|
119
180
|
display: flex;
|
|
120
181
|
align-items: center;
|
|
121
182
|
justify-content: space-between;
|
|
122
|
-
gap: var(--space-
|
|
183
|
+
gap: var(--space-12, 0.75rem);
|
|
184
|
+
min-height: 2.125rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.s-about-modal-row + .s-about-modal-row {
|
|
188
|
+
border-top: 1px solid var(--border-faint, rgb(100 100 100 / 10%));
|
|
123
189
|
}
|
|
124
190
|
|
|
125
191
|
.s-about-modal-row-label {
|
|
126
|
-
|
|
192
|
+
flex: 0 1 auto;
|
|
193
|
+
font-size: var(--text-xs, 0.75rem);
|
|
127
194
|
color: rgb(var(--color-muted));
|
|
128
195
|
}
|
|
129
196
|
|
|
130
197
|
.s-about-modal-row-value {
|
|
131
|
-
|
|
132
|
-
|
|
198
|
+
display: inline-flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: var(--space-8, 0.5rem);
|
|
201
|
+
flex: 0 0 auto;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.s-about-modal-row-number {
|
|
133
205
|
font-family: var(--font-mono, vars.$hikari-font-family-mono);
|
|
206
|
+
font-size: var(--text-xs, 0.75rem);
|
|
207
|
+
font-variant-numeric: tabular-nums;
|
|
208
|
+
letter-spacing: -0.01em;
|
|
209
|
+
color: rgb(var(--color-text));
|
|
134
210
|
}
|
|
135
211
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
212
|
+
// Environment word (生产 / 测试) lives in its own pill so it never melts
|
|
213
|
+
// into the version string.
|
|
214
|
+
.s-about-modal-row-meta {
|
|
215
|
+
padding: 0 var(--space-6, 0.375rem);
|
|
216
|
+
border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
|
|
217
|
+
border-radius: var(--radius-full, 9999px);
|
|
218
|
+
font-size: var(--text-2xs, 0.625rem);
|
|
219
|
+
line-height: 1.6;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
color: rgb(var(--color-muted));
|
|
139
222
|
}
|
|
140
223
|
|
|
141
|
-
.s-about-modal-row-
|
|
142
|
-
|
|
224
|
+
.s-about-modal-row-meta[data-tone="positive"] {
|
|
225
|
+
border-color: rgb(var(--color-success) / 32%);
|
|
226
|
+
background: rgb(var(--color-success) / 10%);
|
|
227
|
+
color: rgb(var(--color-success));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.s-about-modal-row-meta[data-tone="caution"] {
|
|
231
|
+
border-color: rgb(var(--color-warning) / 32%);
|
|
232
|
+
background: rgb(var(--color-warning) / 10%);
|
|
233
|
+
color: rgb(var(--color-warning));
|
|
143
234
|
}
|
|
144
235
|
|
|
145
|
-
|
|
236
|
+
/* ── Chips: licenses and external links ───────────────────────────────── */
|
|
237
|
+
|
|
238
|
+
.s-about-modal-chips {
|
|
146
239
|
display: flex;
|
|
147
240
|
flex-direction: column;
|
|
241
|
+
align-items: center;
|
|
148
242
|
gap: var(--space-8, 0.5rem);
|
|
149
243
|
}
|
|
150
244
|
|
|
@@ -152,38 +246,50 @@
|
|
|
152
246
|
display: flex;
|
|
153
247
|
flex-wrap: wrap;
|
|
154
248
|
justify-content: center;
|
|
155
|
-
gap: var(--space-
|
|
249
|
+
gap: var(--space-6, 0.375rem);
|
|
156
250
|
}
|
|
157
251
|
|
|
252
|
+
// Ghost pills: the chips are secondary navigation, so they carry a hairline
|
|
253
|
+
// instead of a filled brand block.
|
|
158
254
|
.s-about-modal-link {
|
|
159
|
-
padding: var(--space-
|
|
160
|
-
border
|
|
161
|
-
|
|
162
|
-
color: rgb(var(--color-
|
|
255
|
+
padding: var(--space-2, 0.125rem) var(--space-10, 0.625rem);
|
|
256
|
+
border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
|
|
257
|
+
border-radius: var(--radius-full, 9999px);
|
|
258
|
+
color: rgb(var(--color-muted));
|
|
163
259
|
font-size: var(--text-xs, 0.75rem);
|
|
260
|
+
line-height: 1.7;
|
|
164
261
|
text-decoration: none;
|
|
165
|
-
transition:
|
|
262
|
+
transition:
|
|
263
|
+
color var(--duration-short, 0.15s) ease,
|
|
264
|
+
border-color var(--duration-short, 0.15s) ease,
|
|
265
|
+
background var(--duration-short, 0.15s) ease;
|
|
166
266
|
}
|
|
167
267
|
|
|
168
268
|
.s-about-modal-link:hover {
|
|
169
|
-
|
|
269
|
+
border-color: var(--c-primary-strong, rgb(var(--color-primary) / 40%));
|
|
270
|
+
background: var(--c-primary-faint, rgb(var(--color-primary) / 5%));
|
|
271
|
+
color: rgb(var(--color-primary));
|
|
170
272
|
}
|
|
171
273
|
|
|
172
274
|
.s-about-modal-link:active {
|
|
173
|
-
background: rgb(var(--color-primary) /
|
|
275
|
+
background: var(--c-primary-subtle, rgb(var(--color-primary) / 8%));
|
|
174
276
|
}
|
|
175
277
|
|
|
176
278
|
.s-about-modal-link:focus-visible {
|
|
279
|
+
outline: none;
|
|
177
280
|
box-shadow:
|
|
178
281
|
0 0 0 2px rgb(var(--color-surface)),
|
|
179
282
|
0 0 0 4px rgb(var(--color-primary));
|
|
180
283
|
}
|
|
181
284
|
|
|
285
|
+
/* ── Legal footer: filing links + copyright ───────────────────────────── */
|
|
286
|
+
|
|
182
287
|
.s-about-modal-footer-links {
|
|
183
288
|
display: flex;
|
|
184
289
|
flex-wrap: wrap;
|
|
185
290
|
justify-content: center;
|
|
186
|
-
gap: var(--space-
|
|
291
|
+
gap: var(--space-16, 1rem);
|
|
292
|
+
margin-top: calc(-1 * var(--space-4, 0.25rem));
|
|
187
293
|
}
|
|
188
294
|
|
|
189
295
|
.s-about-modal-footer-link {
|
|
@@ -200,4 +306,5 @@
|
|
|
200
306
|
.s-about-modal-footer {
|
|
201
307
|
display: flex;
|
|
202
308
|
justify-content: center;
|
|
309
|
+
margin-top: calc(-1 * var(--space-6, 0.375rem));
|
|
203
310
|
}
|
|
@@ -5,14 +5,16 @@ import { HkAboutModal } from "./HkAboutModal";
|
|
|
5
5
|
import { setLocale } from "../i18n/context";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* HkAboutModal contract tests for the branding
|
|
8
|
+
* HkAboutModal contract tests for the branding + redesign waves:
|
|
9
9
|
* - the first-letter tile is the default identity and `logoSrc` swaps it
|
|
10
|
-
* for an image
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
10
|
+
* for an image inside the identity frame
|
|
11
|
+
* - version and tagline share one subtitle line
|
|
12
|
+
* - description / made-by / license / copyright render the given strings,
|
|
13
|
+
* madeByNameHref turns the author name into an external link
|
|
14
|
+
* - component rows split the machine value from an optional meta pill
|
|
13
15
|
* - the backdrop factory renders inside the clipped backdrop layer
|
|
14
|
-
* - links render as
|
|
15
|
-
* -
|
|
16
|
+
* - licenses and links render as chips under one centered block
|
|
17
|
+
* - footer (filing) links render as external links
|
|
16
18
|
*
|
|
17
19
|
* (Repo test convention: raw createApp + document queries, no
|
|
18
20
|
* @vue/test-utils dependency.)
|
|
@@ -117,10 +119,10 @@ describe("HkAboutModal branding", () => {
|
|
|
117
119
|
expect(document.body.querySelector(".s-about-modal-row-link")).toBeNull();
|
|
118
120
|
});
|
|
119
121
|
|
|
120
|
-
it("renders software-component version rows", async () => {
|
|
122
|
+
it("renders software-component version rows with a meta pill and tone", async () => {
|
|
121
123
|
mountAbout({
|
|
122
124
|
componentVersions: [
|
|
123
|
-
{ label: "WebUI 版本", value: "0.1.138 REW2HF 生产" },
|
|
125
|
+
{ label: "WebUI 版本", value: "0.1.138 REW2HF", meta: "生产", metaTone: "positive" },
|
|
124
126
|
{ label: "计算引擎版本", value: "0.1.0" },
|
|
125
127
|
],
|
|
126
128
|
});
|
|
@@ -128,8 +130,25 @@ describe("HkAboutModal branding", () => {
|
|
|
128
130
|
const rows = [...document.body.querySelectorAll<HTMLElement>(".s-about-modal-row")];
|
|
129
131
|
expect(rows.length).toBe(2);
|
|
130
132
|
expect(rows[0]!.textContent).toContain("WebUI 版本");
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
// The number and the environment word are separate nodes: the value
|
|
134
|
+
// stays machine-readable, the tag carries the typography.
|
|
135
|
+
const number = query<HTMLElement>(".s-about-modal-row-number");
|
|
136
|
+
expect(number.textContent).toBe("0.1.138 REW2HF");
|
|
137
|
+
const meta = query<HTMLElement>(".s-about-modal-row-meta");
|
|
138
|
+
expect(meta.textContent).toBe("生产");
|
|
139
|
+
expect(meta.getAttribute("data-tone")).toBe("positive");
|
|
140
|
+
// The second row has no meta, so it renders no pill.
|
|
141
|
+
expect(rows[1]!.querySelector(".s-about-modal-row-meta")).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("defaults the meta pill to the neutral tone", async () => {
|
|
145
|
+
mountAbout({
|
|
146
|
+
componentVersions: [{ label: "计算引擎版本", value: "0.1.0", meta: "测试" }],
|
|
147
|
+
});
|
|
148
|
+
await flushModal();
|
|
149
|
+
expect(query<HTMLElement>(".s-about-modal-row-meta").getAttribute("data-tone")).toBe(
|
|
150
|
+
"neutral",
|
|
151
|
+
);
|
|
133
152
|
});
|
|
134
153
|
|
|
135
154
|
it("renders centered license chips", async () => {
|
|
@@ -205,5 +224,43 @@ describe("HkAboutModal branding", () => {
|
|
|
205
224
|
expect(document.body.querySelectorAll(".s-about-modal-row").length).toBe(0);
|
|
206
225
|
expect(document.body.querySelector(".s-about-modal-made-by")).toBeNull();
|
|
207
226
|
expect(document.body.querySelector(".s-about-modal-links")).toBeNull();
|
|
227
|
+
expect(document.body.querySelector(".s-about-modal-chips")).toBeNull();
|
|
228
|
+
expect(document.body.querySelector(".s-about-modal-credits")).toBeNull();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("groups license and link chips under one centered chips block", async () => {
|
|
232
|
+
mountAbout({
|
|
233
|
+
licenses: [{ label: "SySL-1.0", href: "https://example.test/sysl" }],
|
|
234
|
+
links: [{ label: "celestia.world", href: "https://celestia.world" }],
|
|
235
|
+
});
|
|
236
|
+
await flushModal();
|
|
237
|
+
const chips = query<HTMLElement>(".s-about-modal-chips");
|
|
238
|
+
const slots = [...chips.querySelectorAll<HTMLElement>(".s-about-modal-links")].map((el) =>
|
|
239
|
+
el.getAttribute("data-slot"),
|
|
240
|
+
);
|
|
241
|
+
expect(slots).toEqual(["licenses", "links"]);
|
|
242
|
+
expect(
|
|
243
|
+
[...chips.querySelectorAll<HTMLAnchorElement>(".s-about-modal-link")].map(
|
|
244
|
+
(chip) => chip.textContent,
|
|
245
|
+
),
|
|
246
|
+
).toEqual(["SySL-1.0", "celestia.world"]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("frames the logo image inside the identity frame", async () => {
|
|
250
|
+
mountAbout({ logoSrc: "data:image/webp;base64,AAA" });
|
|
251
|
+
await flushModal();
|
|
252
|
+
const frame = query<HTMLElement>(".s-about-modal-logo-frame");
|
|
253
|
+
expect(frame.querySelector("img.s-about-modal-logo-img")).toBeTruthy();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("keeps version and tagline on one subtitle line", async () => {
|
|
257
|
+
mountAbout({ version: "9.9.9", tagline: "Chat with your agents" });
|
|
258
|
+
await flushModal();
|
|
259
|
+
const subtitle = query<HTMLElement>(".s-about-modal-subtitle");
|
|
260
|
+
expect(subtitle.querySelector(".s-about-modal-version")?.textContent).toContain("9.9.9");
|
|
261
|
+
expect(subtitle.querySelector(".s-about-modal-tagline")?.textContent).toBe(
|
|
262
|
+
"Chat with your agents",
|
|
263
|
+
);
|
|
264
|
+
expect(subtitle.querySelector(".s-about-modal-subtitle-sep")).toBeTruthy();
|
|
208
265
|
});
|
|
209
266
|
});
|
|
@@ -10,23 +10,34 @@ export interface HAboutLink {
|
|
|
10
10
|
href: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* One software-component version row.
|
|
15
|
+
*
|
|
16
|
+
* `value` is the machine-readable half (version, optionally plus a build
|
|
17
|
+
* hash) and renders in the mono/tabular face; `meta` is the optional
|
|
18
|
+
* human-facing trailing tag (e.g. the environment word 生产 / 测试),
|
|
19
|
+
* rendered as a separate muted pill so it never melts into the version
|
|
20
|
+
* string. `metaTone` tints that pill when the tag carries a status
|
|
21
|
+
* meaning (e.g. a production environment).
|
|
22
|
+
*/
|
|
14
23
|
export interface HAboutComponentVersion {
|
|
15
24
|
label: string;
|
|
16
25
|
value: string;
|
|
26
|
+
meta?: string;
|
|
27
|
+
metaTone?: "neutral" | "positive" | "caution";
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
/**
|
|
20
31
|
* HkAboutModal — version / about dialog.
|
|
21
32
|
* (Upstreamed from shittim-chest's plana-legacy layer.)
|
|
22
33
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
34
|
+
* Layout (2026-09 redesign): a centered identity hero (haloed logo, name,
|
|
35
|
+
* then version + tagline on one compact line), an optional credits block
|
|
36
|
+
* (made-by sentence with a linked author name + organization blurb), a
|
|
37
|
+
* bordered spec card holding the software-component versions, ghost chip
|
|
38
|
+
* rows for licenses / external links, and a muted legal footer (filing
|
|
39
|
+
* links + copyright). Every branding prop is optional — the modal degrades
|
|
40
|
+
* to the plain identity card when none are given.
|
|
30
41
|
*/
|
|
31
42
|
export const HkAboutModal = defineComponent({
|
|
32
43
|
name: "HkAboutModal",
|
|
@@ -38,9 +49,9 @@ export const HkAboutModal = defineComponent({
|
|
|
38
49
|
version: { type: String, required: true },
|
|
39
50
|
/** Optional logo image URL — replaces the first-letter tile. */
|
|
40
51
|
logoSrc: { type: String, default: undefined },
|
|
41
|
-
/** Optional one-liner
|
|
52
|
+
/** Optional one-liner shown beside the version (e.g. the app tagline). */
|
|
42
53
|
tagline: { type: String, default: undefined },
|
|
43
|
-
/** Optional centered small line under the
|
|
54
|
+
/** Optional centered small line under the credits (organization blurb). */
|
|
44
55
|
description: { type: String, default: undefined },
|
|
45
56
|
/** Made-by sentence: text before the linked author name (e.g. "由"). */
|
|
46
57
|
madeByPrefix: { type: String, default: undefined },
|
|
@@ -100,6 +111,27 @@ export const HkAboutModal = defineComponent({
|
|
|
100
111
|
);
|
|
101
112
|
};
|
|
102
113
|
|
|
114
|
+
const renderChips = (items: HAboutLink[], slot: string) => {
|
|
115
|
+
if (items.length === 0) return null;
|
|
116
|
+
return (
|
|
117
|
+
<div class="s-about-modal-links" data-slot={slot}>
|
|
118
|
+
<div class="s-about-modal-links-list">
|
|
119
|
+
{items.map((item) => (
|
|
120
|
+
<a
|
|
121
|
+
key={item.href}
|
|
122
|
+
href={item.href}
|
|
123
|
+
target="_blank"
|
|
124
|
+
rel="noopener noreferrer"
|
|
125
|
+
class="s-about-modal-link"
|
|
126
|
+
>
|
|
127
|
+
{item.label}
|
|
128
|
+
</a>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
103
135
|
return () => (
|
|
104
136
|
<HModal
|
|
105
137
|
modelValue={props.modelValue}
|
|
@@ -115,67 +147,70 @@ export const HkAboutModal = defineComponent({
|
|
|
115
147
|
)}
|
|
116
148
|
<div class="s-about-modal-body">
|
|
117
149
|
<header class="s-about-modal-header">
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
<div class="s-about-modal-logo-frame">
|
|
151
|
+
{props.logoSrc ? (
|
|
152
|
+
<img
|
|
153
|
+
class="s-about-modal-logo-img"
|
|
154
|
+
src={props.logoSrc}
|
|
155
|
+
alt=""
|
|
156
|
+
draggable={false}
|
|
157
|
+
/>
|
|
158
|
+
) : (
|
|
159
|
+
<div class="s-about-modal-logo">{props.appName.slice(0, 1).toUpperCase()}</div>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
<div class="s-about-modal-identity">
|
|
124
163
|
<h2 class="s-about-modal-name">{props.appName}</h2>
|
|
125
|
-
<p class="s-about-modal-
|
|
126
|
-
|
|
164
|
+
<p class="s-about-modal-subtitle">
|
|
165
|
+
<span class="s-about-modal-version">
|
|
166
|
+
{t("hikari::about.version", "Version")} {props.version}
|
|
167
|
+
</span>
|
|
168
|
+
{props.tagline && (
|
|
169
|
+
<>
|
|
170
|
+
<span class="s-about-modal-subtitle-sep" aria-hidden="true">
|
|
171
|
+
·
|
|
172
|
+
</span>
|
|
173
|
+
<span class="s-about-modal-tagline">{props.tagline}</span>
|
|
174
|
+
</>
|
|
175
|
+
)}
|
|
127
176
|
</p>
|
|
128
|
-
{props.tagline && <p class="s-about-modal-tagline">{props.tagline}</p>}
|
|
129
177
|
</div>
|
|
130
178
|
</header>
|
|
131
179
|
|
|
132
|
-
{
|
|
133
|
-
|
|
180
|
+
{(props.madeByName || props.description) && (
|
|
181
|
+
<div class="s-about-modal-credits">
|
|
182
|
+
{renderMadeBy()}
|
|
183
|
+
{props.description && (
|
|
184
|
+
<p class="s-about-modal-description">{props.description}</p>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
134
188
|
|
|
135
189
|
{props.componentVersions.length > 0 && (
|
|
136
190
|
<div class="s-about-modal-rows">
|
|
137
191
|
{props.componentVersions.map((component) => (
|
|
138
192
|
<div class="s-about-modal-row" key={component.label}>
|
|
139
193
|
<span class="s-about-modal-row-label">{component.label}</span>
|
|
140
|
-
<span class="s-about-modal-row-value">
|
|
194
|
+
<span class="s-about-modal-row-value">
|
|
195
|
+
<span class="s-about-modal-row-number">{component.value}</span>
|
|
196
|
+
{component.meta && (
|
|
197
|
+
<span
|
|
198
|
+
class="s-about-modal-row-meta"
|
|
199
|
+
data-tone={component.metaTone ?? "neutral"}
|
|
200
|
+
>
|
|
201
|
+
{component.meta}
|
|
202
|
+
</span>
|
|
203
|
+
)}
|
|
204
|
+
</span>
|
|
141
205
|
</div>
|
|
142
206
|
))}
|
|
143
207
|
</div>
|
|
144
208
|
)}
|
|
145
209
|
|
|
146
|
-
{props.licenses.length > 0 && (
|
|
147
|
-
<div class="s-about-modal-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<a
|
|
151
|
-
key={license.href}
|
|
152
|
-
href={license.href}
|
|
153
|
-
target="_blank"
|
|
154
|
-
rel="noopener noreferrer"
|
|
155
|
-
class="s-about-modal-link"
|
|
156
|
-
>
|
|
157
|
-
{license.label}
|
|
158
|
-
</a>
|
|
159
|
-
))}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
)}
|
|
163
|
-
|
|
164
|
-
{props.links.length > 0 && (
|
|
165
|
-
<div class="s-about-modal-links">
|
|
166
|
-
<div class="s-about-modal-links-list">
|
|
167
|
-
{props.links.map((link) => (
|
|
168
|
-
<a
|
|
169
|
-
key={link.href}
|
|
170
|
-
href={link.href}
|
|
171
|
-
target="_blank"
|
|
172
|
-
rel="noopener noreferrer"
|
|
173
|
-
class="s-about-modal-link"
|
|
174
|
-
>
|
|
175
|
-
{link.label}
|
|
176
|
-
</a>
|
|
177
|
-
))}
|
|
178
|
-
</div>
|
|
210
|
+
{(props.licenses.length > 0 || props.links.length > 0) && (
|
|
211
|
+
<div class="s-about-modal-chips">
|
|
212
|
+
{renderChips(props.licenses, "licenses")}
|
|
213
|
+
{renderChips(props.links, "links")}
|
|
179
214
|
</div>
|
|
180
215
|
)}
|
|
181
216
|
|
package/src/index.ts
CHANGED
|
@@ -353,7 +353,11 @@ export { HkSecretRevealModal as HSecretRevealModal } from "./components/HkSecret
|
|
|
353
353
|
export { HkCaptchaWidget as HCaptchaWidget, type HkCaptchaProvider } from "./components/HkCaptchaWidget";
|
|
354
354
|
export { HkCaptchaModal as HCaptchaModal } from "./components/HkCaptchaModal";
|
|
355
355
|
export { HkProtocolModal as HProtocolModal } from "./components/HkProtocolModal";
|
|
356
|
-
export {
|
|
356
|
+
export {
|
|
357
|
+
HkAboutModal as HAboutModal,
|
|
358
|
+
type HAboutComponentVersion,
|
|
359
|
+
type HAboutLink,
|
|
360
|
+
} from "./components/HkAboutModal";
|
|
357
361
|
export { HkLogWindow as HLogWindow, type HLogTab } from "./components/HkLogWindow";
|
|
358
362
|
export { HkCookieConsent as HCookieConsent } from "./components/HkCookieConsent";
|
|
359
363
|
export {
|