@celestia-island/hikari 0.55.183 → 0.55.185

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.183",
3
+ "version": "0.55.185",
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",
@@ -108,6 +108,23 @@
108
108
  }
109
109
  }
110
110
 
111
+ /* The per-row actions lane: renders only when the host supplies the
112
+ * `actions` slot (render-side guard), as a SIBLING of the content lane
113
+ * inside the row's flex layout — never a child of it, so the content
114
+ * lane's `> :only-child` fill above keeps stretching single-root rows
115
+ * even when actions ride along. Pinning is structural: the content lane
116
+ * keeps flex: 1 and this lane only refuses to shrink, so the actions
117
+ * land on the row's far edge — no margin-left:auto patch (it would
118
+ * paper over a content-lane regression instead of letting the fill rule
119
+ * fail loudly) and no directional property, so the lane follows the
120
+ * row's inline direction under RTL too. */
121
+ .hk-draggable-list-actions {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: var(--space-8, 0.5rem);
125
+ flex-shrink: 0;
126
+ }
127
+
111
128
  .hk-draggable-list-ghost {
112
129
  position: fixed;
113
130
  top: 0;
@@ -81,3 +81,30 @@ describe("HkDraggableList content-lane fill sheet", () => {
81
81
  expect(fillRules[0]).toContain(":only-child");
82
82
  });
83
83
  });
84
+
85
+ describe("HkDraggableList actions-lane sheet", () => {
86
+ const css = listCss();
87
+
88
+ it("keeps the actions lane fixed at its natural width", () => {
89
+ const body = ruleBody(css, ".hk-draggable-list-actions");
90
+ expect(body, "the actions-lane rule must exist").not.toBeNull();
91
+ expect(body).toContain("display: flex");
92
+ expect(body).toContain("align-items: center");
93
+ expect(body).toContain("flex-shrink: 0");
94
+ // Multiple host buttons in one lane need the lane's own gap so they
95
+ // never fuse into one cluster against the row edge.
96
+ expect(body).toContain("gap:");
97
+ });
98
+
99
+ // The right-edge pinning is STRUCTURAL, not a margin patch: the item
100
+ // row is a flex row and the content lane keeps flex: 1, so the lane
101
+ // needs nothing beyond shrink-resistance. A margin-left: auto here
102
+ // would paper over a future content-lane regression instead of
103
+ // letting the fill sheet fail loudly — pin by ownership, not by patch.
104
+ it("pins by the content lane's flex: 1, not a margin patch", () => {
105
+ const body = ruleBody(css, ".hk-draggable-list-actions");
106
+ expect(body).not.toContain("margin-left: auto");
107
+ const content = ruleBody(css, ".hk-draggable-list-content");
108
+ expect(content).toContain("flex: 1");
109
+ });
110
+ });
@@ -390,3 +390,138 @@ describe("HkDraggableList press ergonomics (clickable rows)", () => {
390
390
  }
391
391
  });
392
392
  });
393
+
394
+ describe("HkDraggableList actions lane", () => {
395
+ /** The standard row-actions contract: hosts inject trailing buttons
396
+ * through the scoped `actions` slot instead of hand-rolling
397
+ * ml-auto/flex-1 spacers inside the row content (chest's pricing
398
+ * ladder shipped exactly such a hand-rolled group and its buttons
399
+ * hugged the row's text mid-card). The lane renders as a SIBLING of
400
+ * the content lane — never inside it — so the content sheet's
401
+ * `> :only-child` fill keeps matching single-root rows even when
402
+ * actions ride along (sheet twin: HkDraggableList.styles.test.ts). */
403
+
404
+ function mountWithActions(items: { key: string }[], opts?: { lockedKeys?: string[] }): HTMLElement {
405
+ const container = document.createElement("div");
406
+ document.body.appendChild(container);
407
+ const app = createApp({
408
+ render: () =>
409
+ h(
410
+ HDraggableList,
411
+ { items, lockedKeys: opts?.lockedKeys ?? [] },
412
+ {
413
+ item: ({ item }: { item: { key: string } }) =>
414
+ h("span", { class: "row-body" }, item.key),
415
+ actions: ({
416
+ item,
417
+ index,
418
+ locked,
419
+ }: {
420
+ item: { key: string };
421
+ index: number;
422
+ locked: boolean;
423
+ }) =>
424
+ h("span", {
425
+ class: "row-actions",
426
+ "data-key": item.key,
427
+ "data-index": index,
428
+ "data-locked": locked,
429
+ }),
430
+ },
431
+ ),
432
+ });
433
+ app.mount(container);
434
+ mounts.push({ app, container });
435
+ return container.querySelector(".hk-draggable-list") as HTMLElement;
436
+ }
437
+
438
+ it("renders a right-lane sibling per row only when the slot is provided", () => {
439
+ const root = mountWithActions([{ key: "a" }, { key: "b" }]);
440
+
441
+ const lanes = Array.from(root.querySelectorAll(".hk-draggable-list-actions"));
442
+ expect(lanes).toHaveLength(2);
443
+ for (const lane of lanes) {
444
+ // Sibling of the content lane, not a child of it — the :only-child
445
+ // fill contract above must keep matching the row body.
446
+ expect(lane.closest(".hk-draggable-list-content")).toBeNull();
447
+ expect(lane.previousElementSibling?.classList.contains("hk-draggable-list-content")).toBe(
448
+ true,
449
+ );
450
+ }
451
+ });
452
+
453
+ it("keeps the content lane's :only-child fill intact alongside the lane", () => {
454
+ const root = mountWithActions([{ key: "a" }]);
455
+
456
+ const content = root.querySelector(".hk-draggable-list-content") as HTMLElement;
457
+ const elementChildren = Array.from(content.children);
458
+ expect(elementChildren).toHaveLength(1);
459
+ expect(elementChildren[0]!.classList.contains("row-body")).toBe(true);
460
+ });
461
+
462
+ it("scopes the slot with item, index, and locked-ness", () => {
463
+ const root = mountWithActions([{ key: "a" }, { key: "b" }], { lockedKeys: ["b"] });
464
+
465
+ const marks = Array.from(root.querySelectorAll(".row-actions")) as HTMLElement[];
466
+ expect(marks).toHaveLength(2);
467
+ expect(marks.map((m) => `${m.dataset.key}:${m.dataset.index}:${m.dataset.locked}`).join(",")).toBe(
468
+ "a:0:false,b:1:true",
469
+ );
470
+ });
471
+
472
+ it("renders no lane for hosts that do not adopt the slot", () => {
473
+ const root = mountList(["a"], []);
474
+
475
+ expect(root.querySelectorAll(".hk-draggable-list-actions")).toHaveLength(0);
476
+ });
477
+
478
+ it("keeps a press on a lane button from arming a drag — the handle still drags", async () => {
479
+ /** The lane exists to host buttons, so the row's interactive-content
480
+ * exclusion (button, a, input, textarea, select, [contenteditable])
481
+ * must own every press that starts on one — and a lane FULL of
482
+ * buttons must leave the handle's drag path untouched. */
483
+ const PointerEventCtor: typeof MouseEvent =
484
+ (globalThis as { PointerEvent?: typeof MouseEvent }).PointerEvent ?? MouseEvent;
485
+ const fire = (el: Element, type: string, x = 0, y = 0) =>
486
+ el.dispatchEvent(
487
+ new PointerEventCtor(type, { bubbles: true, cancelable: true, button: 0, clientX: x, clientY: y }),
488
+ );
489
+ const fireWin = (type: string, x = 0, y = 0) =>
490
+ window.dispatchEvent(new PointerEventCtor(type, { bubbles: true, cancelable: true, clientX: x, clientY: y }));
491
+
492
+ const container = document.createElement("div");
493
+ document.body.appendChild(container);
494
+ const app = createApp({
495
+ render: () =>
496
+ h(HDraggableList, { items: [{ key: "a" }] }, {
497
+ item: ({ item }: { item: { key: string } }) => h("span", { class: "row-body" }, item.key),
498
+ actions: () => [
499
+ h("button", { class: "lane-button", type: "button" }, "✏"),
500
+ h("button", { class: "lane-button", type: "button" }, "⋯"),
501
+ ],
502
+ }),
503
+ });
504
+ app.mount(container);
505
+ mounts.push({ app, container });
506
+ const root = container.querySelector(".hk-draggable-list") as HTMLElement;
507
+ const row = root.querySelector(".hk-draggable-list-item") as HTMLElement;
508
+ const button = row.querySelector(".lane-button") as HTMLElement;
509
+ const handle = row.querySelector(".hk-draggable-list-handle") as HTMLElement;
510
+
511
+ // Press ON the button (target = button, bubbling to the row) and
512
+ // travel far past the arm threshold: no drag may publish.
513
+ fire(button, "pointerdown", 10, 10);
514
+ fireWin("pointermove", 30, 40);
515
+ await nextTick();
516
+ expect(row.hasAttribute("data-dragging")).toBe(false);
517
+
518
+ // The same gesture on the handle still arms — a button-filled lane
519
+ // never breaks the drag path.
520
+ fire(handle, "pointerdown", 10, 10);
521
+ fireWin("pointermove", 30, 40);
522
+ await nextTick();
523
+ expect(row.getAttribute("data-dragging")).not.toBeNull();
524
+
525
+ fireWin("pointerup");
526
+ });
527
+ });
@@ -334,6 +334,19 @@ export default defineComponent({
334
334
  <div class="hk-draggable-list-content">
335
335
  {slots.item?.({ item, index, locked })}
336
336
  </div>
337
+ {/* The standard row-actions lane: when the host provides
338
+ * an `actions` slot, every row renders this right-pinned
339
+ * lane (the content lane keeps flex: 1, so the buttons
340
+ * land on the row's far edge structurally — hosts stop
341
+ * hand-rolling ml-auto/flex-1 spacers). The lane is
342
+ * host-decided content: locked rows may still carry
343
+ * actions (chest's pricing ladder), so locked-ness is
344
+ * only reported through the slot scope. */}
345
+ {slots.actions ? (
346
+ <div class="hk-draggable-list-actions">
347
+ {slots.actions({ item, index, locked })}
348
+ </div>
349
+ ) : null}
337
350
  </div>
338
351
  );
339
352
  })}
@@ -0,0 +1,98 @@
1
+ /* HkQuote — the Markdown-blockquote-style info block.
2
+ *
3
+ * Two layers: the OUTER box (`.hk-quote`) only positions the block
4
+ * (centered in its host for fit="fill", a capped centered column for
5
+ * fit="page"); the INNER mark (`.hk-quote-mark`) is the blockquote
6
+ * itself — the 3px accent bar on the inline-start edge plus quiet
7
+ * prose, sized to its content like a real Markdown `>` quote. No
8
+ * surface of its own (no background, no radius, no shadow): the bar IS
9
+ * the visual. The tone class only selects the bar color; text colors
10
+ * stay in the standard primary/secondary text slots for every tone. */
11
+
12
+ .hk-quote {
13
+ --hk-quote-bar: var(--hi-color-primary, #7aa2f7);
14
+
15
+ display: flex;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ .hk-quote--neutral {
20
+ --hk-quote-bar: var(--hi-color-text-tertiary, #8b949e);
21
+ }
22
+
23
+ .hk-quote--danger {
24
+ --hk-quote-bar: var(--hi-color-danger, #f7768e);
25
+ }
26
+
27
+ .hk-quote--warning {
28
+ --hk-quote-bar: var(--hi-color-warning, #eed677);
29
+ }
30
+
31
+ .hk-quote--success {
32
+ --hk-quote-bar: var(--hi-color-success, #0eb840);
33
+ }
34
+
35
+ /* fit="fill": the quote block centers itself inside its host (an empty
36
+ * panel body, a split view) — the host keeps its own size, the quote
37
+ * floats mid-host as an info block. */
38
+ .hk-quote--fill {
39
+ width: 100%;
40
+ height: 100%;
41
+ align-items: center;
42
+ justify-content: center;
43
+ }
44
+
45
+ /* fit="page": a standalone block on a page — capped width, centered
46
+ * horizontally, never spanning the whole column. */
47
+ .hk-quote--page {
48
+ width: 100%;
49
+ margin-inline: auto;
50
+ justify-content: center;
51
+ }
52
+
53
+ @media (min-width: 48rem) {
54
+ .hk-quote--page {
55
+ max-width: clamp(20rem, 30vw, 35rem);
56
+ }
57
+ }
58
+
59
+ .hk-quote-mark {
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: flex-start;
63
+ gap: var(--space-8, 0.5rem);
64
+ padding: 0.5rem 0.875rem;
65
+ border-inline-start: 3px solid var(--hk-quote-bar);
66
+ text-align: start;
67
+ max-width: 100%;
68
+ }
69
+
70
+ .hk-quote-body {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: var(--space-4, 0.25rem);
74
+ max-width: 100%;
75
+ }
76
+
77
+ .hk-quote-title {
78
+ margin: 0;
79
+ font-size: var(--text-base, 0.875rem);
80
+ font-weight: 600;
81
+ line-height: 1.5;
82
+ color: var(--hi-color-text-primary, #1e1e1e);
83
+ }
84
+
85
+ .hk-quote-desc {
86
+ margin: 0;
87
+ font-size: var(--text-sm, 0.8125rem);
88
+ line-height: 1.55;
89
+ color: var(--hi-color-text-secondary, #666666);
90
+ max-width: 36rem;
91
+ }
92
+
93
+ .hk-quote-action {
94
+ display: flex;
95
+ flex-wrap: wrap;
96
+ align-items: center;
97
+ gap: var(--space-8, 0.5rem);
98
+ }
@@ -0,0 +1,131 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { createApp, h, type VNode } from "vue";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import HkQuote from "./HkQuote";
8
+
9
+ const here = dirname(fileURLToPath(import.meta.url));
10
+
11
+ const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
12
+
13
+ function mount(renderNode: () => VNode) {
14
+ const container = document.createElement("div");
15
+ document.body.appendChild(container);
16
+ const app = createApp({ render: renderNode });
17
+ app.mount(container);
18
+ mounts.push({ app, container });
19
+ return container;
20
+ }
21
+
22
+ afterEach(() => {
23
+ for (const { app, container } of mounts.splice(0)) {
24
+ app.unmount();
25
+ container.remove();
26
+ }
27
+ });
28
+
29
+ /**
30
+ * HkQuote is the Markdown-blockquote grammar as a component: an accent
31
+ * bar on the inline-start edge (tone-colored), quiet prose, NO card
32
+ * chrome — no icon, no background, no boxed surface (user direction
33
+ * 2026-10-18: the no-content/error states must read as a quoted info
34
+ * block with a primary-color left bar, not an icon card).
35
+ */
36
+ describe("HkQuote", () => {
37
+ it("renders the quoted title and description inside a note region", () => {
38
+ const c = mount(() =>
39
+ h(HkQuote, {
40
+ title: "This panel has no content yet",
41
+ description: "Configure the data source, or let the agent generate content.",
42
+ }),
43
+ );
44
+ const root = c.querySelector(".hk-quote")!;
45
+ expect(root.getAttribute("role")).toBe("note");
46
+ expect(c.querySelector(".hk-quote-title")!.textContent).toBe("This panel has no content yet");
47
+ expect(c.querySelector(".hk-quote-desc")!.textContent).toContain("Configure the data source");
48
+ });
49
+
50
+ it("defaults to the primary tone and fill fit; every tone has a class", () => {
51
+ const c = mount(() => h(HkQuote, { title: "t" }));
52
+ const root = c.querySelector(".hk-quote")!;
53
+ expect(root.classList.contains("hk-quote--primary")).toBe(true);
54
+ expect(root.classList.contains("hk-quote--fill")).toBe(true);
55
+
56
+ for (const tone of ["neutral", "danger", "warning", "success"] as const) {
57
+ const toneRoot = mount(() => h(HkQuote, { title: "t", tone })).querySelector(".hk-quote")!;
58
+ expect(toneRoot.classList.contains(`hk-quote--${tone}`)).toBe(true);
59
+ }
60
+ });
61
+
62
+ it("centers itself for fill and renders a width-capped page block for page", () => {
63
+ const fill = mount(() => h(HkQuote, { title: "t" })).querySelector(".hk-quote")!;
64
+ expect(fill.classList.contains("hk-quote--fill")).toBe(true);
65
+ const page = mount(() => h(HkQuote, { title: "t", fit: "page" })).querySelector(".hk-quote")!;
66
+ expect(page.classList.contains("hk-quote--page")).toBe(true);
67
+ });
68
+
69
+ it("renders the action row and the default body slot", () => {
70
+ const c = mount(() =>
71
+ h(HkQuote, { title: "t" }, {
72
+ default: () => h("p", { class: "extra" }, "extra line"),
73
+ action: () => h("button", { type: "button" }, "Edit panel"),
74
+ }),
75
+ );
76
+ expect(c.querySelector(".hk-quote-body .extra")!.textContent).toBe("extra line");
77
+ const action = c.querySelector<HTMLButtonElement>(".hk-quote-action button")!;
78
+ expect(action.textContent).toBe("Edit panel");
79
+ // The action row is a SIBLING of the body inside the mark (pinned:
80
+ // nesting it would halve the desc→action gap to the body's --space-4).
81
+ expect(action.closest(".hk-quote-body")).toBeNull();
82
+ expect(action.closest(".hk-quote-mark")).not.toBeNull();
83
+ });
84
+
85
+ it("passes call-site classes onto the root (no card chrome of its own)", () => {
86
+ const c = mount(() => h(HkQuote, { title: "t", class: "host-skin" }));
87
+ const root = c.querySelector(".hk-quote")!;
88
+ expect(root.classList.contains("host-skin")).toBe(true);
89
+ // The mark is the blockquote itself — the component adds no icon,
90
+ // no boxed surface of its own (see the styles contract below).
91
+ expect(c.querySelector(".hk-empty-icon")).toBeNull();
92
+ expect(c.querySelector("svg")).toBeNull();
93
+ });
94
+
95
+ it("lets the call site override the role (error tones announce as alert)", () => {
96
+ // Consumers rendering an error quote pass role="alert" — Vue's attr
97
+ // fallthrough must win over the rendered default (pinned because the
98
+ // chest error-state story depends on it).
99
+ const c = mount(() => h(HkQuote, { title: "Couldn't load", role: "alert" }));
100
+ expect(c.querySelector(".hk-quote")!.getAttribute("role")).toBe("alert");
101
+ });
102
+ });
103
+
104
+ /** The visual grammar contract: the bar IS the visual. The mark carries
105
+ * the accent bar (border-inline-start, tone variable); it must NOT
106
+ * paint a background, border-radius, or shadow — anything card-like
107
+ * would turn the quote back into the placeholder card the component
108
+ * exists to replace. */
109
+ describe("HkQuote styles contract", () => {
110
+ const scss = readFileSync(join(here, "HkQuote.scss"), "utf8");
111
+
112
+ it("draws the accent bar with the tone variable on the mark", () => {
113
+ expect(scss).toMatch(/\.hk-quote-mark[\s\S]*?border-inline-start:\s*3px solid var\(--hk-quote-bar\)/);
114
+ expect(scss).toMatch(/--hk-quote-bar:\s*var\(--hi-color-primary/);
115
+ });
116
+
117
+ it("maps every tone class onto a house color token", () => {
118
+ expect(scss).toMatch(/hk-quote--neutral[\s\S]*?--hi-color-text-tertiary/);
119
+ expect(scss).toMatch(/hk-quote--danger[\s\S]*?--hi-color-danger/);
120
+ expect(scss).toMatch(/hk-quote--warning[\s\S]*?--hi-color-warning/);
121
+ expect(scss).toMatch(/hk-quote--success[\s\S]*?--hi-color-success/);
122
+ });
123
+
124
+ it("ships no card chrome on the mark (no background / radius / shadow)", () => {
125
+ const markBlock = scss.match(/\.hk-quote-mark \{[\s\S]*?\n\}/)?.[0] ?? "";
126
+ expect(markBlock, "the .hk-quote-mark rule exists").toMatch(/^\./);
127
+ expect(markBlock).not.toMatch(/background/);
128
+ expect(markBlock).not.toMatch(/border-radius/);
129
+ expect(markBlock).not.toMatch(/box-shadow/);
130
+ });
131
+ });
@@ -0,0 +1,85 @@
1
+ import { defineComponent, type PropType } from "vue";
2
+
3
+ import "./HkQuote.scss";
4
+
5
+ /** The tone grammar of the quote's accent bar. */
6
+ export type HkQuoteTone = "primary" | "neutral" | "danger" | "warning" | "success";
7
+
8
+ /**
9
+ * HkQuote — the Markdown-blockquote-style info block.
10
+ *
11
+ * The house grammar for "this surface has something to SAY, not
12
+ * something to show": a vertical accent bar in the tone color on the
13
+ * inline-start edge, the message set as quoted prose beside it — the
14
+ * same visual language a Markdown `>` blockquote renders with, promoted
15
+ * to a first-class component with tone and centering. The bar hugs the
16
+ * prose (the block sizes to its content, like a real blockquote); the
17
+ * OUTER box centers that block inside the host.
18
+ *
19
+ * Deliberately NOT an empty-state card: there is no icon, no boxed
20
+ * surface, no chrome of its own — hosts that want the message centered
21
+ * in an otherwise empty body pass `fit="fill"` (the block centers
22
+ * itself in the host) or `fit="page"` (a standalone, width-capped,
23
+ * horizontally centered block). The optional `action` slot renders a
24
+ * quiet row of buttons under the prose (e.g. "Edit panel", "Retry") —
25
+ * affordances belong to the host's story, never chrome bolted onto the
26
+ * quote.
27
+ *
28
+ * ```tsx
29
+ * // A panel that was never generated: quote it, don't card it.
30
+ * <HkQuote tone="primary" fit="fill"
31
+ * title={t("viewPanel.empty.panelNoContent")}
32
+ * description={t("viewPanel.empty.panelNoContentDesc")}>
33
+ * {{ action: () => <HkButton size="sm" onClick={edit}>Edit panel</HkButton> }}
34
+ * </HkQuote>
35
+ * ```
36
+ *
37
+ * A11y: the block is a `role="note"` region by default; pass
38
+ * `role="alert"` (or any other attrs) from the call site for error
39
+ * tones — attrs land on the root element.
40
+ */
41
+ export default defineComponent({
42
+ name: "HkQuote",
43
+ props: {
44
+ /** Lead line — the quoted statement itself. Bold, primary text. */
45
+ title: { type: String, default: undefined },
46
+ /** Follow-up prose — context or the way out. Muted, smaller. */
47
+ description: { type: String, default: undefined },
48
+ /** The accent bar's color grammar. Default: the primary tone. */
49
+ tone: {
50
+ type: String as PropType<HkQuoteTone>,
51
+ default: "primary",
52
+ },
53
+ /** page = standalone, centered, width-capped block; fill = the
54
+ * block centers itself inside its host (panels, split views). */
55
+ fit: {
56
+ type: String as PropType<"page" | "fill">,
57
+ default: "fill",
58
+ },
59
+ },
60
+ setup(props, { slots }) {
61
+ return () => (
62
+ <div
63
+ class={[
64
+ "hk-quote",
65
+ `hk-quote--${props.tone}`,
66
+ props.fit === "fill" ? "hk-quote--fill" : "hk-quote--page",
67
+ ]}
68
+ role="note"
69
+ >
70
+ <div class="hk-quote-mark">
71
+ <div class="hk-quote-body">
72
+ {props.title ? <p class="hk-quote-title">{props.title}</p> : null}
73
+ {props.description ? (
74
+ <p class="hk-quote-desc">{props.description}</p>
75
+ ) : null}
76
+ {slots.default?.()}
77
+ </div>
78
+ {slots.action ? (
79
+ <div class="hk-quote-action">{slots.action()}</div>
80
+ ) : null}
81
+ </div>
82
+ </div>
83
+ );
84
+ },
85
+ });
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ export { default as HPhaseTransition } from "./components/HkPhaseTransition";
54
54
  export { default as HGaugeRing } from "./components/HkGaugeRing";
55
55
  export { default as HProgressRing } from "./components/HkProgressRing";
56
56
  export { default as HQrCode } from "./components/HkQrCode";
57
+ export { default as HQuote, type HkQuoteTone as HQuoteTone } from "./components/HkQuote";
57
58
  export { default as HRollingNumber } from "./components/HkRollingNumber";
58
59
  export { default as HLocalePickerPopup } from "./components/HkLocalePickerPopup";
59
60
  export { default as HHoverRevealAction } from "./components/HkHoverRevealAction";
@@ -641,6 +642,8 @@ export { default as HkPhaseTransition } from "./components/HkPhaseTransition";
641
642
  export { default as HkGaugeRing } from "./components/HkGaugeRing";
642
643
  export { default as HkProgressRing } from "./components/HkProgressRing";
643
644
  export { default as HkQrCode } from "./components/HkQrCode";
645
+ export { default as HkQuote } from "./components/HkQuote";
646
+ export type { HkQuoteTone } from "./components/HkQuote";
644
647
  export { default as HkRollingNumber } from "./components/HkRollingNumber";
645
648
  export { default as HkLocalePickerPopup } from "./components/HkLocalePickerPopup";
646
649
  export { default as HkHoverRevealAction } from "./components/HkHoverRevealAction";