@celestia-island/hikari 0.51.3 → 0.52.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.51.3",
3
+ "version": "0.52.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -295,6 +295,28 @@
295
295
  vertical-align: baseline;
296
296
  }
297
297
 
298
+ // Accent face: bare text like `plain`, but tinted in the primary colour so
299
+ // the link visibly stands out of the run it sits in — the "this is
300
+ // clickable" cue (user direction 2026-09-15; the pure `plain` face melted
301
+ // the credits names into the sentence too well). No frame, no pill padding,
302
+ // and no underline at rest; the hover deepens the tint and adds one.
303
+ //
304
+ // Same specificity story as the `plain` block above: this rule (0,2,0) beats
305
+ // the chip base (0,1,0), and the `[data-face="accent"]:hover` / `:active`
306
+ // rules below (0,3,0) keep the chip's tinted hover box off the bare text.
307
+ .s-about-modal-link[data-face="accent"] {
308
+ display: inline;
309
+ margin: 0;
310
+ padding: 0;
311
+ border: 0;
312
+ border-radius: 0;
313
+ background: none;
314
+ color: rgb(var(--color-primary));
315
+ font-size: inherit;
316
+ line-height: inherit;
317
+ vertical-align: baseline;
318
+ }
319
+
298
320
  // A link carrying an icon is one aligned unit (the mark, then the optional
299
321
  // label) — icon-only links are how the GitHub mark rides beside the domains.
300
322
  // Same specificity as the `plain` block above, so source order decides: this
@@ -339,6 +361,15 @@
339
361
  color: rgb(var(--color-primary));
340
362
  }
341
363
 
364
+ // The accent face already rests in the primary colour, so the hover cue is
365
+ // the deepened tint plus an underline — the glyph colour alone cannot move.
366
+ .s-about-modal-link[data-face="accent"]:hover {
367
+ background: none;
368
+ color: var(--c-primary-strong, rgb(var(--color-primary) / 80%));
369
+ text-decoration: underline;
370
+ text-underline-offset: 0.15em;
371
+ }
372
+
342
373
  .s-about-modal-link:active {
343
374
  background: var(--c-primary-subtle, rgb(var(--color-primary) / 8%));
344
375
  }
@@ -348,6 +379,11 @@
348
379
  color: var(--c-primary-strong, rgb(var(--color-primary) / 40%));
349
380
  }
350
381
 
382
+ .s-about-modal-link[data-face="accent"]:active {
383
+ background: none;
384
+ color: rgb(var(--color-muted));
385
+ }
386
+
351
387
  .s-about-modal-link:focus-visible {
352
388
  outline: none;
353
389
  box-shadow:
@@ -136,6 +136,44 @@ describe("HkAboutModal stylesheet contract", () => {
136
136
  expect(declaration(ruleBody(css, `${PLAIN}:active`), "background")).toBe("none");
137
137
  });
138
138
 
139
+ const ACCENT = ".s-about-modal-link[data-face=accent]";
140
+
141
+ it("keeps the accent face a bare text link tinted in the primary colour", () => {
142
+ const accent = ruleBody(css, ACCENT);
143
+ expect(accent, "the accent-face rule must survive compilation").not.toBe("");
144
+ expect({
145
+ padding: declaration(accent, "padding"),
146
+ border: declaration(accent, "border"),
147
+ radius: declaration(accent, "border-radius"),
148
+ background: declaration(accent, "background"),
149
+ // The tint IS the face: bare geometry like `plain`, but the colour
150
+ // comes from the theme's primary token rather than `inherit`.
151
+ color: declaration(accent, "color"),
152
+ fontSize: declaration(accent, "font-size"),
153
+ lineHeight: declaration(accent, "line-height"),
154
+ }).toEqual({
155
+ padding: "0",
156
+ border: "0",
157
+ radius: "0",
158
+ background: "none",
159
+ color: "rgb(var(--color-primary))",
160
+ fontSize: "inherit",
161
+ lineHeight: "inherit",
162
+ });
163
+ // No underline at rest — the tint is the cue; the hover adds one.
164
+ expect(declaration(accent, "text-decoration")).not.toContain("underline");
165
+ });
166
+
167
+ it("gives the accent hover an added cue beyond the resting tint", () => {
168
+ // The accent face already rests in the primary colour, so a hover that
169
+ // only re-set the same colour would be invisible — the deepened tint
170
+ // plus the underline is the contract.
171
+ const hover = ruleBody(css, `${ACCENT}:hover`);
172
+ expect(declaration(hover, "background")).toBe("none");
173
+ expect(declaration(hover, "text-decoration")).toContain("underline");
174
+ expect(declaration(ruleBody(css, `${ACCENT}:active`), "background")).toBe("none");
175
+ });
176
+
139
177
  it("keeps an icon link one aligned unit with a visible mark", () => {
140
178
  const iconLink = ruleBody(css, ".s-about-modal-link.s-about-modal-link-has-icon");
141
179
  expect(declaration(iconLink, "display")).toBe("inline-flex");
@@ -16,7 +16,8 @@ import { setLocale } from "../i18n/context";
16
16
  * - the backdrop factory renders inside the clipped backdrop layer
17
17
  * - licenses and links render as chips under one centered block
18
18
  * - footer (filing) links render as external links
19
- * - `face: "plain"` turns any of them into a bare text link (still a link,
19
+ * - `face: "plain"` / `face: "accent"` turn any of them into a bare text
20
+ * link (still a link,
20
21
  * still a new tab), and an entry carrying an `icon` renders the mark, with
21
22
  * an accessible name when it has no label to be named by
22
23
  *
@@ -156,6 +157,31 @@ describe("HkAboutModal branding", () => {
156
157
  }
157
158
  });
158
159
 
160
+ it("renders a credit name as a tinted accent link when the entry asks for it", async () => {
161
+ mountAbout({
162
+ credits: [
163
+ { text: "来自 " },
164
+ { name: "Celestia Island", href: "https://github.com/celestia-island", face: "accent" },
165
+ { text: ",由 " },
166
+ { name: "伊欧", href: "https://github.com/langyo", face: "accent" },
167
+ { text: " 主创" },
168
+ ],
169
+ });
170
+ await flushModal();
171
+ const links = [
172
+ ...document.body.querySelectorAll<HTMLAnchorElement>(".s-about-modal-credit-link"),
173
+ ];
174
+ // Same bare-text shape as `plain` (no chip geometry), but the entry's
175
+ // face reaches the stylesheet as `accent`.
176
+ expect(links.map((link) => link.textContent)).toEqual(["Celestia Island", "伊欧"]);
177
+ for (const link of links) {
178
+ expect(link.getAttribute("data-face")).toBe("accent");
179
+ expect(link.classList.contains("s-about-modal-link")).toBe(true);
180
+ expect(link.getAttribute("target")).toBe("_blank");
181
+ expect(link.getAttribute("aria-label")).toBeNull();
182
+ }
183
+ });
184
+
159
185
  it("renders plain faces across the link rows and the legal filings", async () => {
160
186
  mountAbout({
161
187
  links: [
@@ -11,11 +11,13 @@ import "./HkAboutModal.scss";
11
11
  *
12
12
  * `chip` is the dialog's original face — a ghost tag. `plain` is bare text:
13
13
  * no frame, no underline, so the hover colour shift is the whole
14
- * affordance. Chosen per link, because one dialog can want both (the
15
- * credits names / URLs / filings read as sentence text, the licenses stay
16
- * tags).
14
+ * affordance. `accent` is also bare text, but tinted in the primary colour
15
+ * so the link visibly stands out of the sentence it sits in — the "this is
16
+ * clickable" cue (user direction 2026-09-15). Chosen per link, because one
17
+ * dialog can want both (the credits names / URLs / filings read as sentence
18
+ * text, the licenses stay tags).
17
19
  */
18
- export type HAboutLinkFace = "chip" | "plain";
20
+ export type HAboutLinkFace = "chip" | "plain" | "accent";
19
21
 
20
22
  /** Leading icon a link can carry; an icon-only link shows it alone. */
21
23
  export type HAboutLinkIcon = "github";
@@ -90,8 +92,8 @@ export interface HAboutComponentVersion {
90
92
  * bordered spec card holding the software-component versions, and the
91
93
  * link rows — licenses, external links and legal filings. Every link opens
92
94
  * in a new tab and renders in the face its entry asks for: the ghost chip,
93
- * or bare text (`plain`) for names / URLs / filings that should read as
94
- * ordinary sentence text. Every branding prop is optional — the modal
95
+ * or bare text (`plain` / tinted `accent`) for names / URLs / filings that
96
+ * should read as ordinary sentence text. Every branding prop is optional — the modal
95
97
  * degrades to the plain identity card when none are given.
96
98
  */
97
99
  export const HkAboutModal = defineComponent({
@@ -111,7 +113,8 @@ export const HkAboutModal = defineComponent({
111
113
  /**
112
114
  * Credits sentence, assembled from text runs and linked names
113
115
  * (e.g. 来自 <Celestia Island>,由 <伊欧> 主创). Each name renders in the
114
- * face its entry asks for — `chip`, or bare text with `plain`.
116
+ * face its entry asks for — `chip`, bare text with `plain`, or bare
117
+ * text tinted in the primary colour with `accent`.
115
118
  */
116
119
  credits: { type: Array as PropType<HAboutCredit[]>, default: () => [] },
117
120
  /** License links (e.g. SySL-1.0 / BUSL-1.1), rendered centered. */