@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.107",
3
+ "version": "0.55.109",
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",
@@ -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={{ padding: props.contentPadding }}>{slots.content?.()}</div>
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"],
@@ -11,7 +11,7 @@ export type ButtonVariant =
11
11
  | "ghost"
12
12
  | "danger"
13
13
  | "outline";
14
- export type ButtonSize = "sm" | "md" | "lg";
14
+ export type ButtonSize = "xs" | "sm" | "md" | "lg";
15
15
 
16
16
  export default defineComponent({
17
17
  name: "HkButton",
@@ -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 size="sm" variant="secondary" onClick={onRetryButtonClick}>
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
  )}