@celestia-island/hikari 0.55.107 → 0.55.109
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/HkAdminShell.test.tsx +44 -0
- package/src/components/HkAdminShell.tsx +22 -2
- package/src/components/HkButton.scss +31 -1
- package/src/components/HkButton.test.tsx +17 -0
- package/src/components/HkButton.tsx +1 -1
- package/src/components/HkStatusBar.test.tsx +3 -0
- package/src/components/HkStatusBar.tsx +6 -1
package/package.json
CHANGED
|
@@ -76,6 +76,17 @@ const setWidth = (w: number) => {
|
|
|
76
76
|
window.dispatchEvent(new Event("resize"));
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
function paddingInner(container: HTMLElement): HTMLElement | null | undefined {
|
|
80
|
+
const viewport = container.querySelector(".hk-scroll-container-viewport")
|
|
81
|
+
?? container.querySelector(".hk-scroll-container");
|
|
82
|
+
return viewport?.firstElementChild as HTMLElement | null | undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mobileInnerPadding(container: HTMLElement): string {
|
|
86
|
+
return paddingInner(container)?.style.padding ?? "";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
79
90
|
function shellNode(
|
|
80
91
|
props: Record<string, unknown>,
|
|
81
92
|
slots: Record<string, unknown>,
|
|
@@ -234,4 +245,37 @@ describe("HkAdminShell", () => {
|
|
|
234
245
|
expect(inner).toBeTruthy();
|
|
235
246
|
expect(inner?.style.padding).toBe("2rem");
|
|
236
247
|
});
|
|
248
|
+
|
|
249
|
+
it("drops the horizontal half of the content padding below the mobile breakpoint", async () => {
|
|
250
|
+
// Phone width: side gutters must vanish; vertical clearance survives.
|
|
251
|
+
// Asserted via longhands: happy-dom's shorthand parser collapses
|
|
252
|
+
// 3-value paddings, so style.padding cannot distinguish them here.
|
|
253
|
+
setWidth(390);
|
|
254
|
+
const mobileShell = mount(shellNode(
|
|
255
|
+
{ navTitle: "Navigation", contentPadding: "2rem" },
|
|
256
|
+
{ header: () => null, sidebar: NAV, content: CONTENT },
|
|
257
|
+
));
|
|
258
|
+
const inner = paddingInner(mobileShell);
|
|
259
|
+
expect(inner?.style.paddingTop).toBe("2rem");
|
|
260
|
+
expect(inner?.style.paddingBottom).toBe("2rem");
|
|
261
|
+
expect(inner?.style.paddingLeft).toBe("0px");
|
|
262
|
+
expect(inner?.style.paddingRight).toBe("0px");
|
|
263
|
+
|
|
264
|
+
// Multi-value shorthand keeps per-side meaning: vertical survives,
|
|
265
|
+
// horizontal is zeroed (never the inverted "1rem 2rem 0").
|
|
266
|
+
const multi = mount(shellNode(
|
|
267
|
+
{ navTitle: "Navigation", contentPadding: "1rem 2rem" },
|
|
268
|
+
{ header: () => null, sidebar: NAV, content: CONTENT },
|
|
269
|
+
));
|
|
270
|
+
const multiInner = paddingInner(multi);
|
|
271
|
+
expect(multiInner?.style.paddingTop).toBe("1rem");
|
|
272
|
+
expect(multiInner?.style.paddingBottom).toBe("1rem");
|
|
273
|
+
expect(multiInner?.style.paddingLeft).toBe("0px");
|
|
274
|
+
expect(multiInner?.style.paddingRight).toBe("0px");
|
|
275
|
+
|
|
276
|
+
// Crossing back to desktop restores the verbatim padding.
|
|
277
|
+
setWidth(1280);
|
|
278
|
+
await nextTick();
|
|
279
|
+
expect(mobileInnerPadding(mobileShell)).toBe("2rem");
|
|
280
|
+
});
|
|
237
281
|
});
|
|
@@ -21,7 +21,11 @@ export const HkAdminShell = defineComponent({
|
|
|
21
21
|
drawerPanelClass: { type: String, default: undefined },
|
|
22
22
|
/** Content-area padding (inside the scroll viewport). Padding is
|
|
23
23
|
* applied to an inner wrapper rather than the scroll container so
|
|
24
|
-
* card box-shadows are never clipped at the viewport edges.
|
|
24
|
+
* card box-shadows are never clipped at the viewport edges. Below
|
|
25
|
+
* the mobile breakpoint the horizontal half is dropped
|
|
26
|
+
* automatically (2026-10-03 user direction: on phones the content
|
|
27
|
+
* area must span the full width for 2D panels and the 3D scene —
|
|
28
|
+
* only the vertical clearance survives). */
|
|
25
29
|
contentPadding: { type: String, default: "1.5rem" },
|
|
26
30
|
},
|
|
27
31
|
setup(props, { slots }) {
|
|
@@ -30,6 +34,22 @@ export const HkAdminShell = defineComponent({
|
|
|
30
34
|
const isDesktop = computed(() => viewportWidth.value >= props.mobileBreakpoint);
|
|
31
35
|
const sidebarOpen = ref(false);
|
|
32
36
|
|
|
37
|
+
// Content padding with the mobile side-gutter rule applied: desktop
|
|
38
|
+
// gets the value verbatim; below the breakpoint the horizontal
|
|
39
|
+
// tracks become zero while the vertical tracks survive. The CSS
|
|
40
|
+
// padding shorthand is parsed per side so multi-value values keep
|
|
41
|
+
// their meaning ("1rem 2rem" -> "1rem 0", not the invalid or
|
|
42
|
+
// inverted "1rem 2rem 0"). An empty value passes through untouched.
|
|
43
|
+
const mobileSideGutterFree = (value: string): string => {
|
|
44
|
+
const sides = value.trim().split(/\s+/).filter(Boolean);
|
|
45
|
+
if (sides.length === 0) return value;
|
|
46
|
+
const [top, , bottom] = sides;
|
|
47
|
+
return `${top} 0 ${bottom ?? top}`;
|
|
48
|
+
};
|
|
49
|
+
const contentStyle = computed(() => ({
|
|
50
|
+
padding: isDesktop.value ? props.contentPadding : mobileSideGutterFree(props.contentPadding),
|
|
51
|
+
}));
|
|
52
|
+
|
|
33
53
|
const actionBar = provideActionBar();
|
|
34
54
|
|
|
35
55
|
const toggleHamburger = () => {
|
|
@@ -80,7 +100,7 @@ export const HkAdminShell = defineComponent({
|
|
|
80
100
|
{/* Padding lives INSIDE the scroll viewport (an inner
|
|
81
101
|
wrapper) so card box-shadows are not clipped at the
|
|
82
102
|
viewport edges. */}
|
|
83
|
-
<div style={
|
|
103
|
+
<div style={contentStyle.value}>{slots.content?.()}</div>
|
|
84
104
|
</HScrollContainer>
|
|
85
105
|
</main>
|
|
86
106
|
|
|
@@ -44,7 +44,20 @@
|
|
|
44
44
|
text and toolbar rows. Font size does NOT scale with the size
|
|
45
45
|
(2026-08-30 user report, Material-Design convention): md and lg share
|
|
46
46
|
--text-base and lg only grows padding/min-height for a larger touch
|
|
47
|
-
target; sm keeps its compact --text-xs.
|
|
47
|
+
target; sm keeps its compact --text-xs. xs (2026-10-02, connection
|
|
48
|
+
popover action row) shrinks the footprint further for inline popover
|
|
49
|
+
footers: same --text-xs, tighter padding, 1.375rem min-height, and a
|
|
50
|
+
13px glyph produced entirely by the CSS chain below (the glyph box
|
|
51
|
+
drops to 0.8125rem and the inner .hk-icon span scales to 100% of it,
|
|
52
|
+
so the svg that always renders at HIcon's size={16} lands at 13px)
|
|
53
|
+
while the square-glyph contract keeps its rhythm at the smaller
|
|
54
|
+
scale. HkButton.tsx itself renders every size's glyph identically. */
|
|
55
|
+
|
|
56
|
+
.hk-btn-xs {
|
|
57
|
+
padding: var(--space-4) var(--space-8);
|
|
58
|
+
font-size: var(--text-xs);
|
|
59
|
+
min-height: 1.375rem;
|
|
60
|
+
}
|
|
48
61
|
|
|
49
62
|
.hk-btn-sm {
|
|
50
63
|
padding: var(--space-6) var(--space-12);
|
|
@@ -73,6 +86,19 @@
|
|
|
73
86
|
consistent margin from the adjacent label via the button's own gap.
|
|
74
87
|
The 1rem box matches the 16px glyph HkButton renders for the
|
|
75
88
|
icon/suffix props. */
|
|
89
|
+
.hk-btn-xs .hk-btn-icon,
|
|
90
|
+
.hk-btn-xs .hk-btn-suffix {
|
|
91
|
+
inline-size: 0.8125rem;
|
|
92
|
+
block-size: 0.8125rem;
|
|
93
|
+
|
|
94
|
+
/* HIcon's smallest span is 1rem; the box scales the span (and the svg
|
|
95
|
+
fills the span), so the glyph lands at 13px inside the 22px button. */
|
|
96
|
+
.hk-icon {
|
|
97
|
+
inline-size: 100%;
|
|
98
|
+
block-size: 100%;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
.hk-btn-icon,
|
|
77
103
|
.hk-btn-suffix {
|
|
78
104
|
display: inline-flex;
|
|
@@ -121,6 +147,10 @@
|
|
|
121
147
|
box-sizing: border-box;
|
|
122
148
|
}
|
|
123
149
|
|
|
150
|
+
.hk-btn-xs.hk-btn-icon-only {
|
|
151
|
+
width: 1.375rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
.hk-btn-sm.hk-btn-icon-only {
|
|
125
155
|
width: 1.75rem;
|
|
126
156
|
}
|
|
@@ -62,6 +62,15 @@ describe("HkButton class mapping", () => {
|
|
|
62
62
|
expect(cls).not.toContain("hk-btn-md");
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
it("maps the xs size with its leading icon box", () => {
|
|
66
|
+
const xs = mount(h(HkButton, { size: "xs", icon: "RotateCw" }, () => "Reconnect"));
|
|
67
|
+
const xsBtn = button(xs);
|
|
68
|
+
expect(xsBtn.className).toContain("hk-btn-xs");
|
|
69
|
+
const box = xsBtn.querySelector(".hk-btn-icon");
|
|
70
|
+
expect(box, "xs renders the leading icon box").toBeTruthy();
|
|
71
|
+
expect(box!.querySelector(".hk-icon"), "HIcon span renders").toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
|
|
65
74
|
it("adds the block modifier only when block is set", () => {
|
|
66
75
|
const blocked = mount(h(HkButton, { block: true }, () => "wide"));
|
|
67
76
|
expect(button(blocked).className).toContain("hk-btn-block");
|
|
@@ -207,8 +216,16 @@ describe("HkButton icon-only SCSS contract", () => {
|
|
|
207
216
|
expect(rule).toContain("width: 100%");
|
|
208
217
|
});
|
|
209
218
|
|
|
219
|
+
it("shrinks the xs glyph box and scales the HIcon span with it", () => {
|
|
220
|
+
const rule = scss.match(/\.hk-btn-xs \.hk-btn-icon,\n\.hk-btn-xs \.hk-btn-suffix\s*{[^}]*}/)?.[0] ?? "";
|
|
221
|
+
expect(rule, "xs glyph-box rule must exist").toBeTruthy();
|
|
222
|
+
expect(rule).toContain("inline-size: 0.8125rem");
|
|
223
|
+
expect(rule).toContain("inline-size: 100%");
|
|
224
|
+
});
|
|
225
|
+
|
|
210
226
|
it("locks each size variant width to its min-height", () => {
|
|
211
227
|
const cases: Array<[string, string]> = [
|
|
228
|
+
["xs", "1.375rem"],
|
|
212
229
|
["sm", "1.75rem"],
|
|
213
230
|
["md", "2.5rem"],
|
|
214
231
|
["lg", "2.75rem"],
|
|
@@ -345,6 +345,9 @@ describe("HkStatusBar", () => {
|
|
|
345
345
|
expect(row, "actions row renders").toBeTruthy();
|
|
346
346
|
const button = row.querySelector<HTMLButtonElement>("button")!;
|
|
347
347
|
expect(button.textContent).toContain("Reconnect now");
|
|
348
|
+
// 2026-10-02 user direction: small buttons with a leading glyph.
|
|
349
|
+
expect(button.className).toContain("hk-btn-xs");
|
|
350
|
+
expect(button.querySelector(".hk-btn-icon svg"), "leading glyph renders").toBeTruthy();
|
|
348
351
|
expect(panel.textContent).not.toContain("Click to retry");
|
|
349
352
|
button.click();
|
|
350
353
|
expect(onRetry, "popover button click retries").toHaveBeenCalledTimes(1);
|
|
@@ -458,7 +458,12 @@ export const HkStatusBar = defineComponent({
|
|
|
458
458
|
}}
|
|
459
459
|
>
|
|
460
460
|
{showRetryButton && (
|
|
461
|
-
<HkButton
|
|
461
|
+
<HkButton
|
|
462
|
+
size="xs"
|
|
463
|
+
variant="secondary"
|
|
464
|
+
icon="RotateCw"
|
|
465
|
+
onClick={onRetryButtonClick}
|
|
466
|
+
>
|
|
462
467
|
{t("hikari::statusBar.retryNow", "Reconnect now")}
|
|
463
468
|
</HkButton>
|
|
464
469
|
)}
|