@krizaka/ui 3.18.2 → 3.20.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.
Files changed (186) hide show
  1. package/dist/avatar/defaults.js +6 -1
  2. package/dist/avatar/defaults.js.map +1 -1
  3. package/dist/avatar.js +6 -1
  4. package/dist/avatar.js.map +1 -1
  5. package/dist/blocks.css +11 -0
  6. package/dist/card-grid.d.ts +14 -1
  7. package/dist/card-grid.js +29 -6
  8. package/dist/card-grid.js.map +1 -1
  9. package/dist/chat.d.ts +51 -3
  10. package/dist/chat.js +64 -32
  11. package/dist/chat.js.map +1 -1
  12. package/dist/{chunk-276PTQMQ.js → chunk-3BC4XQ6C.js} +2 -1
  13. package/dist/{chunk-YIBKJ36L.js → chunk-KB7OLYIJ.js} +7 -2
  14. package/dist/{chunk-YIBKJ36L.js.map → chunk-KB7OLYIJ.js.map} +1 -1
  15. package/dist/data-table.js +1 -1
  16. package/dist/file-upload.js +2 -2
  17. package/dist/file-upload.js.map +1 -1
  18. package/dist/match-dialog.js +2 -2
  19. package/dist/media-grid.d.ts +66 -3
  20. package/dist/media-grid.js +312 -65
  21. package/dist/media-grid.js.map +1 -1
  22. package/dist/native.js +6 -1
  23. package/dist/native.js.map +1 -1
  24. package/dist/progress.js +2 -2
  25. package/dist/progress.js.map +1 -1
  26. package/dist/vertical-video.d.ts +7 -1
  27. package/dist/vertical-video.js +7 -5
  28. package/dist/vertical-video.js.map +1 -1
  29. package/package.json +8 -8
  30. package/registry/achievement.json +16 -2
  31. package/registry/action-bar.json +16 -2
  32. package/registry/action-sheet.json +15 -0
  33. package/registry/activity-feed.json +13 -2
  34. package/registry/alert.json +15 -2
  35. package/registry/app-shell.json +15 -2
  36. package/registry/argument-card.json +16 -2
  37. package/registry/audio-player.json +15 -2
  38. package/registry/auth-layout.json +15 -2
  39. package/registry/avatar.json +14 -3
  40. package/registry/badge.json +13 -2
  41. package/registry/button.json +13 -2
  42. package/registry/card-grid.json +69 -4
  43. package/registry/card.json +13 -2
  44. package/registry/catalogue-page.json +15 -2
  45. package/registry/changelog-page.json +15 -2
  46. package/registry/chat.json +115 -10
  47. package/registry/checkbox.json +13 -2
  48. package/registry/chip-link.json +15 -2
  49. package/registry/chip.json +13 -2
  50. package/registry/choice-grid.json +16 -2
  51. package/registry/cn.json +12 -0
  52. package/registry/code-block.json +15 -2
  53. package/registry/combobox.json +13 -2
  54. package/registry/command.json +15 -2
  55. package/registry/component-page.json +15 -2
  56. package/registry/confirm-button.json +15 -2
  57. package/registry/contact-page.json +15 -2
  58. package/registry/continue-prompt.json +16 -2
  59. package/registry/countdown.json +13 -2
  60. package/registry/cta-panel.json +16 -2
  61. package/registry/dashboard-layout.json +15 -2
  62. package/registry/data-table.json +15 -2
  63. package/registry/date-picker.json +13 -2
  64. package/registry/decision-list.json +16 -2
  65. package/registry/description-list.json +13 -2
  66. package/registry/detail-layout.json +15 -2
  67. package/registry/dialog.json +13 -2
  68. package/registry/docs-article-page.json +15 -2
  69. package/registry/docs-hub-page.json +15 -2
  70. package/registry/docs-layout.json +15 -2
  71. package/registry/document-viewer.json +15 -2
  72. package/registry/dropdown-menu.json +15 -2
  73. package/registry/empty-state.json +13 -2
  74. package/registry/error-page.json +14 -2
  75. package/registry/examples/card-grid/foundation.d.ts +4 -0
  76. package/registry/examples/card-grid/foundation.js +38 -0
  77. package/registry/examples/card-grid/foundation.tsx +35 -0
  78. package/registry/examples/chat/conversation.js +2 -2
  79. package/registry/examples/chat/conversation.tsx +4 -3
  80. package/registry/examples/chat/pill-composer.d.ts +4 -0
  81. package/registry/examples/chat/pill-composer.js +34 -0
  82. package/registry/examples/chat/pill-composer.tsx +39 -0
  83. package/registry/examples/glow-card/plans.js +9 -12
  84. package/registry/examples/glow-card/plans.tsx +18 -14
  85. package/registry/examples/media-grid/loading.d.ts +4 -0
  86. package/registry/examples/media-grid/loading.js +28 -0
  87. package/registry/examples/media-grid/loading.tsx +25 -0
  88. package/registry/examples/vertical-video/rail.js +3 -2
  89. package/registry/examples/vertical-video/rail.tsx +4 -3
  90. package/registry/fab.json +15 -0
  91. package/registry/field.json +13 -2
  92. package/registry/file-upload.json +15 -2
  93. package/registry/filter-bar.json +15 -2
  94. package/registry/form.json +13 -2
  95. package/registry/gallery-page.json +15 -2
  96. package/registry/glow-card.json +18 -4
  97. package/registry/goal-gauge.json +13 -2
  98. package/registry/header.json +15 -0
  99. package/registry/how-it-works-page.json +15 -2
  100. package/registry/image-cropper.json +15 -2
  101. package/registry/image-viewer.json +15 -0
  102. package/registry/index.json +1773 -10
  103. package/registry/iso.json +15 -2
  104. package/registry/kanban.json +15 -2
  105. package/registry/kbd.json +14 -2
  106. package/registry/landing-page.json +15 -2
  107. package/registry/landscape.json +15 -2
  108. package/registry/legal-page.json +15 -2
  109. package/registry/link-card.json +16 -2
  110. package/registry/list.json +13 -2
  111. package/registry/loader.json +13 -2
  112. package/registry/longform-page.json +15 -2
  113. package/registry/map.json +14 -3
  114. package/registry/markdown.json +13 -2
  115. package/registry/marketing-layout.json +15 -2
  116. package/registry/marquee.json +15 -2
  117. package/registry/mascot-quote.json +16 -2
  118. package/registry/mascot-state.json +13 -2
  119. package/registry/mascots.json +13 -2
  120. package/registry/match-dialog.json +13 -2
  121. package/registry/media-capture.json +13 -2
  122. package/registry/media-grid.json +341 -13
  123. package/registry/media-picker.json +15 -0
  124. package/registry/meter.json +13 -2
  125. package/registry/nav-bar.json +17 -3
  126. package/registry/number-ticker.json +13 -2
  127. package/registry/onboarding.json +15 -0
  128. package/registry/otp-input.json +15 -0
  129. package/registry/page-header.json +15 -2
  130. package/registry/page-hero.json +16 -2
  131. package/registry/page.json +16 -2
  132. package/registry/pagination.json +13 -2
  133. package/registry/permission-prompt.json +15 -0
  134. package/registry/photo-grid.json +13 -2
  135. package/registry/pointer-light.json +15 -2
  136. package/registry/popover.json +15 -2
  137. package/registry/pricing-page.json +15 -2
  138. package/registry/product-page.json +15 -2
  139. package/registry/profile-card.json +13 -2
  140. package/registry/progress.json +14 -3
  141. package/registry/promise-card.json +16 -2
  142. package/registry/pull-to-refresh.json +15 -0
  143. package/registry/radio-group.json +13 -2
  144. package/registry/recipes.json +14 -2
  145. package/registry/screen.json +15 -0
  146. package/registry/scroll-stage.json +15 -2
  147. package/registry/search-field.json +13 -2
  148. package/registry/section-backdrop.json +15 -2
  149. package/registry/segmented.json +15 -0
  150. package/registry/separator.json +15 -2
  151. package/registry/settings-layout.json +15 -2
  152. package/registry/showcase.json +13 -2
  153. package/registry/site-footer.json +16 -3
  154. package/registry/sites.json +15 -2
  155. package/registry/skeleton.json +13 -2
  156. package/registry/skip-link.json +14 -2
  157. package/registry/slider.json +13 -2
  158. package/registry/slot.json +14 -2
  159. package/registry/spinner.json +13 -2
  160. package/registry/splash-handoff.json +15 -0
  161. package/registry/split-view.json +15 -2
  162. package/registry/stage-banner.json +15 -2
  163. package/registry/stat.json +15 -2
  164. package/registry/status-page.json +15 -2
  165. package/registry/step-progress.json +13 -2
  166. package/registry/stepper.json +13 -2
  167. package/registry/swipe-deck.json +13 -2
  168. package/registry/swipeable-row.json +15 -0
  169. package/registry/switch.json +13 -2
  170. package/registry/tab-bar.json +15 -0
  171. package/registry/tabs.json +13 -2
  172. package/registry/tag-input.json +15 -2
  173. package/registry/tag-selector.json +13 -2
  174. package/registry/terminal-replay.json +15 -2
  175. package/registry/theme.json +13 -2
  176. package/registry/toast.json +13 -2
  177. package/registry/toc.json +15 -2
  178. package/registry/tooltip.json +14 -2
  179. package/registry/tree-view.json +15 -2
  180. package/registry/txt.json +13 -0
  181. package/registry/verified-badge.json +13 -2
  182. package/registry/vertical-video.json +32 -6
  183. package/registry/video-player.json +13 -2
  184. package/registry/video-trimmer.json +15 -2
  185. package/registry/virtual.json +15 -2
  186. /package/dist/{chunk-276PTQMQ.js.map → chunk-3BC4XQ6C.js.map} +0 -0
@@ -2,7 +2,12 @@
2
2
  import "./chunk-NPF3OHE2.js";
3
3
  import {
4
4
  Avatar
5
- } from "./chunk-YIBKJ36L.js";
5
+ } from "./chunk-KB7OLYIJ.js";
6
+ import "./chunk-JXLDUDTD.js";
7
+ import {
8
+ Button,
9
+ LoaderGlyph
10
+ } from "./chunk-3BC4XQ6C.js";
6
11
  import {
7
12
  defineRecipe
8
13
  } from "./chunk-D3T54RSL.js";
@@ -10,11 +15,58 @@ import {
10
15
  cn
11
16
  } from "./chunk-2FRWNSRO.js";
12
17
 
13
- // src/media-grid/shorts-shelf.tsx
18
+ // src/media-grid/load-more.tsx
14
19
  import * as React from "react";
15
20
 
16
- // src/block-link.tsx
21
+ // src/spinner/spinner.tsx
22
+ import { tv } from "tailwind-variants";
17
23
  import { jsx } from "react/jsx-runtime";
24
+ var spinnerVariants = tv({
25
+ base: "shrink-0 text-accent",
26
+ variants: { size: { sm: "h-4 w-4", md: "h-6 w-6", lg: "h-10 w-10" } },
27
+ defaultVariants: { size: "md" }
28
+ });
29
+ function Spinner({ label, size, value, className, ...props }) {
30
+ return /* @__PURE__ */ jsx(
31
+ LoaderGlyph,
32
+ {
33
+ role: "status",
34
+ "aria-label": label,
35
+ "aria-hidden": void 0,
36
+ value,
37
+ "data-size": size ?? "md",
38
+ className: spinnerVariants({ size, className }),
39
+ ...props
40
+ }
41
+ );
42
+ }
43
+
44
+ // src/media-grid/load-more.tsx
45
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
46
+ function LoadMore({ onLoadMore, hasMore, loading = false, failed = false, label, loadingLabel, retryLabel, endLabel, rootMargin = "900px 0px", className, ...props }) {
47
+ const sentinel = React.useRef(null);
48
+ const ask = React.useRef(onLoadMore);
49
+ React.useEffect(() => {
50
+ ask.current = onLoadMore;
51
+ });
52
+ React.useEffect(() => {
53
+ const el = sentinel.current;
54
+ if (!el || !hasMore || loading || failed || typeof IntersectionObserver === "undefined") return;
55
+ const io = new IntersectionObserver((entries) => entries.some((e) => e.isIntersecting) && ask.current(), { rootMargin });
56
+ io.observe(el);
57
+ return () => io.disconnect();
58
+ }, [hasMore, loading, failed, rootMargin]);
59
+ return /* @__PURE__ */ jsxs("div", { "data-load-more": "", "data-state": loading ? "loading" : failed ? "failed" : hasMore ? "idle" : "end", className: cn("relative flex min-h-14 items-center justify-center py-6", className), ...props, children: [
60
+ /* @__PURE__ */ jsx2("div", { ref: sentinel, "aria-hidden": true, className: "pointer-events-none absolute inset-x-0 top-0 h-px" }),
61
+ loading ? /* @__PURE__ */ jsx2(Spinner, { label: loadingLabel, size: "md" }) : hasMore ? /* @__PURE__ */ jsx2(Button, { type: "button", variant: "secondary", size: "md", onClick: () => onLoadMore(), children: failed ? retryLabel ?? label : label }) : endLabel ? /* @__PURE__ */ jsx2("p", { className: "m-0 text-sm text-fg-secondary", children: endLabel }) : null
62
+ ] });
63
+ }
64
+
65
+ // src/media-grid/shorts-shelf.tsx
66
+ import * as React2 from "react";
67
+
68
+ // src/block-link.tsx
69
+ import { jsx as jsx3 } from "react/jsx-runtime";
18
70
  var isExternal = (href) => /^[a-z][a-z0-9+.-]*:/i.test(href);
19
71
  function BlockLink({
20
72
  href,
@@ -23,38 +75,38 @@ function BlockLink({
23
75
  rel,
24
76
  ...props
25
77
  }) {
26
- if (external) return /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: rel ?? "noopener noreferrer", ...props });
78
+ if (external) return /* @__PURE__ */ jsx3("a", { href, target: "_blank", rel: rel ?? "noopener noreferrer", ...props });
27
79
  const Comp = Link;
28
- return /* @__PURE__ */ jsx(Comp, { href, ...props });
80
+ return /* @__PURE__ */ jsx3(Comp, { href, ...props });
29
81
  }
30
82
 
31
83
  // src/media-grid/shorts-shelf.tsx
32
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
33
- var Chevron = ({ flip }) => /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, className: flip ? "rotate-180" : void 0, children: /* @__PURE__ */ jsx2("path", { d: "m9 6 6 6-6 6" }) });
84
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
85
+ var Chevron = ({ flip }) => /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, className: flip ? "rotate-180" : void 0, children: /* @__PURE__ */ jsx4("path", { d: "m9 6 6 6-6 6" }) });
34
86
  var ARROW = "hidden h-9 w-9 items-center justify-center rounded-full border border-border-default bg-surface-1 text-fg shadow-sm transition-colors hover:bg-surface-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40 [@media(hover:hover)]:inline-flex";
35
87
  function ShortsShelf({ label, title, items, getId, renderItem, seeAll, previousLabel, nextLabel, className, ...props }) {
36
- const row = React.useRef(null);
37
- const [edge, setEdge] = React.useState({ start: true, end: false });
38
- const measure = React.useCallback(() => {
88
+ const row = React2.useRef(null);
89
+ const [edge, setEdge] = React2.useState({ start: true, end: false });
90
+ const measure = React2.useCallback(() => {
39
91
  const el = row.current;
40
92
  if (!el) return;
41
93
  setEdge({ start: el.scrollLeft <= 2, end: el.scrollLeft + el.clientWidth >= el.scrollWidth - 2 });
42
94
  }, []);
43
- React.useEffect(measure, [measure, items.length]);
95
+ React2.useEffect(measure, [measure, items.length]);
44
96
  const page = (dir) => {
45
97
  const el = row.current;
46
98
  if (el) el.scrollBy({ left: dir * el.clientWidth * 0.85, behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
47
99
  };
48
- const headingId = React.useId();
100
+ const headingId = React2.useId();
49
101
  if (items.length === 0) return null;
50
- return /* @__PURE__ */ jsxs("section", { "aria-labelledby": headingId, "data-shorts-shelf": true, className: cn("min-w-0", className), ...props, children: [
51
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
52
- /* @__PURE__ */ jsx2("h2", { id: headingId, className: "m-0 mr-auto text-base font-bold text-fg", children: title ?? label }),
53
- seeAll && /* @__PURE__ */ jsx2(BlockLink, { href: seeAll.href, linkAs: seeAll.linkAs, className: "text-sm font-semibold text-fg-accent hover:underline", children: seeAll.label }),
54
- /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": previousLabel, disabled: edge.start, onClick: () => page(-1), className: ARROW, children: /* @__PURE__ */ jsx2(Chevron, { flip: true }) }),
55
- /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": nextLabel, disabled: edge.end, onClick: () => page(1), className: ARROW, children: /* @__PURE__ */ jsx2(Chevron, {}) })
102
+ return /* @__PURE__ */ jsxs2("section", { "aria-labelledby": headingId, "data-shorts-shelf": true, className: cn("min-w-0", className), ...props, children: [
103
+ /* @__PURE__ */ jsxs2("div", { className: "mb-3 flex items-center gap-2", children: [
104
+ /* @__PURE__ */ jsx4("h2", { id: headingId, className: "m-0 mr-auto text-base font-bold text-fg", children: title ?? label }),
105
+ seeAll && /* @__PURE__ */ jsx4(BlockLink, { href: seeAll.href, linkAs: seeAll.linkAs, className: "text-sm font-semibold text-fg-accent hover:underline", children: seeAll.label }),
106
+ /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": previousLabel, disabled: edge.start, onClick: () => page(-1), className: ARROW, children: /* @__PURE__ */ jsx4(Chevron, { flip: true }) }),
107
+ /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": nextLabel, disabled: edge.end, onClick: () => page(1), className: ARROW, children: /* @__PURE__ */ jsx4(Chevron, {}) })
56
108
  ] }),
57
- /* @__PURE__ */ jsx2(
109
+ /* @__PURE__ */ jsx4(
58
110
  "ul",
59
111
  {
60
112
  ref: row,
@@ -62,14 +114,14 @@ function ShortsShelf({ label, title, items, getId, renderItem, seeAll, previousL
62
114
  tabIndex: 0,
63
115
  "aria-labelledby": headingId,
64
116
  className: "m-0 flex list-none snap-x snap-mandatory items-stretch gap-3 overflow-x-auto overscroll-x-contain p-0 pb-2 [scrollbar-width:none] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-scrollbar]:hidden",
65
- children: items.map((item, i) => /* @__PURE__ */ jsx2("li", { className: "w-[9.5rem] shrink-0 snap-start sm:w-44", children: renderItem(item, i) }, getId(item)))
117
+ children: items.map((item, i) => /* @__PURE__ */ jsx4("li", { className: "w-[9.5rem] shrink-0 snap-start sm:w-44", children: renderItem(item, i) }, getId(item)))
66
118
  }
67
119
  )
68
120
  ] });
69
121
  }
70
122
 
71
123
  // src/media-grid/media-grid.tsx
72
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
124
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
73
125
  var GRID = {
74
126
  landscape: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
75
127
  portrait: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6"
@@ -81,18 +133,18 @@ function MediaGrid({ items, getId, orientationOf, renderItem, mode = "split", fr
81
133
  const rest = split ? items.filter((i) => orientationOf(i) !== "portrait") : items;
82
134
  const gridFrame = split ? "landscape" : frame;
83
135
  const state = (item, aspect) => ({ aspect, layout: row ? "row" : "stack", mismatch: orientationOf(item) !== aspect });
84
- return /* @__PURE__ */ jsxs2("div", { "data-media-grid": mode, className: cn("flex min-w-0 flex-col gap-8", className), ...props, children: [
85
- verticals.length > 0 && /* @__PURE__ */ jsx3(ShortsShelf, { ...shelf, items: verticals, getId, renderItem: (item) => renderItem(item, { ...state(item, "portrait"), layout: "stack" }) }),
86
- rest.length > 0 && /* @__PURE__ */ jsx3("ul", { "data-grid-frame": gridFrame, className: cn("m-0 grid list-none auto-rows-fr gap-4 p-0", row ? "grid-cols-1" : GRID[gridFrame]), children: rest.map((item) => /* @__PURE__ */ jsx3("li", { className: "min-w-0", children: renderItem(item, state(item, gridFrame)) }, getId(item))) })
136
+ return /* @__PURE__ */ jsxs3("div", { "data-media-grid": mode, className: cn("flex min-w-0 flex-col gap-8", className), ...props, children: [
137
+ verticals.length > 0 && /* @__PURE__ */ jsx5(ShortsShelf, { ...shelf, items: verticals, getId, renderItem: (item) => renderItem(item, { ...state(item, "portrait"), layout: "stack" }) }),
138
+ rest.length > 0 && /* @__PURE__ */ jsx5("ul", { "data-grid-frame": gridFrame, className: cn("m-0 grid list-none auto-rows-fr gap-4 p-0", row ? "grid-cols-1" : GRID[gridFrame]), children: rest.map((item) => /* @__PURE__ */ jsx5("li", { className: "min-w-0", children: renderItem(item, state(item, gridFrame)) }, getId(item))) })
87
139
  ] });
88
140
  }
89
141
 
90
142
  // src/media-grid/video-card.tsx
91
- import * as React2 from "react";
143
+ import * as React3 from "react";
92
144
 
93
145
  // src/card/card.tsx
94
146
  import { Slot } from "radix-ui";
95
- import { tv } from "tailwind-variants";
147
+ import { tv as tv2 } from "tailwind-variants";
96
148
 
97
149
  // src/card/recipe.ts
98
150
  var cardRecipe = defineRecipe({
@@ -120,13 +172,13 @@ var cardRecipe = defineRecipe({
120
172
  });
121
173
 
122
174
  // src/card/card.tsx
123
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
124
- var card = tv(cardRecipe);
175
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
176
+ var card = tv2(cardRecipe);
125
177
  var slots = card();
126
178
  function CardRoot({ asChild, reveal, radius, tone, interactive, className, style, ...props }) {
127
179
  const Comp = asChild ? Slot.Root : "div";
128
180
  const revealStyle = reveal === void 0 ? style : { ...style, "--kz-delay": `${Math.min(Math.max(reveal, 0), 8) * 50}ms` };
129
- return /* @__PURE__ */ jsx4(
181
+ return /* @__PURE__ */ jsx6(
130
182
  Comp,
131
183
  {
132
184
  "data-reveal": reveal === void 0 ? void 0 : "",
@@ -139,13 +191,13 @@ function CardRoot({ asChild, reveal, radius, tone, interactive, className, style
139
191
  }
140
192
  var ASPECT = { video: "aspect-video", square: "aspect-square", portrait: "aspect-[3/4]", auto: "" };
141
193
  function CardMedia({ aspect = "video", className, ...props }) {
142
- return /* @__PURE__ */ jsx4("div", { "data-aspect": aspect, className: slots.media({ className: cn(ASPECT[aspect], className) }), ...props });
194
+ return /* @__PURE__ */ jsx6("div", { "data-aspect": aspect, className: slots.media({ className: cn(ASPECT[aspect], className) }), ...props });
143
195
  }
144
196
  function CardImage({ src, alt = "", fallback, loading = "lazy", className, ...props }) {
145
197
  if (!src) {
146
- return /* @__PURE__ */ jsx4("div", { "data-fallback": "", "aria-hidden": true, className: slots.fallback({ className }), children: fallback });
198
+ return /* @__PURE__ */ jsx6("div", { "data-fallback": "", "aria-hidden": true, className: slots.fallback({ className }), children: fallback });
147
199
  }
148
- return /* @__PURE__ */ jsx4("img", { src, alt, loading, decoding: "async", className: slots.image({ className }), ...props });
200
+ return /* @__PURE__ */ jsx6("img", { src, alt, loading, decoding: "async", className: slots.image({ className }), ...props });
149
201
  }
150
202
  var CORNER = {
151
203
  "top-left": "left-2.5 top-2.5",
@@ -154,25 +206,25 @@ var CORNER = {
154
206
  "bottom-right": "bottom-2.5 right-2.5"
155
207
  };
156
208
  function CardOverlay({ corner = "top-left", className, ...props }) {
157
- return /* @__PURE__ */ jsx4("div", { "data-corner": corner, className: slots.overlay({ className: cn(CORNER[corner], className) }), ...props });
209
+ return /* @__PURE__ */ jsx6("div", { "data-corner": corner, className: slots.overlay({ className: cn(CORNER[corner], className) }), ...props });
158
210
  }
159
211
  function CardBody({ padding, className, ...props }) {
160
- return /* @__PURE__ */ jsx4("div", { className: card({ padding }).body({ className }), ...props });
212
+ return /* @__PURE__ */ jsx6("div", { className: card({ padding }).body({ className }), ...props });
161
213
  }
162
214
  function CardTitle({ as: Tag = "h3", className, ...props }) {
163
- return /* @__PURE__ */ jsx4(Tag, { className: slots.title({ className }), ...props });
215
+ return /* @__PURE__ */ jsx6(Tag, { className: slots.title({ className }), ...props });
164
216
  }
165
217
  function CardDescription({ className, ...props }) {
166
- return /* @__PURE__ */ jsx4("p", { className: slots.description({ className }), ...props });
218
+ return /* @__PURE__ */ jsx6("p", { className: slots.description({ className }), ...props });
167
219
  }
168
220
  function CardStat({ label, className, children, ...props }) {
169
- return /* @__PURE__ */ jsxs3("div", { className: slots.stat({ className }), ...props, children: [
170
- /* @__PURE__ */ jsx4("p", { className: slots.statLabel(), children: label }),
171
- /* @__PURE__ */ jsx4("p", { className: slots.statValue(), children })
221
+ return /* @__PURE__ */ jsxs4("div", { className: slots.stat({ className }), ...props, children: [
222
+ /* @__PURE__ */ jsx6("p", { className: slots.statLabel(), children: label }),
223
+ /* @__PURE__ */ jsx6("p", { className: slots.statValue(), children })
172
224
  ] });
173
225
  }
174
226
  function CardFooter({ className, ...props }) {
175
- return /* @__PURE__ */ jsx4("div", { className: slots.footer({ className }), ...props });
227
+ return /* @__PURE__ */ jsx6("div", { className: slots.footer({ className }), ...props });
176
228
  }
177
229
  var Card = {
178
230
  Root: CardRoot,
@@ -186,8 +238,48 @@ var Card = {
186
238
  Footer: CardFooter
187
239
  };
188
240
 
241
+ // src/media-grid/preview-focus.ts
242
+ var MIN_RATIO = 0.6;
243
+ var ratios = /* @__PURE__ */ new Map();
244
+ var listeners = /* @__PURE__ */ new Set();
245
+ var observer = null;
246
+ var leader = null;
247
+ function elect() {
248
+ let best = null;
249
+ let bestRatio = MIN_RATIO - Number.EPSILON;
250
+ for (const [el, ratio] of ratios) {
251
+ if (ratio > bestRatio) {
252
+ best = el;
253
+ bestRatio = ratio;
254
+ }
255
+ }
256
+ if (best === leader) return;
257
+ leader = best;
258
+ for (const l of listeners) l();
259
+ }
260
+ function trackVisibility(el) {
261
+ if (typeof IntersectionObserver === "undefined") return () => {
262
+ };
263
+ observer ??= new IntersectionObserver(
264
+ (entries) => {
265
+ for (const e of entries) ratios.set(e.target, e.isIntersecting ? e.intersectionRatio : 0);
266
+ elect();
267
+ },
268
+ { threshold: [0, 0.25, 0.5, 0.6, 0.75, 1] }
269
+ );
270
+ ratios.set(el, 0);
271
+ observer.observe(el);
272
+ return () => {
273
+ observer?.unobserve(el);
274
+ ratios.delete(el);
275
+ elect();
276
+ };
277
+ }
278
+ var subscribeLeader = (l) => (listeners.add(l), () => void listeners.delete(l));
279
+ var isLeader = (el) => el !== null && leader === el;
280
+
189
281
  // src/media-grid/video-card.tsx
190
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
282
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
191
283
  var FRAME = { landscape: "aspect-video", portrait: "aspect-[9/16]" };
192
284
  function VideoCard({
193
285
  title,
@@ -197,6 +289,8 @@ function VideoCard({
197
289
  layout = "stack",
198
290
  poster,
199
291
  previewSrc,
292
+ previewDelay = 200,
293
+ posterPriority = "auto",
200
294
  fit = "cover",
201
295
  duration,
202
296
  badges,
@@ -212,41 +306,63 @@ function VideoCard({
212
306
  onMouseLeave,
213
307
  onFocus,
214
308
  onBlur,
309
+ ref,
215
310
  ...props
216
311
  }) {
217
- const [live, setLive] = React2.useState(false);
218
- const picture = live && previewSrc ? previewSrc : poster;
312
+ const [node, setNode] = React3.useState(null);
313
+ const setRef = React3.useCallback(
314
+ (el) => {
315
+ setNode(el);
316
+ if (typeof ref === "function") ref(el);
317
+ else if (ref) ref.current = el;
318
+ },
319
+ [ref]
320
+ );
321
+ const canPreview = Boolean(previewSrc) && !blurred;
322
+ const { live, arm, disarm } = usePreviewIntent(node, canPreview, previewDelay);
219
323
  const row = layout === "row";
220
324
  const tall = aspect === "portrait" && !row;
221
- return /* @__PURE__ */ jsx5(
325
+ return /* @__PURE__ */ jsx7(
222
326
  "article",
223
327
  {
328
+ ref: setRef,
224
329
  "data-aspect": aspect,
225
330
  "data-layout": layout,
226
331
  className: cn("group relative h-full min-w-0", className),
227
- onMouseEnter: (e) => (setLive(true), onMouseEnter?.(e)),
228
- onMouseLeave: (e) => (setLive(false), onMouseLeave?.(e)),
229
- onFocus: (e) => (setLive(true), onFocus?.(e)),
230
- onBlur: (e) => (setLive(false), onBlur?.(e)),
332
+ "data-preview": live ? "on" : void 0,
333
+ onMouseEnter: (e) => (arm("pointer"), onMouseEnter?.(e)),
334
+ onMouseLeave: (e) => (disarm("pointer"), onMouseLeave?.(e)),
335
+ onFocus: (e) => (arm("focus"), onFocus?.(e)),
336
+ onBlur: (e) => (disarm("focus"), onBlur?.(e)),
231
337
  ...props,
232
- children: /* @__PURE__ */ jsxs4(Card.Root, { interactive: true, tone: "glass", className: cn("h-full w-full max-w-full", row ? "flex-row" : "flex-col"), children: [
233
- /* @__PURE__ */ jsxs4(Card.Media, { aspect: "auto", className: cn("shrink-0", FRAME[aspect], row && (aspect === "portrait" ? "w-24 sm:w-28" : "w-40 sm:w-56")), children: [
234
- fit === "contain" && picture && /* @__PURE__ */ jsx5("img", { src: picture, alt: "", "aria-hidden": true, loading: "lazy", className: "absolute inset-0 h-full w-full scale-125 object-cover opacity-70 blur-2xl" }),
235
- /* @__PURE__ */ jsx5(Card.Image, { src: picture, loading, fallback, className: cn(fit === "contain" && "relative object-contain", blurred && "scale-110 blur-xl") }),
236
- overlay && /* @__PURE__ */ jsx5("div", { className: "absolute inset-0 z-20 flex", children: overlay }),
237
- badges && /* @__PURE__ */ jsx5(Card.Overlay, { corner: "top-left", className: "theme-dark flex-wrap", children: badges }),
238
- duration && /* @__PURE__ */ jsx5(Card.Overlay, { corner: "bottom-right", children: /* @__PURE__ */ jsx5("span", { className: "rounded-sm bg-scrim px-1.5 py-0.5 font-mono text-[11px] text-fg-on-media", children: duration }) })
338
+ children: /* @__PURE__ */ jsxs5(Card.Root, { interactive: true, tone: "glass", className: cn("h-full w-full max-w-full", row ? "flex-row" : "flex-col"), children: [
339
+ /* @__PURE__ */ jsxs5(Card.Media, { aspect: "auto", className: cn("shrink-0", FRAME[aspect], row && (aspect === "portrait" ? "w-24 sm:w-28" : "w-40 sm:w-56")), children: [
340
+ fit === "contain" && poster && /* @__PURE__ */ jsx7("img", { src: poster, alt: "", "aria-hidden": true, loading: "lazy", className: "absolute inset-0 h-full w-full scale-125 object-cover opacity-70 blur-2xl" }),
341
+ /* @__PURE__ */ jsx7(
342
+ Card.Image,
343
+ {
344
+ src: poster,
345
+ loading,
346
+ fetchPriority: posterPriority === "auto" ? void 0 : posterPriority,
347
+ fallback,
348
+ className: cn(fit === "contain" && "relative object-contain", blurred && "scale-110 blur-xl")
349
+ }
350
+ ),
351
+ live && previewSrc && /* @__PURE__ */ jsx7(PreviewLayer, { src: previewSrc, contain: fit === "contain" }),
352
+ overlay && /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 z-20 flex", children: overlay }),
353
+ badges && /* @__PURE__ */ jsx7(Card.Overlay, { corner: "top-left", className: "theme-dark pointer-events-none flex-wrap", children: badges }),
354
+ duration && /* @__PURE__ */ jsx7(Card.Overlay, { corner: "bottom-right", className: "pointer-events-none", children: /* @__PURE__ */ jsx7("span", { className: "rounded-sm bg-scrim px-1.5 py-0.5 font-mono text-[11px] text-fg-on-media", children: duration }) })
239
355
  ] }),
240
- /* @__PURE__ */ jsxs4(Card.Body, { className: "min-w-0 flex-1 justify-between gap-2 p-3", children: [
241
- /* @__PURE__ */ jsx5("h3", { className: "m-0 min-h-[2.5em] text-sm font-semibold leading-tight text-fg", children: /* @__PURE__ */ jsx5(BlockLink, { href, linkAs, className: "line-clamp-2 outline-none after:absolute after:inset-0 after:z-0 after:content-[''] focus-visible:after:ring-2 focus-visible:after:ring-ring", children: title }) }),
242
- /* @__PURE__ */ jsxs4("div", { className: cn("relative z-10 text-xs text-fg-secondary", tall ? "flex flex-col gap-1" : "flex h-8 items-center gap-2"), children: [
243
- creator && /* @__PURE__ */ jsxs4("div", { className: "flex h-6 min-w-0 items-center gap-2", children: [
244
- /* @__PURE__ */ jsx5(Avatar, { src: creator.avatarSrc, defaultAvatar: creator.defaultAvatar, alt: "", fallback: creator.name.charAt(0), size: "xs", className: "shrink-0" }),
245
- creator.href ? /* @__PURE__ */ jsx5(BlockLink, { href: creator.href, linkAs, className: "min-w-0 truncate hover:text-fg", children: creator.name }) : /* @__PURE__ */ jsx5("span", { className: "min-w-0 truncate", children: creator.name })
356
+ /* @__PURE__ */ jsxs5(Card.Body, { className: "static min-w-0 flex-1 justify-between gap-2 p-3", children: [
357
+ /* @__PURE__ */ jsx7("h3", { className: "m-0 min-h-[2.5em] text-sm font-semibold leading-tight text-fg", children: /* @__PURE__ */ jsx7(BlockLink, { href, linkAs, className: "line-clamp-2 outline-none after:absolute after:inset-0 after:z-0 after:content-[''] focus-visible:after:ring-2 focus-visible:after:ring-ring", children: title }) }),
358
+ /* @__PURE__ */ jsxs5("div", { className: cn("relative z-10 text-xs text-fg-secondary", tall ? "flex flex-col gap-1" : "flex h-8 items-center gap-2"), children: [
359
+ creator && /* @__PURE__ */ jsxs5("div", { className: "flex h-6 min-w-0 items-center gap-2", children: [
360
+ /* @__PURE__ */ jsx7(Avatar, { src: creator.avatarSrc, defaultAvatar: creator.defaultAvatar, alt: "", fallback: creator.name.charAt(0), size: "xs", className: "shrink-0" }),
361
+ creator.href ? /* @__PURE__ */ jsx7(BlockLink, { href: creator.href, linkAs, className: "min-w-0 truncate hover:text-fg", children: creator.name }) : /* @__PURE__ */ jsx7("span", { className: "min-w-0 truncate", children: creator.name })
246
362
  ] }),
247
- (meta || action) && /* @__PURE__ */ jsxs4("div", { className: cn("flex items-center gap-2", tall ? "h-7 justify-between" : "ml-auto shrink-0"), children: [
248
- meta && /* @__PURE__ */ jsx5("span", { className: "shrink-0 font-mono text-[11px] text-fg-muted", children: meta }),
249
- action && /* @__PURE__ */ jsx5("span", { className: "shrink-0", children: action })
363
+ (meta || action) && /* @__PURE__ */ jsxs5("div", { className: cn("flex items-center gap-2", tall ? "h-7 justify-between" : "ml-auto shrink-0"), children: [
364
+ meta && /* @__PURE__ */ jsx7("span", { className: "shrink-0 font-mono text-[11px] text-fg-muted", children: meta }),
365
+ action && /* @__PURE__ */ jsx7("span", { className: "shrink-0", children: action })
250
366
  ] })
251
367
  ] })
252
368
  ] })
@@ -254,9 +370,140 @@ function VideoCard({
254
370
  }
255
371
  );
256
372
  }
373
+ function PreviewLayer({ src, contain }) {
374
+ const [ready, setReady] = React3.useState(false);
375
+ return /* @__PURE__ */ jsx7(
376
+ "img",
377
+ {
378
+ src,
379
+ alt: "",
380
+ "aria-hidden": true,
381
+ decoding: "async",
382
+ "data-preview-layer": "",
383
+ "data-ready": ready ? "" : void 0,
384
+ onLoad: () => setReady(true),
385
+ className: cn("pointer-events-none absolute inset-0 h-full w-full opacity-0 transition-opacity duration-300 data-[ready]:opacity-100 motion-reduce:transition-none", contain ? "object-contain" : "object-cover")
386
+ }
387
+ );
388
+ }
389
+ var matches = (query) => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(query).matches;
390
+ var hoverCapable = () => matches("(hover: hover)");
391
+ var saveData = () => typeof navigator !== "undefined" && navigator.connection?.saveData === true;
392
+ function usePreviewIntent(node, enabled, delay) {
393
+ const [causes, setCauses] = React3.useState(/* @__PURE__ */ new Set());
394
+ const timers = React3.useRef(/* @__PURE__ */ new Map());
395
+ const arm = React3.useCallback(
396
+ (cause) => {
397
+ if (!enabled || saveData()) return;
398
+ if (cause === "pointer" && !hoverCapable()) return;
399
+ clearTimeout(timers.current.get(cause));
400
+ timers.current.set(
401
+ cause,
402
+ setTimeout(() => setCauses((c) => new Set(c).add(cause)), delay)
403
+ );
404
+ },
405
+ [enabled, delay]
406
+ );
407
+ const disarm = React3.useCallback((cause) => {
408
+ clearTimeout(timers.current.get(cause));
409
+ timers.current.delete(cause);
410
+ setCauses((c) => {
411
+ if (!c.has(cause)) return c;
412
+ const next = new Set(c);
413
+ next.delete(cause);
414
+ return next;
415
+ });
416
+ }, []);
417
+ React3.useEffect(() => {
418
+ const pending = timers.current;
419
+ return () => {
420
+ for (const t of pending.values()) clearTimeout(t);
421
+ pending.clear();
422
+ };
423
+ }, []);
424
+ const touch = enabled && !hoverCapable();
425
+ const leading = React3.useSyncExternalStore(
426
+ subscribeLeader,
427
+ () => isLeader(node),
428
+ () => false
429
+ );
430
+ React3.useEffect(() => {
431
+ if (!node || !touch) return;
432
+ return trackVisibility(node);
433
+ }, [node, touch]);
434
+ const [ready, setReady] = React3.useState(false);
435
+ const [wasLeading, setWasLeading] = React3.useState(false);
436
+ if (leading !== wasLeading) {
437
+ setWasLeading(leading);
438
+ if (!leading) setReady(false);
439
+ }
440
+ const wanted = touch && leading && !saveData() && !matches("(prefers-reduced-motion: reduce)");
441
+ React3.useEffect(() => {
442
+ if (!wanted) return;
443
+ const t = setTimeout(() => setReady(true), delay);
444
+ return () => clearTimeout(t);
445
+ }, [wanted, delay]);
446
+ return { live: enabled && (causes.size > 0 || wanted && ready), arm, disarm };
447
+ }
448
+
449
+ // src/skeleton/skeleton.tsx
450
+ import { tv as tv3 } from "tailwind-variants";
451
+ import { jsx as jsx8 } from "react/jsx-runtime";
452
+ var skeletonVariants = tv3({
453
+ base: "kz-skeleton block bg-surface-3",
454
+ variants: {
455
+ shape: { text: "h-3 w-full rounded-sm", circle: "aspect-square h-10 rounded-full", rect: "h-24 w-full rounded-lg" }
456
+ },
457
+ defaultVariants: { shape: "text" }
458
+ });
459
+ function Skeleton({ shape, className, ...props }) {
460
+ return /* @__PURE__ */ jsx8("span", { "aria-hidden": true, "data-shape": shape ?? "text", className: skeletonVariants({ shape, className }), ...props });
461
+ }
462
+
463
+ // src/media-grid/video-card-skeleton.tsx
464
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
465
+ var FRAME2 = { landscape: "aspect-video", portrait: "aspect-[9/16]" };
466
+ function VideoCardSkeleton({ aspect = "landscape", layout = "stack", className, ...props }) {
467
+ const row = layout === "row";
468
+ const tall = aspect === "portrait" && !row;
469
+ return /* @__PURE__ */ jsx9("div", { "aria-hidden": true, "data-skeleton": "video-card", "data-aspect": aspect, "data-layout": layout, className: cn("h-full min-w-0", className), ...props, children: /* @__PURE__ */ jsxs6(Card.Root, { tone: "glass", className: cn("h-full w-full max-w-full", row ? "flex-row" : "flex-col"), children: [
470
+ /* @__PURE__ */ jsx9(Card.Media, { aspect: "auto", className: cn("shrink-0", FRAME2[aspect], row && (aspect === "portrait" ? "w-24 sm:w-28" : "w-40 sm:w-56")), children: /* @__PURE__ */ jsx9(Skeleton, { shape: "rect", className: "h-full w-full rounded-none" }) }),
471
+ /* @__PURE__ */ jsxs6(Card.Body, { className: "static min-w-0 flex-1 justify-between gap-2 p-3", children: [
472
+ /* @__PURE__ */ jsxs6("div", { className: "flex min-h-[2.5em] flex-col justify-center gap-1.5", children: [
473
+ /* @__PURE__ */ jsx9(Skeleton, { className: "h-3" }),
474
+ /* @__PURE__ */ jsx9(Skeleton, { className: "h-3 w-3/5" })
475
+ ] }),
476
+ /* @__PURE__ */ jsxs6("div", { className: cn("flex text-xs", tall ? "flex-col gap-1" : "h-8 items-center gap-2"), children: [
477
+ /* @__PURE__ */ jsxs6("div", { className: "flex h-6 min-w-0 items-center gap-2", children: [
478
+ /* @__PURE__ */ jsx9(Skeleton, { shape: "circle", className: "h-6 w-6 shrink-0" }),
479
+ /* @__PURE__ */ jsx9(Skeleton, { className: "h-2.5 w-20" })
480
+ ] }),
481
+ /* @__PURE__ */ jsx9("div", { className: cn("flex items-center", tall ? "h-7" : "ml-auto"), children: /* @__PURE__ */ jsx9(Skeleton, { className: "h-2.5 w-14" }) })
482
+ ] })
483
+ ] })
484
+ ] }) });
485
+ }
486
+ var GRID2 = {
487
+ landscape: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
488
+ portrait: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6"
489
+ };
490
+ function MediaGridSkeleton({ label, count = 8, shelfCount = 0, layout = "grid", frame = "landscape", className, ...props }) {
491
+ const row = layout === "list";
492
+ return /* @__PURE__ */ jsxs6("div", { role: "status", "aria-busy": "true", "data-media-grid-skeleton": "", className: cn("flex min-w-0 flex-col gap-8", className), ...props, children: [
493
+ /* @__PURE__ */ jsx9("span", { className: "sr-only", children: label }),
494
+ shelfCount > 0 && /* @__PURE__ */ jsxs6("div", { "aria-hidden": true, className: "min-w-0", children: [
495
+ /* @__PURE__ */ jsx9(Skeleton, { className: "mb-3 h-4 w-24" }),
496
+ /* @__PURE__ */ jsx9("ul", { className: "m-0 flex list-none items-stretch gap-3 overflow-hidden p-0", children: Array.from({ length: shelfCount }, (_, i) => /* @__PURE__ */ jsx9("li", { className: "w-[9.5rem] shrink-0 sm:w-44", children: /* @__PURE__ */ jsx9(VideoCardSkeleton, { aspect: "portrait" }) }, i)) })
497
+ ] }),
498
+ /* @__PURE__ */ jsx9("ul", { "aria-hidden": true, className: cn("m-0 grid list-none auto-rows-fr gap-4 p-0", row ? "grid-cols-1" : GRID2[frame]), children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx9("li", { className: "min-w-0", children: /* @__PURE__ */ jsx9(VideoCardSkeleton, { aspect: frame, layout: row ? "row" : "stack" }) }, i)) })
499
+ ] });
500
+ }
257
501
  export {
502
+ LoadMore,
258
503
  MediaGrid,
504
+ MediaGridSkeleton,
259
505
  ShortsShelf,
260
- VideoCard
506
+ VideoCard,
507
+ VideoCardSkeleton
261
508
  };
262
509
  //# sourceMappingURL=media-grid.js.map