@krizaka/ui 3.6.0 → 3.7.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 (189) hide show
  1. package/dist/chat.js +2 -1
  2. package/dist/chat.js.map +1 -1
  3. package/dist/{chunk-F2GKFIYK.js → chunk-CURW36FI.js} +1 -1
  4. package/dist/chunk-JXLDUDTD.js +2 -0
  5. package/dist/chunk-JXLDUDTD.js.map +1 -0
  6. package/dist/chunk-NPF3OHE2.js +2 -0
  7. package/dist/chunk-NPF3OHE2.js.map +1 -0
  8. package/dist/{chunk-ECSCA3CN.js → chunk-UOMP6VOZ.js} +1 -1
  9. package/dist/data-table.js +2 -1
  10. package/dist/data-table.js.map +1 -1
  11. package/dist/match-dialog.js +4 -2
  12. package/dist/match-dialog.js.map +1 -1
  13. package/dist/native/vertical-video.d.ts +102 -0
  14. package/dist/native/vertical-video.js +163 -0
  15. package/dist/native/vertical-video.js.map +1 -0
  16. package/dist/vertical-video.d.ts +112 -0
  17. package/dist/vertical-video.js +307 -0
  18. package/dist/vertical-video.js.map +1 -0
  19. package/package.json +39 -5
  20. package/registry/achievement.json +1 -1
  21. package/registry/action-bar.json +1 -1
  22. package/registry/activity-feed.json +1 -1
  23. package/registry/alert.json +1 -1
  24. package/registry/app-shell.json +1 -1
  25. package/registry/argument-card.json +1 -1
  26. package/registry/audio-player.json +1 -1
  27. package/registry/auth-layout.json +1 -1
  28. package/registry/avatar.json +6 -6
  29. package/registry/badge.json +1 -1
  30. package/registry/button.json +1 -1
  31. package/registry/card-grid.json +1 -1
  32. package/registry/card.json +1 -1
  33. package/registry/catalogue-page.json +1 -1
  34. package/registry/changelog-page.json +1 -1
  35. package/registry/chat.json +1 -1
  36. package/registry/checkbox.json +1 -1
  37. package/registry/chip-link.json +1 -1
  38. package/registry/chip.json +1 -1
  39. package/registry/choice-grid.json +1 -1
  40. package/registry/code-block.json +1 -1
  41. package/registry/combobox.json +1 -1
  42. package/registry/command.json +1 -1
  43. package/registry/component-page.json +1 -1
  44. package/registry/confirm-button.json +1 -1
  45. package/registry/contact-page.json +1 -1
  46. package/registry/continue-prompt.json +1 -1
  47. package/registry/countdown.json +1 -1
  48. package/registry/cta-panel.json +1 -1
  49. package/registry/dashboard-layout.json +1 -1
  50. package/registry/data-table.json +1 -1
  51. package/registry/date-picker.json +1 -1
  52. package/registry/decision-list.json +1 -1
  53. package/registry/description-list.json +1 -1
  54. package/registry/detail-layout.json +1 -1
  55. package/registry/dialog.json +1 -1
  56. package/registry/docs-article-page.json +1 -1
  57. package/registry/docs-hub-page.json +1 -1
  58. package/registry/docs-layout.json +1 -1
  59. package/registry/dropdown-menu.json +1 -1
  60. package/registry/empty-state.json +1 -1
  61. package/registry/error-page.json +1 -1
  62. package/registry/examples/avatar/group.js +4 -7
  63. package/registry/examples/avatar/group.tsx +4 -11
  64. package/registry/examples/avatar/native/group.dark.png +0 -0
  65. package/registry/examples/avatar/native/group.js +4 -4
  66. package/registry/examples/avatar/native/group.light.png +0 -0
  67. package/registry/examples/avatar/native/group.tsx +4 -4
  68. package/registry/examples/avatar/native/presence.dark.png +0 -0
  69. package/registry/examples/avatar/native/presence.js +8 -5
  70. package/registry/examples/avatar/native/presence.light.png +0 -0
  71. package/registry/examples/avatar/native/presence.tsx +9 -5
  72. package/registry/examples/avatar/with-image.js +1 -4
  73. package/registry/examples/avatar/with-image.tsx +2 -9
  74. package/registry/examples/profile-card/creator.js +2 -2
  75. package/registry/examples/profile-card/creator.tsx +2 -2
  76. package/registry/examples/profile-card/match.js +2 -2
  77. package/registry/examples/profile-card/match.tsx +2 -2
  78. package/registry/examples/profile-card/native/default.dark.png +0 -0
  79. package/registry/examples/profile-card/native/default.js +2 -2
  80. package/registry/examples/profile-card/native/default.light.png +0 -0
  81. package/registry/examples/profile-card/native/default.tsx +2 -2
  82. package/registry/examples/screen/native/app-shell.dark.png +0 -0
  83. package/registry/examples/screen/native/app-shell.js +1 -1
  84. package/registry/examples/screen/native/app-shell.light.png +0 -0
  85. package/registry/examples/screen/native/app-shell.tsx +1 -1
  86. package/registry/examples/screen/native/feed.dark.png +0 -0
  87. package/registry/examples/screen/native/feed.js +31 -14
  88. package/registry/examples/screen/native/feed.light.png +0 -0
  89. package/registry/examples/screen/native/feed.tsx +28 -24
  90. package/registry/examples/screen/native/profile.dark.png +0 -0
  91. package/registry/examples/screen/native/profile.js +2 -2
  92. package/registry/examples/screen/native/profile.light.png +0 -0
  93. package/registry/examples/screen/native/profile.tsx +2 -2
  94. package/registry/examples/swipe-deck/discover.js +2 -3
  95. package/registry/examples/swipe-deck/discover.tsx +2 -3
  96. package/registry/examples/swipe-deck/native/discover.dark.png +0 -0
  97. package/registry/examples/swipe-deck/native/discover.js +2 -3
  98. package/registry/examples/swipe-deck/native/discover.light.png +0 -0
  99. package/registry/examples/swipe-deck/native/discover.tsx +2 -3
  100. package/registry/examples/vertical-video/feed.d.ts +4 -0
  101. package/registry/examples/vertical-video/feed.js +40 -0
  102. package/registry/examples/vertical-video/feed.tsx +36 -0
  103. package/registry/examples/vertical-video/native/rail.d.ts +4 -0
  104. package/registry/examples/vertical-video/native/rail.dark.png +0 -0
  105. package/registry/examples/vertical-video/native/rail.js +39 -0
  106. package/registry/examples/vertical-video/native/rail.light.png +0 -0
  107. package/registry/examples/vertical-video/native/rail.tsx +37 -0
  108. package/registry/examples/vertical-video/rail.d.ts +4 -0
  109. package/registry/examples/vertical-video/rail.js +39 -0
  110. package/registry/examples/vertical-video/rail.tsx +39 -0
  111. package/registry/field.json +1 -1
  112. package/registry/file-upload.json +1 -1
  113. package/registry/filter-bar.json +1 -1
  114. package/registry/form.json +1 -1
  115. package/registry/gallery-page.json +1 -1
  116. package/registry/glow-card.json +1 -1
  117. package/registry/goal-gauge.json +1 -1
  118. package/registry/how-it-works-page.json +1 -1
  119. package/registry/image-cropper.json +1 -1
  120. package/registry/index.json +27 -4
  121. package/registry/iso.json +1 -1
  122. package/registry/kanban.json +1 -1
  123. package/registry/kbd.json +1 -1
  124. package/registry/landing-page.json +1 -1
  125. package/registry/landscape.json +1 -1
  126. package/registry/legal-page.json +1 -1
  127. package/registry/link-card.json +1 -1
  128. package/registry/list.json +1 -1
  129. package/registry/loader.json +1 -1
  130. package/registry/longform-page.json +1 -1
  131. package/registry/map.json +2 -2
  132. package/registry/marketing-layout.json +1 -1
  133. package/registry/marquee.json +1 -1
  134. package/registry/mascot-quote.json +1 -1
  135. package/registry/mascot-state.json +1 -1
  136. package/registry/mascots.json +1 -1
  137. package/registry/match-dialog.json +1 -1
  138. package/registry/media-capture.json +1 -1
  139. package/registry/meter.json +1 -1
  140. package/registry/nav-bar.json +1 -1
  141. package/registry/number-ticker.json +1 -1
  142. package/registry/page-header.json +1 -1
  143. package/registry/page-hero.json +1 -1
  144. package/registry/page.json +1 -1
  145. package/registry/pagination.json +1 -1
  146. package/registry/photo-grid.json +1 -1
  147. package/registry/pointer-light.json +1 -1
  148. package/registry/popover.json +1 -1
  149. package/registry/pricing-page.json +1 -1
  150. package/registry/product-page.json +1 -1
  151. package/registry/profile-card.json +5 -5
  152. package/registry/progress.json +1 -1
  153. package/registry/promise-card.json +1 -1
  154. package/registry/radio-group.json +1 -1
  155. package/registry/screen.json +5 -5
  156. package/registry/scroll-stage.json +1 -1
  157. package/registry/search-field.json +1 -1
  158. package/registry/section-backdrop.json +1 -1
  159. package/registry/separator.json +1 -1
  160. package/registry/settings-layout.json +1 -1
  161. package/registry/skeleton.json +1 -1
  162. package/registry/skip-link.json +1 -1
  163. package/registry/slider.json +1 -1
  164. package/registry/slot.json +1 -1
  165. package/registry/spinner.json +1 -1
  166. package/registry/split-view.json +1 -1
  167. package/registry/stage-banner.json +1 -1
  168. package/registry/stat.json +1 -1
  169. package/registry/status-page.json +1 -1
  170. package/registry/step-progress.json +1 -1
  171. package/registry/stepper.json +1 -1
  172. package/registry/swipe-deck.json +4 -4
  173. package/registry/switch.json +1 -1
  174. package/registry/tabs.json +1 -1
  175. package/registry/tag-input.json +1 -1
  176. package/registry/tag-selector.json +1 -1
  177. package/registry/terminal-replay.json +1 -1
  178. package/registry/theme.json +1 -1
  179. package/registry/toast.json +1 -1
  180. package/registry/toc.json +1 -1
  181. package/registry/tooltip.json +1 -1
  182. package/registry/tree-view.json +1 -1
  183. package/registry/verified-badge.json +1 -1
  184. package/registry/vertical-video.json +871 -0
  185. package/registry/video-player.json +1 -1
  186. package/registry/video-trimmer.json +1 -1
  187. package/registry/virtual.json +1 -1
  188. /package/dist/{chunk-F2GKFIYK.js.map → chunk-CURW36FI.js.map} +0 -0
  189. /package/dist/{chunk-ECSCA3CN.js.map → chunk-UOMP6VOZ.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dialog/dialog.tsx","../src/dialog/return-focus.ts","../src/match-dialog/match-dialog.tsx"],"sourcesContent":["// Client (a portal). Radix does the focus trap, the scroll lock, Escape, the outside click, aria-modal, the\n// aria-labelledby / aria-describedby wiring and the focus return: this file only holds the shape.\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { IconButton } from \"../button/button\";\nimport { useReturnFocus } from \"./return-focus\";\n\nexport const dialog = tv({\n slots: {\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n content:\n \"kz-dialog fixed z-50 flex flex-col overflow-hidden border border-border-default bg-surface-1 text-fg shadow-lg focus:outline-hidden\",\n close: \"absolute right-3 top-3 h-9 w-9\",\n header: \"flex flex-col gap-1 px-5 pb-2 pr-14 pt-5\",\n title: \"text-base font-bold text-fg\",\n description: \"text-sm text-fg-secondary\",\n body: \"min-h-0 flex-1 overflow-y-auto px-5 pb-5\",\n footer:\n \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle px-5 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]\",\n },\n variants: {\n placement: {\n center: { content: \"left-1/2 top-1/2 max-h-[85dvh] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl\" },\n /* A sheet: anchored at the bottom on a phone, centred from `sm` up. */\n bottom: {\n content:\n \"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[1.75rem] sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:w-full \" +\n \"sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.75rem]\",\n },\n right: { content: \"inset-y-0 right-0 h-full w-full max-w-md rounded-none border-y-0 border-r-0\" },\n /* A navigation drawer: full height, from the left, clear of the notch. */\n left: { content: \"inset-y-0 left-0 h-full w-full max-w-md rounded-none border-y-0 border-l-0 pl-[env(safe-area-inset-left)]\" },\n },\n size: { sm: { content: \"sm:max-w-sm\" }, md: { content: \"sm:max-w-md\" }, lg: { content: \"sm:max-w-2xl\" } },\n },\n defaultVariants: { placement: \"center\", size: \"md\" },\n});\nconst s = dialog();\n\nexport type DialogVariants = VariantProps<typeof dialog>;\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype CloseButtonProps =\n | {\n /** The accessible name of the close button — passed translated. */\n closeLabel: string;\n /** Leaves the close button out (the content brings its own way out, or none: see `dismissible`). */\n hideClose?: false;\n }\n | { closeLabel?: string; hideClose: true };\n\nexport type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &\n DialogVariants &\n CloseButtonProps & {\n /**\n * center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height) · left (a\n * navigation drawer, full height).\n * @default \"center\"\n */\n placement?: DialogVariants[\"placement\"];\n /**\n * sm · md · lg: the width of the dialog.\n * @default \"md\"\n */\n size?: DialogVariants[\"size\"];\n /**\n * `false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\n * through `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.\n */\n dismissible?: boolean;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n };\n\nconst prevent = (event: Event) => event.preventDefault();\n\n/** Radix's content, mounted only while open: it remembers the opener then and gives focus back on close (WCAG 2.4.3). */\nfunction ReturnFocusContent({ onCloseAutoFocus, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {\n return <DialogPrimitive.Content onCloseAutoFocus={useReturnFocus(onCloseAutoFocus)} {...props} />;\n}\n\n/** The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`. */\nexport function DialogContent({\n placement,\n size,\n closeLabel,\n hideClose,\n dismissible = true,\n container,\n className,\n children,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n ...props\n}: DialogContentProps) {\n const v = dialog({ placement, size });\n return (\n <DialogPrimitive.Portal container={container}>\n <DialogPrimitive.Overlay className={v.overlay()} />\n <ReturnFocusContent\n data-placement={placement ?? \"center\"}\n data-dismissible={dismissible ? undefined : \"false\"}\n className={v.content({ className })}\n onEscapeKeyDown={dismissible ? onEscapeKeyDown : chain(onEscapeKeyDown, prevent)}\n onPointerDownOutside={dismissible ? onPointerDownOutside : chain(onPointerDownOutside, prevent)}\n onInteractOutside={dismissible ? onInteractOutside : chain(onInteractOutside, prevent)}\n {...props}\n >\n {children}\n {!hideClose && (\n <DialogPrimitive.Close asChild>\n <IconButton label={closeLabel as string} variant=\"ghost\" shape=\"pill\" className={v.close()}>\n <CloseIcon />\n </IconButton>\n </DialogPrimitive.Close>\n )}\n </ReturnFocusContent>\n </DialogPrimitive.Portal>\n );\n}\n\nfunction chain<E extends Event>(handler: ((event: E) => void) | undefined, then: (event: E) => void) {\n return (event: E) => {\n handler?.(event);\n then(event);\n };\n}\n\nexport function DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.header({ className })} {...props} />;\n}\n\nexport function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return <DialogPrimitive.Title className={s.title({ className })} {...props} />;\n}\n\nexport function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return <DialogPrimitive.Description className={s.description({ className })} {...props} />;\n}\n\nexport function DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport function DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.footer({ className })} {...props} />;\n}\n\n/** `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Dialog = {\n Root: DialogPrimitive.Root,\n Trigger: DialogPrimitive.Trigger,\n Close: DialogPrimitive.Close,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n};\n\n/** `Omit` that keeps the close-button union (`closeLabel` required unless `hideClose`). */\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\nexport type SheetProps = DistributiveOmit<DialogContentProps, \"placement\">;\n\n/** A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`. */\nexport function Sheet(props: SheetProps) {\n return <DialogContent placement=\"bottom\" {...props} />;\n}\n","// Client. WCAG 2.4.3: when a dialog closes, focus goes back to what opened it. Radix only does it for `Dialog.Trigger`;\n// most app dialogs are controlled (`open` / `onOpenChange`) and opened by a plain button, so on Escape focus fell to\n// <body>. The content remembers the focused element when it mounts (it is still the opener: Radix moves focus in an\n// effect) and gives it back on close, unless the consumer's `onCloseAutoFocus` prevented the default.\nimport * as React from \"react\";\n\n/** The handler for `onCloseAutoFocus`: yours first, then focus back on the opener if it is still on the page. */\nexport function useReturnFocus(onCloseAutoFocus?: (event: Event) => void) {\n const [opener] = React.useState<Element | null>(() => (typeof document === \"undefined\" ? null : document.activeElement));\n return React.useCallback(\n (event: Event) => {\n onCloseAutoFocus?.(event);\n if (event.defaultPrevented) return;\n if (opener instanceof HTMLElement && opener !== document.body && opener.isConnected) {\n event.preventDefault();\n opener.focus();\n }\n },\n [opener, onCloseAutoFocus],\n );\n}\n","// Client (a portal). The moment two people choose each other — a match, a pair, a deal: a dialog in the brand\n// gradient, the two avatars meeting at a heart, the next step. The entrance plays once; still under reduced motion.\nimport * as React from \"react\";\n\nimport { Avatar } from \"../avatar\";\nimport { Button } from \"../button\";\nimport { Dialog } from \"../dialog\";\n\nexport type MatchDialogProps = {\n /** Shown (controlled). */\n open: boolean;\n /** Called when it closes. */\n onOpenChange: (open: boolean) => void;\n /** The headline (“It's a match!”) — passed translated; the dialog's name. */\n title: string;\n /** A line under it. */\n description?: string;\n /** The two people: avatar URLs and names. */\n people: readonly [{ src?: string | null; name: string }, { src?: string | null; name: string }];\n /** The main action (“Send a message”). */\n primaryLabel: string;\n /** What it does. */\n onPrimary: () => void;\n /** The way back (“Keep swiping”). */\n secondaryLabel: string;\n /** Where it is drawn (the page's body by default): give an element inside a brand scope (`.brand-…`) so the dialog wears the brand too. */\n container?: HTMLElement | null;\n};\n\n/** Two people choose each other: the avatars meet at a heart in the brand gradient, the next step under them. */\nexport function MatchDialog({ open, onOpenChange, title, description, people, primaryLabel, onPrimary, secondaryLabel, container }: MatchDialogProps) {\n return (\n <Dialog.Root open={open} onOpenChange={onOpenChange}>\n <Dialog.Content hideClose size=\"sm\" container={container} className=\"overflow-hidden border-0 bg-gradient-to-br from-accent to-accent-2 text-center text-on-accent\">\n <style href=\"krizaka-match\" precedence=\"default\">\n {\"@media (prefers-reduced-motion:no-preference){[data-kz-match] [data-side=a]{animation:kz-match-a .55s cubic-bezier(.3,1.6,.5,1) both}[data-kz-match] [data-side=b]{animation:kz-match-b .55s cubic-bezier(.3,1.6,.5,1) both}[data-kz-match] [data-heart]{animation:kz-match-heart .45s .25s cubic-bezier(.3,1.6,.5,1) both}}@keyframes kz-match-a{from{transform:translateX(-60px) rotate(-8deg);opacity:0}}@keyframes kz-match-b{from{transform:translateX(60px) rotate(8deg);opacity:0}}@keyframes kz-match-heart{from{transform:scale(0)}}\"}\n </style>\n <div data-kz-match=\"\" className=\"flex flex-col items-center gap-3 px-2 pb-2 pt-6\">\n <Dialog.Title className=\"font-display text-4xl font-black text-on-accent\">{title}</Dialog.Title>\n {description ? <Dialog.Description className=\"max-w-xs text-sm text-on-accent\">{description}</Dialog.Description> : null}\n <div className=\"relative my-6 flex h-36 w-64 items-center justify-center\" aria-hidden>\n <span data-side=\"a\" className=\"absolute left-6 -rotate-6 rounded-full ring-4 ring-on-accent\">\n <Avatar src={people[0].src ?? null} alt=\"\" fallback={people[0].name.charAt(0)} className=\"h-28 w-28 text-3xl\" />\n </span>\n <span data-side=\"b\" className=\"absolute right-6 rotate-6 rounded-full ring-4 ring-on-accent\">\n <Avatar src={people[1].src ?? null} alt=\"\" fallback={people[1].name.charAt(0)} className=\"h-28 w-28 text-3xl\" />\n </span>\n <span data-heart=\"\" className=\"absolute -bottom-1 flex h-12 w-12 items-center justify-center rounded-full bg-on-accent text-accent shadow-lg\">\n <svg viewBox=\"0 0 24 24\" className=\"h-6 w-6\" fill=\"currentColor\">\n <path d=\"M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z\" />\n </svg>\n </span>\n </div>\n <div className=\"flex w-full flex-col gap-2\">\n <Button\n size=\"lg\"\n onClick={() => {\n onOpenChange(false);\n onPrimary();\n }}\n >\n {primaryLabel}\n </Button>\n <Dialog.Close asChild>\n <Button size=\"lg\" variant=\"ghost\" className=\"text-on-accent hover:bg-on-accent/15\">\n {secondaryLabel}\n </Button>\n </Dialog.Close>\n </div>\n </div>\n </Dialog.Content>\n </Dialog.Root>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/dialog/dialog.tsx","../src/dialog/return-focus.ts","../src/match-dialog/match-dialog.tsx"],"sourcesContent":["// Client (a portal). Radix does the focus trap, the scroll lock, Escape, the outside click, aria-modal, the\n// aria-labelledby / aria-describedby wiring and the focus return: this file only holds the shape.\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { IconButton } from \"../button/button\";\nimport { useReturnFocus } from \"./return-focus\";\n\nexport const dialog = tv({\n slots: {\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n content:\n \"kz-dialog fixed z-50 flex flex-col overflow-hidden border border-border-default bg-surface-1 text-fg shadow-lg focus:outline-hidden\",\n close: \"absolute right-3 top-3 h-9 w-9\",\n header: \"flex flex-col gap-1 px-5 pb-2 pr-14 pt-5\",\n title: \"text-base font-bold text-fg\",\n description: \"text-sm text-fg-secondary\",\n body: \"min-h-0 flex-1 overflow-y-auto px-5 pb-5\",\n footer:\n \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle px-5 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]\",\n },\n variants: {\n placement: {\n center: { content: \"left-1/2 top-1/2 max-h-[85dvh] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl\" },\n /* A sheet: anchored at the bottom on a phone, centred from `sm` up. */\n bottom: {\n content:\n \"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[1.75rem] sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:w-full \" +\n \"sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.75rem]\",\n },\n right: { content: \"inset-y-0 right-0 h-full w-full max-w-md rounded-none border-y-0 border-r-0\" },\n /* A navigation drawer: full height, from the left, clear of the notch. */\n left: { content: \"inset-y-0 left-0 h-full w-full max-w-md rounded-none border-y-0 border-l-0 pl-[env(safe-area-inset-left)]\" },\n },\n size: { sm: { content: \"sm:max-w-sm\" }, md: { content: \"sm:max-w-md\" }, lg: { content: \"sm:max-w-2xl\" } },\n },\n defaultVariants: { placement: \"center\", size: \"md\" },\n});\nconst s = dialog();\n\nexport type DialogVariants = VariantProps<typeof dialog>;\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype CloseButtonProps =\n | {\n /** The accessible name of the close button — passed translated. */\n closeLabel: string;\n /** Leaves the close button out (the content brings its own way out, or none: see `dismissible`). */\n hideClose?: false;\n }\n | { closeLabel?: string; hideClose: true };\n\nexport type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &\n DialogVariants &\n CloseButtonProps & {\n /**\n * center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height) · left (a\n * navigation drawer, full height).\n * @default \"center\"\n */\n placement?: DialogVariants[\"placement\"];\n /**\n * sm · md · lg: the width of the dialog.\n * @default \"md\"\n */\n size?: DialogVariants[\"size\"];\n /**\n * `false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\n * through `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.\n */\n dismissible?: boolean;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n };\n\nconst prevent = (event: Event) => event.preventDefault();\n\n/** Radix's content, mounted only while open: it remembers the opener then and gives focus back on close (WCAG 2.4.3). */\nfunction ReturnFocusContent({ onCloseAutoFocus, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {\n return <DialogPrimitive.Content onCloseAutoFocus={useReturnFocus(onCloseAutoFocus)} {...props} />;\n}\n\n/** The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`. */\nexport function DialogContent({\n placement,\n size,\n closeLabel,\n hideClose,\n dismissible = true,\n container,\n className,\n children,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n ...props\n}: DialogContentProps) {\n const v = dialog({ placement, size });\n return (\n <DialogPrimitive.Portal container={container}>\n <DialogPrimitive.Overlay className={v.overlay()} />\n <ReturnFocusContent\n data-placement={placement ?? \"center\"}\n data-dismissible={dismissible ? undefined : \"false\"}\n className={v.content({ className })}\n onEscapeKeyDown={dismissible ? onEscapeKeyDown : chain(onEscapeKeyDown, prevent)}\n onPointerDownOutside={dismissible ? onPointerDownOutside : chain(onPointerDownOutside, prevent)}\n onInteractOutside={dismissible ? onInteractOutside : chain(onInteractOutside, prevent)}\n {...props}\n >\n {children}\n {!hideClose && (\n <DialogPrimitive.Close asChild>\n <IconButton label={closeLabel as string} variant=\"ghost\" shape=\"pill\" className={v.close()}>\n <CloseIcon />\n </IconButton>\n </DialogPrimitive.Close>\n )}\n </ReturnFocusContent>\n </DialogPrimitive.Portal>\n );\n}\n\nfunction chain<E extends Event>(handler: ((event: E) => void) | undefined, then: (event: E) => void) {\n return (event: E) => {\n handler?.(event);\n then(event);\n };\n}\n\nexport function DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.header({ className })} {...props} />;\n}\n\nexport function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return <DialogPrimitive.Title className={s.title({ className })} {...props} />;\n}\n\nexport function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return <DialogPrimitive.Description className={s.description({ className })} {...props} />;\n}\n\nexport function DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport function DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.footer({ className })} {...props} />;\n}\n\n/** `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Dialog = {\n Root: DialogPrimitive.Root,\n Trigger: DialogPrimitive.Trigger,\n Close: DialogPrimitive.Close,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n};\n\n/** `Omit` that keeps the close-button union (`closeLabel` required unless `hideClose`). */\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\nexport type SheetProps = DistributiveOmit<DialogContentProps, \"placement\">;\n\n/** A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`. */\nexport function Sheet(props: SheetProps) {\n return <DialogContent placement=\"bottom\" {...props} />;\n}\n","// Client. WCAG 2.4.3: when a dialog closes, focus goes back to what opened it. Radix only does it for `Dialog.Trigger`;\n// most app dialogs are controlled (`open` / `onOpenChange`) and opened by a plain button, so on Escape focus fell to\n// <body>. The content remembers the focused element when it mounts (it is still the opener: Radix moves focus in an\n// effect) and gives it back on close, unless the consumer's `onCloseAutoFocus` prevented the default.\nimport * as React from \"react\";\n\n/** The handler for `onCloseAutoFocus`: yours first, then focus back on the opener if it is still on the page. */\nexport function useReturnFocus(onCloseAutoFocus?: (event: Event) => void) {\n const [opener] = React.useState<Element | null>(() => (typeof document === \"undefined\" ? null : document.activeElement));\n return React.useCallback(\n (event: Event) => {\n onCloseAutoFocus?.(event);\n if (event.defaultPrevented) return;\n if (opener instanceof HTMLElement && opener !== document.body && opener.isConnected) {\n event.preventDefault();\n opener.focus();\n }\n },\n [opener, onCloseAutoFocus],\n );\n}\n","// Client (a portal). The moment two people choose each other — a match, a pair, a deal: a dialog in the brand\n// gradient, the two avatars meeting at a heart, the next step. The entrance plays once; still under reduced motion.\nimport * as React from \"react\";\n\nimport { Avatar } from \"../avatar\";\nimport { Button } from \"../button\";\nimport { Dialog } from \"../dialog\";\n\nexport type MatchDialogProps = {\n /** Shown (controlled). */\n open: boolean;\n /** Called when it closes. */\n onOpenChange: (open: boolean) => void;\n /** The headline (“It's a match!”) — passed translated; the dialog's name. */\n title: string;\n /** A line under it. */\n description?: string;\n /** The two people: avatar URLs and names. */\n people: readonly [{ src?: string | null; name: string }, { src?: string | null; name: string }];\n /** The main action (“Send a message”). */\n primaryLabel: string;\n /** What it does. */\n onPrimary: () => void;\n /** The way back (“Keep swiping”). */\n secondaryLabel: string;\n /** Where it is drawn (the page's body by default): give an element inside a brand scope (`.brand-…`) so the dialog wears the brand too. */\n container?: HTMLElement | null;\n};\n\n/** Two people choose each other: the avatars meet at a heart in the brand gradient, the next step under them. */\nexport function MatchDialog({ open, onOpenChange, title, description, people, primaryLabel, onPrimary, secondaryLabel, container }: MatchDialogProps) {\n return (\n <Dialog.Root open={open} onOpenChange={onOpenChange}>\n <Dialog.Content hideClose size=\"sm\" container={container} className=\"overflow-hidden border-0 bg-gradient-to-br from-accent to-accent-2 text-center text-on-accent\">\n <style href=\"krizaka-match\" precedence=\"default\">\n {\"@media (prefers-reduced-motion:no-preference){[data-kz-match] [data-side=a]{animation:kz-match-a .55s cubic-bezier(.3,1.6,.5,1) both}[data-kz-match] [data-side=b]{animation:kz-match-b .55s cubic-bezier(.3,1.6,.5,1) both}[data-kz-match] [data-heart]{animation:kz-match-heart .45s .25s cubic-bezier(.3,1.6,.5,1) both}}@keyframes kz-match-a{from{transform:translateX(-60px) rotate(-8deg);opacity:0}}@keyframes kz-match-b{from{transform:translateX(60px) rotate(8deg);opacity:0}}@keyframes kz-match-heart{from{transform:scale(0)}}\"}\n </style>\n <div data-kz-match=\"\" className=\"flex flex-col items-center gap-3 px-2 pb-2 pt-6\">\n <Dialog.Title className=\"font-display text-4xl font-black text-on-accent\">{title}</Dialog.Title>\n {description ? <Dialog.Description className=\"max-w-xs text-sm text-on-accent\">{description}</Dialog.Description> : null}\n <div className=\"relative my-6 flex h-36 w-64 items-center justify-center\" aria-hidden>\n <span data-side=\"a\" className=\"absolute left-6 -rotate-6 rounded-full ring-4 ring-on-accent\">\n <Avatar src={people[0].src ?? null} alt=\"\" fallback={people[0].name.charAt(0)} className=\"h-28 w-28 text-3xl\" />\n </span>\n <span data-side=\"b\" className=\"absolute right-6 rotate-6 rounded-full ring-4 ring-on-accent\">\n <Avatar src={people[1].src ?? null} alt=\"\" fallback={people[1].name.charAt(0)} className=\"h-28 w-28 text-3xl\" />\n </span>\n <span data-heart=\"\" className=\"absolute -bottom-1 flex h-12 w-12 items-center justify-center rounded-full bg-on-accent text-accent shadow-lg\">\n <svg viewBox=\"0 0 24 24\" className=\"h-6 w-6\" fill=\"currentColor\">\n <path d=\"M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z\" />\n </svg>\n </span>\n </div>\n <div className=\"flex w-full flex-col gap-2\">\n <Button\n size=\"lg\"\n onClick={() => {\n onOpenChange(false);\n onPrimary();\n }}\n >\n {primaryLabel}\n </Button>\n <Dialog.Close asChild>\n <Button size=\"lg\" variant=\"ghost\" className=\"text-on-accent hover:bg-on-accent/15\">\n {secondaryLabel}\n </Button>\n </Dialog.Close>\n </div>\n </div>\n </Dialog.Content>\n </Dialog.Root>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -0,0 +1,102 @@
1
+ import * as React from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+
4
+ interface VerticalVideoLabels {
5
+ play: string;
6
+ pause: string;
7
+ like: string;
8
+ comment: string;
9
+ share: string;
10
+ more: string;
11
+ /** "Follow {name}". */
12
+ follow: string;
13
+ /** "Following {name}". */
14
+ following: string;
15
+ expand: string;
16
+ collapse: string;
17
+ progress: string;
18
+ /** The rail's name ("Actions"). */
19
+ actions: string;
20
+ }
21
+ declare const verticalVideoLabels: VerticalVideoLabels;
22
+ type VerticalVideoProps = {
23
+ /** The video's accessible name ("Video: Night shoot, part 2"), passed translated. */
24
+ title: string;
25
+ /** The clip's address (MP4 or HLS). Absent, the poster alone shows. */
26
+ uri?: string;
27
+ /** The picture until the first frame. */
28
+ posterUri?: string;
29
+ /** Plays while `true` (a `ShortFeed` passes it for the slide in view). Default `true`. */
30
+ active?: boolean;
31
+ /** Who made it: name, portrait, the seal's words. */
32
+ creator: {
33
+ name: string;
34
+ avatarUri?: string;
35
+ verifiedLabel?: string;
36
+ };
37
+ /** A pill beside the name ("AI-GENERATED"). */
38
+ disclosure?: string;
39
+ /** The caption, two lines until expanded. */
40
+ caption?: string;
41
+ /** Tags after the caption (without `#`). */
42
+ tags?: readonly string[];
43
+ /** The sound or credit line. */
44
+ credit?: string;
45
+ /** The like count, compact in `locale`. */
46
+ likes?: number;
47
+ /** The comment count. */
48
+ comments?: number;
49
+ /** The share count. */
50
+ shares?: number;
51
+ /** Whether the viewer likes it. */
52
+ liked?: boolean;
53
+ /** Whether the viewer follows the creator. */
54
+ following?: boolean;
55
+ /** The viewer likes or unlikes. */
56
+ onLike?: (liked: boolean) => void;
57
+ /** Opens the comments. */
58
+ onComment?: () => void;
59
+ /** Opens the share sheet. */
60
+ onShare?: () => void;
61
+ /** Follows or unfollows; without it the avatar has no "+". */
62
+ onFollow?: (following: boolean) => void;
63
+ /** The "more" action; without it the button is not drawn. */
64
+ onMore?: () => void;
65
+ /** The product's own action on the rail (a tip, an unlock): a named, 44 pt button of yours. */
66
+ tipSlot?: React.ReactNode;
67
+ /** Top left over the picture: a chip (an auction's time left). */
68
+ topStart?: React.ReactNode;
69
+ /** Above the caption: a goal gauge. */
70
+ aboveCaption?: React.ReactNode;
71
+ /** The locale of the counts. Default `en`. */
72
+ locale?: string;
73
+ /** Every word, passed translated. */
74
+ labels?: Partial<VerticalVideoLabels>;
75
+ /** Styles merged last, on the frame. */
76
+ style?: StyleProp<ViewStyle>;
77
+ };
78
+ /** One short video: 9:16, the rail of actions on the right, the creator's caption bottom-left. */
79
+ declare function VerticalVideo({ title, uri, posterUri, active, creator, disclosure, caption, tags, credit, likes, comments, shares, liked, following, onLike, onComment, onShare, onFollow, onMore, tipSlot, topStart, aboveCaption, locale, labels: custom, style, }: VerticalVideoProps): React.JSX.Element;
80
+ type ShortFeedProps<T> = {
81
+ /** The feed's accessible name ("Videos"). */
82
+ label: string;
83
+ /** The videos, in order. */
84
+ items: readonly T[];
85
+ /** A stable key for an item. */
86
+ getId: (item: T) => string;
87
+ /** One slide: `active` is true for the one in view. */
88
+ renderItem: (item: T, state: {
89
+ active: boolean;
90
+ index: number;
91
+ }) => React.ReactElement;
92
+ /** The page's height in points (the feed's own height; one slide per page). */
93
+ height: number;
94
+ /** Called when the slide in view changes. */
95
+ onActiveChange?: (index: number, item: T) => void;
96
+ /** Styles merged last. */
97
+ style?: StyleProp<ViewStyle>;
98
+ };
99
+ /** A vertical paging feed: one video per screen, the one in view plays, the others pause. */
100
+ declare function ShortFeed<T>({ label, items, getId, renderItem, height, onActiveChange, style }: ShortFeedProps<T>): React.JSX.Element;
101
+
102
+ export { ShortFeed, type ShortFeedProps, VerticalVideo, type VerticalVideoLabels, type VerticalVideoProps, verticalVideoLabels };
@@ -0,0 +1,163 @@
1
+ // src/native/vertical-video.tsx
2
+ import { formatCompact } from "@krizaka/intl";
3
+ import { Avatar, Txt, useTheme, VerifiedBadge } from "@krizaka/ui/native";
4
+ import { VideoPlayer } from "@krizaka/ui/native/video-player";
5
+ import * as React from "react";
6
+ import { FlatList, Image, Pressable, StyleSheet, View } from "react-native";
7
+ import Svg, { Path } from "react-native-svg";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ var verticalVideoLabels = {
10
+ play: "Play",
11
+ pause: "Pause",
12
+ like: "Like",
13
+ comment: "Comments",
14
+ share: "Share",
15
+ more: "More",
16
+ follow: "Follow {name}",
17
+ following: "Following {name}",
18
+ expand: "More",
19
+ collapse: "Less",
20
+ progress: "Progress",
21
+ actions: "Actions"
22
+ };
23
+ var HEART = "M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z";
24
+ var COMMENT = "M4 5h16v11H9l-5 4z";
25
+ var SHARE = "M4 12v7h16v-7M12 3v12M7 8l5-5 5 5";
26
+ var MORE = "M5 12h.01M12 12h.01M19 12h.01";
27
+ function Glyph({ d, color, filled }) {
28
+ return /* @__PURE__ */ jsx(Svg, { width: 24, height: 24, viewBox: "0 0 24 24", fill: filled ? color : "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx(Path, { d }) });
29
+ }
30
+ function RailButton({ label, count, pressed, onPress, children }) {
31
+ const { theme } = useTheme();
32
+ return /* @__PURE__ */ jsxs(View, { style: styles.railItem, children: [
33
+ /* @__PURE__ */ jsx(Pressable, { role: "button", "aria-label": label, "aria-pressed": pressed, onPress, hitSlop: 4, style: [styles.railButton, { backgroundColor: theme.scrim }], children }),
34
+ count !== void 0 ? /* @__PURE__ */ jsx(Txt, { variant: "caption", tone: "onMedia", style: styles.count, children: count }) : null
35
+ ] });
36
+ }
37
+ function VerticalVideo({
38
+ title,
39
+ uri,
40
+ posterUri,
41
+ active = true,
42
+ creator,
43
+ disclosure,
44
+ caption,
45
+ tags = [],
46
+ credit,
47
+ likes,
48
+ comments,
49
+ shares,
50
+ liked = false,
51
+ following = false,
52
+ onLike,
53
+ onComment,
54
+ onShare,
55
+ onFollow,
56
+ onMore,
57
+ tipSlot,
58
+ topStart,
59
+ aboveCaption,
60
+ locale = "en",
61
+ labels: custom,
62
+ style
63
+ }) {
64
+ const { theme } = useTheme();
65
+ const labels = { ...verticalVideoLabels, ...custom };
66
+ const [open, setOpen] = React.useState(false);
67
+ const [paused, setPaused] = React.useState(false);
68
+ const count = (n) => n === void 0 ? void 0 : formatCompact(n, { locale });
69
+ const on = theme.textOnMedia;
70
+ return /* @__PURE__ */ jsxs(View, { role: "group", "aria-label": title, style: [styles.frame, { backgroundColor: theme.media }, style], children: [
71
+ uri ? /* @__PURE__ */ jsx(VideoPlayer, { source: { uri }, title, aspectRatio: 9 / 16, contentFit: "cover", nativeControls: false, fullscreen: false, autoPlay: true, loop: true, muted: true, paused: !active || paused, style: StyleSheet.absoluteFill, ...posterUri ? { poster: { uri: posterUri } } : {} }) : posterUri ? /* @__PURE__ */ jsx(Image, { source: { uri: posterUri }, "aria-hidden": true, resizeMode: "cover", style: StyleSheet.absoluteFill }) : null,
72
+ /* @__PURE__ */ jsx(Pressable, { role: "button", "aria-label": paused ? labels.play : labels.pause, onPress: () => setPaused((p) => !p), onLongPress: () => onLike?.(!liked), style: StyleSheet.absoluteFill }),
73
+ /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: [styles.shade, { backgroundColor: theme.scrim }] }),
74
+ topStart ? /* @__PURE__ */ jsx(View, { style: styles.topStart, children: topStart }) : null,
75
+ /* @__PURE__ */ jsxs(View, { role: "group", "aria-label": labels.actions, style: styles.rail, children: [
76
+ /* @__PURE__ */ jsxs(View, { children: [
77
+ /* @__PURE__ */ jsx(Avatar, { alt: creator.name, ...creator.avatarUri ? { src: creator.avatarUri } : {}, size: "md" }),
78
+ onFollow ? /* @__PURE__ */ jsx(
79
+ Pressable,
80
+ {
81
+ role: "button",
82
+ "aria-label": (following ? labels.following : labels.follow).replace("{name}", creator.name),
83
+ "aria-pressed": following,
84
+ onPress: () => onFollow(!following),
85
+ hitSlop: 12,
86
+ style: [styles.follow, { backgroundColor: following ? theme.surface2 : theme.accent }],
87
+ children: /* @__PURE__ */ jsx(Glyph, { d: following ? "M5 12l5 5 9-10" : "M12 5v14M5 12h14", color: following ? theme.textPrimary : theme.onAccent })
88
+ }
89
+ ) : null
90
+ ] }),
91
+ /* @__PURE__ */ jsx(RailButton, { label: labels.like, count: count(likes), pressed: liked, onPress: () => onLike?.(!liked), children: /* @__PURE__ */ jsx(Glyph, { d: HEART, color: liked ? theme.accent : on, filled: liked }) }),
92
+ /* @__PURE__ */ jsx(RailButton, { label: labels.comment, count: count(comments), onPress: onComment, children: /* @__PURE__ */ jsx(Glyph, { d: COMMENT, color: on }) }),
93
+ /* @__PURE__ */ jsx(RailButton, { label: labels.share, count: count(shares), onPress: onShare, children: /* @__PURE__ */ jsx(Glyph, { d: SHARE, color: on }) }),
94
+ tipSlot,
95
+ onMore ? /* @__PURE__ */ jsx(RailButton, { label: labels.more, onPress: onMore, children: /* @__PURE__ */ jsx(Glyph, { d: MORE, color: on }) }) : null
96
+ ] }),
97
+ /* @__PURE__ */ jsxs(View, { style: styles.caption, children: [
98
+ aboveCaption,
99
+ /* @__PURE__ */ jsxs(View, { style: styles.nameRow, children: [
100
+ /* @__PURE__ */ jsx(Txt, { variant: "label", tone: "onMedia", children: creator.name }),
101
+ creator.verifiedLabel ? /* @__PURE__ */ jsx(VerifiedBadge, { label: creator.verifiedLabel, on: "media", size: 14 }) : null,
102
+ disclosure ? /* @__PURE__ */ jsx(View, { style: [styles.pill, { backgroundColor: theme.scrim }], children: /* @__PURE__ */ jsx(Txt, { variant: "caption", tone: "onMedia", style: styles.pillText, children: disclosure.toUpperCase() }) }) : null
103
+ ] }),
104
+ caption ? /* @__PURE__ */ jsxs(Txt, { tone: "onMedia", numberOfLines: open ? void 0 : 2, children: [
105
+ caption,
106
+ tags.length > 0 ? ` ${tags.map((tag) => `#${tag}`).join(" ")}` : ""
107
+ ] }) : null,
108
+ caption && caption.length > 90 ? /* @__PURE__ */ jsx(Pressable, { role: "button", "aria-label": open ? labels.collapse : labels.expand, "aria-expanded": open, onPress: () => setOpen((v) => !v), hitSlop: 10, children: /* @__PURE__ */ jsx(Txt, { variant: "caption", tone: "onMedia", style: styles.underline, children: open ? labels.collapse : labels.expand }) }) : null,
109
+ credit ? /* @__PURE__ */ jsx(Txt, { variant: "caption", tone: "onMedia", children: credit }) : null
110
+ ] })
111
+ ] });
112
+ }
113
+ function ShortFeed({ label, items, getId, renderItem, height, onActiveChange, style }) {
114
+ const [active, setActive] = React.useState(0);
115
+ const [onViewable] = React.useState(() => ({ viewableItems }) => {
116
+ const first = viewableItems.find((v) => v.isViewable && typeof v.index === "number");
117
+ if (first && typeof first.index === "number") setActive(first.index);
118
+ });
119
+ React.useEffect(() => {
120
+ const item = items[active];
121
+ if (item !== void 0) onActiveChange?.(active, item);
122
+ }, [active]);
123
+ return /* @__PURE__ */ jsx(View, { role: "list", "aria-label": label, style: [{ height }, style], children: /* @__PURE__ */ jsx(
124
+ FlatList,
125
+ {
126
+ data: items,
127
+ keyExtractor: getId,
128
+ pagingEnabled: true,
129
+ showsVerticalScrollIndicator: false,
130
+ snapToInterval: height,
131
+ decelerationRate: "fast",
132
+ getItemLayout: (_, index) => ({ length: height, offset: height * index, index }),
133
+ windowSize: 3,
134
+ initialNumToRender: 2,
135
+ viewabilityConfig: { itemVisiblePercentThreshold: 60 },
136
+ onViewableItemsChanged: onViewable,
137
+ extraData: active,
138
+ style: { height },
139
+ renderItem: ({ item, index }) => /* @__PURE__ */ jsx(View, { style: { height }, children: renderItem(item, { active: index === active, index }) })
140
+ }
141
+ ) });
142
+ }
143
+ var styles = StyleSheet.create({
144
+ frame: { width: "100%", aspectRatio: 9 / 16, overflow: "hidden" },
145
+ shade: { position: "absolute", left: 0, right: 0, bottom: 0, height: "30%", opacity: 0.55 },
146
+ topStart: { position: "absolute", left: 12, top: 12 },
147
+ rail: { position: "absolute", right: 8, bottom: 20, alignItems: "center", gap: 12 },
148
+ railItem: { alignItems: "center", gap: 2 },
149
+ railButton: { width: 44, height: 44, borderRadius: 22, alignItems: "center", justifyContent: "center" },
150
+ count: { fontVariant: ["tabular-nums"], fontWeight: "600" },
151
+ follow: { position: "absolute", bottom: -10, alignSelf: "center", width: 22, height: 22, borderRadius: 11, alignItems: "center", justifyContent: "center", overflow: "hidden" },
152
+ caption: { position: "absolute", left: 12, right: 76, bottom: 16, gap: 6 },
153
+ nameRow: { flexDirection: "row", alignItems: "center", gap: 6 },
154
+ pill: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999 },
155
+ pillText: { fontWeight: "700", letterSpacing: 0.4 },
156
+ underline: { textDecorationLine: "underline", fontWeight: "600" }
157
+ });
158
+ export {
159
+ ShortFeed,
160
+ VerticalVideo,
161
+ verticalVideoLabels
162
+ };
163
+ //# sourceMappingURL=vertical-video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/native/vertical-video.tsx"],"sourcesContent":["// @krizaka/ui/native/vertical-video — its own entry (it plays video on expo-video, an optional peer). A 9:16 clip filling\n// its frame, the action rail on the right (avatar + follow, like, comments, share, a slot for the product's tip), the\n// creator's caption bottom-left, a progress hairline; `ShortFeed` pages them vertically and plays the one in view.\n// Over the picture everything reads on the theme's invariant `scrim` / `textOnMedia`.\nimport { formatCompact } from \"@krizaka/intl\";\nimport { Avatar, Txt, useTheme, VerifiedBadge } from \"@krizaka/ui/native\";\nimport { VideoPlayer } from \"@krizaka/ui/native/video-player\";\nimport * as React from \"react\";\nimport { FlatList, Image,Pressable, type StyleProp, StyleSheet, View, type ViewStyle, type ViewToken } from \"react-native\";\nimport Svg, { Path } from \"react-native-svg\";\n\nexport interface VerticalVideoLabels {\n play: string;\n pause: string;\n like: string;\n comment: string;\n share: string;\n more: string;\n /** \"Follow {name}\". */\n follow: string;\n /** \"Following {name}\". */\n following: string;\n expand: string;\n collapse: string;\n progress: string;\n /** The rail's name (\"Actions\"). */\n actions: string;\n}\n\nexport const verticalVideoLabels: VerticalVideoLabels = {\n play: \"Play\",\n pause: \"Pause\",\n like: \"Like\",\n comment: \"Comments\",\n share: \"Share\",\n more: \"More\",\n follow: \"Follow {name}\",\n following: \"Following {name}\",\n expand: \"More\",\n collapse: \"Less\",\n progress: \"Progress\",\n actions: \"Actions\",\n};\n\nexport type VerticalVideoProps = {\n /** The video's accessible name (\"Video: Night shoot, part 2\"), passed translated. */\n title: string;\n /** The clip's address (MP4 or HLS). Absent, the poster alone shows. */\n uri?: string;\n /** The picture until the first frame. */\n posterUri?: string;\n /** Plays while `true` (a `ShortFeed` passes it for the slide in view). Default `true`. */\n active?: boolean;\n /** Who made it: name, portrait, the seal's words. */\n creator: { name: string; avatarUri?: string; verifiedLabel?: string };\n /** A pill beside the name (\"AI-GENERATED\"). */\n disclosure?: string;\n /** The caption, two lines until expanded. */\n caption?: string;\n /** Tags after the caption (without `#`). */\n tags?: readonly string[];\n /** The sound or credit line. */\n credit?: string;\n /** The like count, compact in `locale`. */\n likes?: number;\n /** The comment count. */\n comments?: number;\n /** The share count. */\n shares?: number;\n /** Whether the viewer likes it. */\n liked?: boolean;\n /** Whether the viewer follows the creator. */\n following?: boolean;\n /** The viewer likes or unlikes. */\n onLike?: (liked: boolean) => void;\n /** Opens the comments. */\n onComment?: () => void;\n /** Opens the share sheet. */\n onShare?: () => void;\n /** Follows or unfollows; without it the avatar has no \"+\". */\n onFollow?: (following: boolean) => void;\n /** The \"more\" action; without it the button is not drawn. */\n onMore?: () => void;\n /** The product's own action on the rail (a tip, an unlock): a named, 44 pt button of yours. */\n tipSlot?: React.ReactNode;\n /** Top left over the picture: a chip (an auction's time left). */\n topStart?: React.ReactNode;\n /** Above the caption: a goal gauge. */\n aboveCaption?: React.ReactNode;\n /** The locale of the counts. Default `en`. */\n locale?: string;\n /** Every word, passed translated. */\n labels?: Partial<VerticalVideoLabels>;\n /** Styles merged last, on the frame. */\n style?: StyleProp<ViewStyle>;\n};\n\nconst HEART = \"M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z\";\nconst COMMENT = \"M4 5h16v11H9l-5 4z\";\nconst SHARE = \"M4 12v7h16v-7M12 3v12M7 8l5-5 5 5\";\nconst MORE = \"M5 12h.01M12 12h.01M19 12h.01\";\n\nfunction Glyph({ d, color, filled }: { d: string; color: string; filled?: boolean }) {\n return (\n <Svg width={24} height={24} viewBox=\"0 0 24 24\" fill={filled ? color : \"none\"} stroke={color} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <Path d={d} />\n </Svg>\n );\n}\n\nfunction RailButton({ label, count, pressed, onPress, children }: { label: string; count?: string | undefined; pressed?: boolean | undefined; onPress?: (() => void) | undefined; children: React.ReactNode }) {\n const { theme } = useTheme();\n return (\n <View style={styles.railItem}>\n <Pressable role=\"button\" aria-label={label} aria-pressed={pressed} onPress={onPress} hitSlop={4} style={[styles.railButton, { backgroundColor: theme.scrim }]}>\n {children}\n </Pressable>\n {count !== undefined ? (\n <Txt variant=\"caption\" tone=\"onMedia\" style={styles.count}>\n {count}\n </Txt>\n ) : null}\n </View>\n );\n}\n\n/** One short video: 9:16, the rail of actions on the right, the creator's caption bottom-left. */\nexport function VerticalVideo({\n title,\n uri,\n posterUri,\n active = true,\n creator,\n disclosure,\n caption,\n tags = [],\n credit,\n likes,\n comments,\n shares,\n liked = false,\n following = false,\n onLike,\n onComment,\n onShare,\n onFollow,\n onMore,\n tipSlot,\n topStart,\n aboveCaption,\n locale = \"en\",\n labels: custom,\n style,\n}: VerticalVideoProps) {\n const { theme } = useTheme();\n const labels = { ...verticalVideoLabels, ...custom };\n const [open, setOpen] = React.useState(false);\n const [paused, setPaused] = React.useState(false);\n const count = (n?: number) => (n === undefined ? undefined : formatCompact(n, { locale }));\n const on = theme.textOnMedia;\n return (\n <View role=\"group\" aria-label={title} style={[styles.frame, { backgroundColor: theme.media }, style]}>\n {uri ? (\n <VideoPlayer source={{ uri }} title={title} aspectRatio={9 / 16} contentFit=\"cover\" nativeControls={false} fullscreen={false} autoPlay loop muted paused={!active || paused} style={StyleSheet.absoluteFill} {...(posterUri ? { poster: { uri: posterUri } } : {})} />\n ) : posterUri ? (\n <Image source={{ uri: posterUri }} aria-hidden resizeMode=\"cover\" style={StyleSheet.absoluteFill} />\n ) : null}\n <Pressable role=\"button\" aria-label={paused ? labels.play : labels.pause} onPress={() => setPaused((p) => !p)} onLongPress={() => onLike?.(!liked)} style={StyleSheet.absoluteFill} />\n <View pointerEvents=\"none\" style={[styles.shade, { backgroundColor: theme.scrim }]} />\n {topStart ? <View style={styles.topStart}>{topStart}</View> : null}\n\n <View role=\"group\" aria-label={labels.actions} style={styles.rail}>\n <View>\n <Avatar alt={creator.name} {...(creator.avatarUri ? { src: creator.avatarUri } : {})} size=\"md\" />\n {onFollow ? (\n <Pressable\n role=\"button\"\n aria-label={(following ? labels.following : labels.follow).replace(\"{name}\", creator.name)}\n aria-pressed={following}\n onPress={() => onFollow(!following)}\n hitSlop={12}\n style={[styles.follow, { backgroundColor: following ? theme.surface2 : theme.accent }]}\n >\n <Glyph d={following ? \"M5 12l5 5 9-10\" : \"M12 5v14M5 12h14\"} color={following ? theme.textPrimary : theme.onAccent} />\n </Pressable>\n ) : null}\n </View>\n <RailButton label={labels.like} count={count(likes)} pressed={liked} onPress={() => onLike?.(!liked)}>\n <Glyph d={HEART} color={liked ? theme.accent : on} filled={liked} />\n </RailButton>\n <RailButton label={labels.comment} count={count(comments)} onPress={onComment}>\n <Glyph d={COMMENT} color={on} />\n </RailButton>\n <RailButton label={labels.share} count={count(shares)} onPress={onShare}>\n <Glyph d={SHARE} color={on} />\n </RailButton>\n {tipSlot}\n {onMore ? (\n <RailButton label={labels.more} onPress={onMore}>\n <Glyph d={MORE} color={on} />\n </RailButton>\n ) : null}\n </View>\n\n <View style={styles.caption}>\n {aboveCaption}\n <View style={styles.nameRow}>\n <Txt variant=\"label\" tone=\"onMedia\">\n {creator.name}\n </Txt>\n {creator.verifiedLabel ? <VerifiedBadge label={creator.verifiedLabel} on=\"media\" size={14} /> : null}\n {disclosure ? (\n <View style={[styles.pill, { backgroundColor: theme.scrim }]}>\n <Txt variant=\"caption\" tone=\"onMedia\" style={styles.pillText}>\n {disclosure.toUpperCase()}\n </Txt>\n </View>\n ) : null}\n </View>\n {caption ? (\n <Txt tone=\"onMedia\" numberOfLines={open ? undefined : 2}>\n {caption}\n {tags.length > 0 ? ` ${tags.map((tag) => `#${tag}`).join(\" \")}` : \"\"}\n </Txt>\n ) : null}\n {caption && caption.length > 90 ? (\n <Pressable role=\"button\" aria-label={open ? labels.collapse : labels.expand} aria-expanded={open} onPress={() => setOpen((v) => !v)} hitSlop={10}>\n <Txt variant=\"caption\" tone=\"onMedia\" style={styles.underline}>\n {open ? labels.collapse : labels.expand}\n </Txt>\n </Pressable>\n ) : null}\n {credit ? (\n <Txt variant=\"caption\" tone=\"onMedia\">\n {credit}\n </Txt>\n ) : null}\n </View>\n </View>\n );\n}\n\nexport type ShortFeedProps<T> = {\n /** The feed's accessible name (\"Videos\"). */\n label: string;\n /** The videos, in order. */\n items: readonly T[];\n /** A stable key for an item. */\n getId: (item: T) => string;\n /** One slide: `active` is true for the one in view. */\n renderItem: (item: T, state: { active: boolean; index: number }) => React.ReactElement;\n /** The page's height in points (the feed's own height; one slide per page). */\n height: number;\n /** Called when the slide in view changes. */\n onActiveChange?: (index: number, item: T) => void;\n /** Styles merged last. */\n style?: StyleProp<ViewStyle>;\n};\n\n/** A vertical paging feed: one video per screen, the one in view plays, the others pause. */\nexport function ShortFeed<T>({ label, items, getId, renderItem, height, onActiveChange, style }: ShortFeedProps<T>) {\n const [active, setActive] = React.useState(0);\n // FlatList wants one stable callback for its whole life.\n const [onViewable] = React.useState(() => ({ viewableItems }: { viewableItems: ViewToken[] }) => {\n const first = viewableItems.find((v) => v.isViewable && typeof v.index === \"number\");\n if (first && typeof first.index === \"number\") setActive(first.index);\n });\n React.useEffect(() => {\n const item = items[active];\n if (item !== undefined) onActiveChange?.(active, item);\n // Only a change of the slide in view is an event.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active]);\n return (\n <View role=\"list\" aria-label={label} style={[{ height }, style]}>\n <FlatList\n data={items as T[]}\n keyExtractor={getId}\n pagingEnabled\n showsVerticalScrollIndicator={false}\n snapToInterval={height}\n decelerationRate=\"fast\"\n getItemLayout={(_, index) => ({ length: height, offset: height * index, index })}\n windowSize={3}\n initialNumToRender={2}\n viewabilityConfig={{ itemVisiblePercentThreshold: 60 }}\n onViewableItemsChanged={onViewable}\n extraData={active}\n style={{ height }}\n renderItem={({ item, index }) => <View style={{ height }}>{renderItem(item, { active: index === active, index })}</View>}\n />\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n frame: { width: \"100%\", aspectRatio: 9 / 16, overflow: \"hidden\" },\n shade: { position: \"absolute\", left: 0, right: 0, bottom: 0, height: \"30%\", opacity: 0.55 },\n topStart: { position: \"absolute\", left: 12, top: 12 },\n rail: { position: \"absolute\", right: 8, bottom: 20, alignItems: \"center\", gap: 12 },\n railItem: { alignItems: \"center\", gap: 2 },\n railButton: { width: 44, height: 44, borderRadius: 22, alignItems: \"center\", justifyContent: \"center\" },\n count: { fontVariant: [\"tabular-nums\"], fontWeight: \"600\" },\n follow: { position: \"absolute\", bottom: -10, alignSelf: \"center\", width: 22, height: 22, borderRadius: 11, alignItems: \"center\", justifyContent: \"center\", overflow: \"hidden\" },\n caption: { position: \"absolute\", left: 12, right: 76, bottom: 16, gap: 6 },\n nameRow: { flexDirection: \"row\", alignItems: \"center\", gap: 6 },\n pill: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999 },\n pillText: { fontWeight: \"700\", letterSpacing: 0.4 },\n underline: { textDecorationLine: \"underline\", fontWeight: \"600\" },\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,112 @@
1
+ import * as React from 'react';
2
+
3
+ interface ShortFeedProps<T> extends Omit<React.ComponentProps<"section">, "children"> {
4
+ /** The feed's accessible name ("Videos"), passed translated. */
5
+ label: string;
6
+ /** The videos, in order. */
7
+ items: readonly T[];
8
+ /** A stable key for an item. */
9
+ getId: (item: T) => string;
10
+ /** One slide: `active` is true for the one in view, `preload` for the next, `near` false for the ones kept empty. */
11
+ renderItem: (item: T, state: {
12
+ active: boolean;
13
+ preload: "none" | "metadata" | "auto";
14
+ index: number;
15
+ }) => React.ReactNode;
16
+ /** The slides mounted around the one in view (before and after). Default 1; the others keep their height empty. */
17
+ window?: number;
18
+ /** Called when the slide in view changes. */
19
+ onActiveChange?: (index: number, item: T) => void;
20
+ }
21
+ /** A vertical snap-scrolling feed that plays the video in view, preloads the next and pauses the rest. */
22
+ declare function ShortFeed<T>({ label, items, getId, renderItem, window: around, onActiveChange, className, onKeyDown, ...props }: ShortFeedProps<T>): React.JSX.Element;
23
+
24
+ interface VerticalVideoLabels {
25
+ /** "Play" / "Pause" — the tap target over the picture. */
26
+ play: string;
27
+ pause: string;
28
+ /** "Like" / "Unlike" (the button's name; the state is `aria-pressed`). */
29
+ like: string;
30
+ comment: string;
31
+ share: string;
32
+ more: string;
33
+ /** "Follow Elena" — `{name}` is replaced by the creator's name. */
34
+ follow: string;
35
+ /** "Following Elena". */
36
+ following: string;
37
+ /** "More" / "Less" on a long caption. */
38
+ expand: string;
39
+ collapse: string;
40
+ /** The progress bar's name ("Progress"). */
41
+ progress: string;
42
+ /** The rail's name ("Actions"). */
43
+ actions: string;
44
+ }
45
+ declare const verticalVideoLabels: VerticalVideoLabels;
46
+ interface VerticalVideoCreator {
47
+ name: string;
48
+ /** Their portrait. */
49
+ avatarSrc?: string;
50
+ /** The seal's words ("Verified"): shows the seal beside the name. */
51
+ verifiedLabel?: string;
52
+ }
53
+ interface VerticalVideoProps extends Omit<React.ComponentProps<"article">, "title" | "children"> {
54
+ /** The video's accessible name ("Video: Night shoot, part 2"), passed translated. */
55
+ title: string;
56
+ /** The clip — absent, the poster alone shows (MP4 or WebM; an HLS address only in Safari — use the player of `@krizaka/ui/video-player` for HLS). */
57
+ src?: string;
58
+ /** The picture until the first frame, and under reduced motion. */
59
+ poster?: string;
60
+ /** Plays while `true`; a `ShortFeed` passes it for the slide in view. Default `true`. */
61
+ active?: boolean;
62
+ /** The next slide's: `auto` loads it ahead, `none` waits. Default `metadata`. */
63
+ preload?: "none" | "metadata" | "auto";
64
+ /** Plays again at the end. Default `true`. */
65
+ loop?: boolean;
66
+ /** Starts muted (autoplay needs it). Default `true`. */
67
+ muted?: boolean;
68
+ /** Who made it: name, portrait, the seal's words. */
69
+ creator: VerticalVideoCreator;
70
+ /** A pill beside the name: "AI-GENERATED" — said on the picture, never only in a page's footnote. */
71
+ disclosure?: string;
72
+ /** The caption: collapsed to two lines, expandable when long. */
73
+ caption?: string;
74
+ /** Tags under the caption (without `#`). */
75
+ tags?: readonly string[];
76
+ /** The sound or credit line ("Original sound — Elena"). */
77
+ credit?: string;
78
+ /** The like count under its button, compact (`12.4K`) in `locale`. */
79
+ likes?: number;
80
+ /** The comment count under its button. */
81
+ comments?: number;
82
+ /** The share count under its button. */
83
+ shares?: number;
84
+ /** Whether the viewer likes it: `aria-pressed` on the like button. */
85
+ liked?: boolean;
86
+ /** Whether the viewer follows the creator: the "+" on the avatar becomes a check. */
87
+ following?: boolean;
88
+ /** The viewer likes or unlikes (the button, `L`, a double tap). */
89
+ onLike?: (liked: boolean) => void;
90
+ /** Opens the comments; without it the button still shows. */
91
+ onComment?: () => void;
92
+ /** Opens the share sheet. */
93
+ onShare?: () => void;
94
+ /** The viewer follows or unfollows; without it the avatar has no "+". */
95
+ onFollow?: (following: boolean) => void;
96
+ /** The "more" button's action (report, hide); without it the button is not drawn. */
97
+ onMore?: () => void;
98
+ /** The product's own action on the rail: a tip, an unlock — a button of yours (≥ 44 px, named). */
99
+ tipSlot?: React.ReactNode;
100
+ /** Top left: a chip over the picture (an auction's time left). */
101
+ topStart?: React.ReactNode;
102
+ /** Above the caption: a goal gauge, a challenge's progress. */
103
+ aboveCaption?: React.ReactNode;
104
+ /** The locale of the counts. Default `en`. */
105
+ locale?: string;
106
+ /** Every word, passed translated. */
107
+ labels?: Partial<VerticalVideoLabels>;
108
+ }
109
+ /** One short video: 9:16, the rail of actions on the right, the creator's caption bottom-left, a progress hairline. */
110
+ declare function VerticalVideo({ title, src, poster, active, preload, loop, muted, creator, disclosure, caption, tags, credit, likes, comments, shares, liked, following, onLike, onComment, onShare, onFollow, onMore, tipSlot, topStart, aboveCaption, locale, labels: custom, className, onKeyDown, ...props }: VerticalVideoProps): React.JSX.Element;
111
+
112
+ export { ShortFeed, type ShortFeedProps, VerticalVideo, type VerticalVideoCreator, type VerticalVideoLabels, type VerticalVideoProps, verticalVideoLabels };