@jotis-me/react 0.0.0-stage → 0.1.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 (179) hide show
  1. package/README.md +77 -2
  2. package/dist/components/ActivityFeed.d.ts +29 -0
  3. package/dist/components/ActivityFeed.js +10 -0
  4. package/dist/components/Alert.d.ts +34 -0
  5. package/dist/components/Alert.js +27 -0
  6. package/dist/components/AppIcon.d.ts +27 -0
  7. package/dist/components/AppIcon.js +9 -0
  8. package/dist/components/Avatar.d.ts +20 -0
  9. package/dist/components/Avatar.js +27 -0
  10. package/dist/components/AvatarGroup.d.ts +10 -0
  11. package/dist/components/AvatarGroup.js +7 -0
  12. package/dist/components/AvatarLabel.d.ts +10 -0
  13. package/dist/components/AvatarLabel.js +7 -0
  14. package/dist/components/BackgroundPattern.d.ts +35 -0
  15. package/dist/components/BackgroundPattern.js +73 -0
  16. package/dist/components/Badge.d.ts +38 -0
  17. package/dist/components/Badge.js +82 -0
  18. package/dist/components/Banner.d.ts +48 -0
  19. package/dist/components/Banner.js +60 -0
  20. package/dist/components/BarChart.d.ts +25 -0
  21. package/dist/components/BarChart.js +30 -0
  22. package/dist/components/Breadcrumbs.d.ts +19 -0
  23. package/dist/components/Breadcrumbs.js +14 -0
  24. package/dist/components/BrowserFrame.d.ts +18 -0
  25. package/dist/components/BrowserFrame.js +13 -0
  26. package/dist/components/Button.d.ts +51 -0
  27. package/dist/components/Button.js +54 -0
  28. package/dist/components/ButtonGroup.d.ts +21 -0
  29. package/dist/components/ButtonGroup.js +14 -0
  30. package/dist/components/CalendarView.d.ts +30 -0
  31. package/dist/components/CalendarView.js +43 -0
  32. package/dist/components/CamFrame.d.ts +21 -0
  33. package/dist/components/CamFrame.js +11 -0
  34. package/dist/components/CardHeader.d.ts +32 -0
  35. package/dist/components/CardHeader.js +12 -0
  36. package/dist/components/ChannelBanner.d.ts +24 -0
  37. package/dist/components/ChannelBanner.js +13 -0
  38. package/dist/components/ChannelPanel.d.ts +36 -0
  39. package/dist/components/ChannelPanel.js +12 -0
  40. package/dist/components/ChartLegend.d.ts +12 -0
  41. package/dist/components/ChartLegend.js +7 -0
  42. package/dist/components/ChatBadge.d.ts +15 -0
  43. package/dist/components/ChatBadge.js +39 -0
  44. package/dist/components/ChatBox.d.ts +42 -0
  45. package/dist/components/ChatBox.js +35 -0
  46. package/dist/components/Checkbox.d.ts +17 -0
  47. package/dist/components/Checkbox.js +18 -0
  48. package/dist/components/CloseButton.d.ts +7 -0
  49. package/dist/components/CloseButton.js +8 -0
  50. package/dist/components/CodeSnippet.d.ts +23 -0
  51. package/dist/components/CodeSnippet.js +75 -0
  52. package/dist/components/ColorPicker.d.ts +15 -0
  53. package/dist/components/ColorPicker.js +42 -0
  54. package/dist/components/CommandMenu.d.ts +72 -0
  55. package/dist/components/CommandMenu.js +102 -0
  56. package/dist/components/ContentDivider.d.ts +17 -0
  57. package/dist/components/ContentDivider.js +13 -0
  58. package/dist/components/CreditCard.d.ts +28 -0
  59. package/dist/components/CreditCard.js +63 -0
  60. package/dist/components/DatePicker.d.ts +48 -0
  61. package/dist/components/DatePicker.js +105 -0
  62. package/dist/components/Dropdown.d.ts +39 -0
  63. package/dist/components/Dropdown.js +39 -0
  64. package/dist/components/EmptyState.d.ts +18 -0
  65. package/dist/components/EmptyState.js +7 -0
  66. package/dist/components/FeaturedIcon.d.ts +17 -0
  67. package/dist/components/FeaturedIcon.js +32 -0
  68. package/dist/components/FileUpload.d.ts +39 -0
  69. package/dist/components/FileUpload.js +50 -0
  70. package/dist/components/FilterBar.d.ts +46 -0
  71. package/dist/components/FilterBar.js +24 -0
  72. package/dist/components/Icon.d.ts +5 -0
  73. package/dist/components/Icon.js +9 -0
  74. package/dist/components/InlineCTA.d.ts +19 -0
  75. package/dist/components/InlineCTA.js +7 -0
  76. package/dist/components/Input.d.ts +16 -0
  77. package/dist/components/Input.js +10 -0
  78. package/dist/components/LineChart.d.ts +25 -0
  79. package/dist/components/LineChart.js +23 -0
  80. package/dist/components/Message.d.ts +22 -0
  81. package/dist/components/Message.js +10 -0
  82. package/dist/components/MessageInput.d.ts +17 -0
  83. package/dist/components/MessageInput.js +25 -0
  84. package/dist/components/MetricCard.d.ts +28 -0
  85. package/dist/components/MetricCard.js +11 -0
  86. package/dist/components/Modal.d.ts +45 -0
  87. package/dist/components/Modal.js +15 -0
  88. package/dist/components/Notification.d.ts +15 -0
  89. package/dist/components/Notification.js +33 -0
  90. package/dist/components/NowPlaying.d.ts +25 -0
  91. package/dist/components/NowPlaying.js +16 -0
  92. package/dist/components/PageHeader.d.ts +22 -0
  93. package/dist/components/PageHeader.js +8 -0
  94. package/dist/components/Pagination.d.ts +29 -0
  95. package/dist/components/Pagination.js +33 -0
  96. package/dist/components/PieChart.d.ts +32 -0
  97. package/dist/components/PieChart.js +30 -0
  98. package/dist/components/ProgressBar.d.ts +11 -0
  99. package/dist/components/ProgressBar.js +8 -0
  100. package/dist/components/Radio.d.ts +15 -0
  101. package/dist/components/Radio.js +10 -0
  102. package/dist/components/RadioGroup.d.ts +26 -0
  103. package/dist/components/RadioGroup.js +9 -0
  104. package/dist/components/SectionFooter.d.ts +12 -0
  105. package/dist/components/SectionFooter.js +7 -0
  106. package/dist/components/SectionHeader.d.ts +24 -0
  107. package/dist/components/SectionHeader.js +7 -0
  108. package/dist/components/Select.d.ts +22 -0
  109. package/dist/components/Select.js +14 -0
  110. package/dist/components/SidebarNav.d.ts +38 -0
  111. package/dist/components/SidebarNav.js +26 -0
  112. package/dist/components/Skeleton.d.ts +15 -0
  113. package/dist/components/Skeleton.js +10 -0
  114. package/dist/components/Slideout.d.ts +28 -0
  115. package/dist/components/Slideout.js +15 -0
  116. package/dist/components/Slider.d.ts +15 -0
  117. package/dist/components/Slider.js +17 -0
  118. package/dist/components/Spinner.d.ts +11 -0
  119. package/dist/components/Spinner.js +12 -0
  120. package/dist/components/StreamAlert.d.ts +34 -0
  121. package/dist/components/StreamAlert.js +10 -0
  122. package/dist/components/StreamCountdown.d.ts +30 -0
  123. package/dist/components/StreamCountdown.js +44 -0
  124. package/dist/components/StreamEvents.d.ts +40 -0
  125. package/dist/components/StreamEvents.js +20 -0
  126. package/dist/components/StreamGoal.d.ts +22 -0
  127. package/dist/components/StreamGoal.js +21 -0
  128. package/dist/components/StreamScene.d.ts +35 -0
  129. package/dist/components/StreamScene.js +33 -0
  130. package/dist/components/StreamStatus.d.ts +28 -0
  131. package/dist/components/StreamStatus.js +26 -0
  132. package/dist/components/Table.d.ts +36 -0
  133. package/dist/components/Table.js +15 -0
  134. package/dist/components/Tabs.d.ts +38 -0
  135. package/dist/components/Tabs.js +34 -0
  136. package/dist/components/Tag.d.ts +18 -0
  137. package/dist/components/Tag.js +15 -0
  138. package/dist/components/TextEditor.d.ts +37 -0
  139. package/dist/components/TextEditor.js +103 -0
  140. package/dist/components/Textarea.d.ts +6 -0
  141. package/dist/components/Textarea.js +10 -0
  142. package/dist/components/Toggle.d.ts +22 -0
  143. package/dist/components/Toggle.js +12 -0
  144. package/dist/components/Tooltip.d.ts +15 -0
  145. package/dist/components/Tooltip.js +27 -0
  146. package/dist/components/TreeView.d.ts +33 -0
  147. package/dist/components/TreeView.js +48 -0
  148. package/dist/components/VideoPlayer.d.ts +25 -0
  149. package/dist/components/VideoPlayer.js +52 -0
  150. package/dist/index.d.ts +80 -0
  151. package/dist/index.js +76 -0
  152. package/dist/internal/chart.d.ts +38 -0
  153. package/dist/internal/chart.js +33 -0
  154. package/dist/internal/choice.d.ts +18 -0
  155. package/dist/internal/choice.js +17 -0
  156. package/dist/internal/cx.d.ts +3 -0
  157. package/dist/internal/cx.js +26 -0
  158. package/dist/internal/field.d.ts +29 -0
  159. package/dist/internal/field.js +23 -0
  160. package/dist/internal/format.d.ts +12 -0
  161. package/dist/internal/format.js +40 -0
  162. package/dist/internal/kbd.d.ts +6 -0
  163. package/dist/internal/kbd.js +6 -0
  164. package/dist/internal/layer.d.ts +21 -0
  165. package/dist/internal/layer.js +27 -0
  166. package/dist/internal/line.d.ts +2 -0
  167. package/dist/internal/line.js +5 -0
  168. package/dist/internal/overlay.d.ts +2 -0
  169. package/dist/internal/overlay.js +4 -0
  170. package/dist/internal/stream-stage.d.ts +18 -0
  171. package/dist/internal/stream-stage.js +38 -0
  172. package/dist/internal/stream.d.ts +56 -0
  173. package/dist/internal/stream.js +112 -0
  174. package/dist/internal/strings.d.ts +100 -0
  175. package/dist/internal/strings.js +102 -0
  176. package/dist/internal/use-controllable.d.ts +5 -0
  177. package/dist/internal/use-controllable.js +17 -0
  178. package/package.json +66 -5
  179. package/styles.css +7 -0
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef, useState } from "react";
4
+ import { cx } from "../internal/cx.js";
5
+ import { Icon } from "./Icon.js";
6
+ const DEFAULT_LABELS = {
7
+ play: "Reproducir",
8
+ pause: "Pausa",
9
+ seek: "Posición",
10
+ mute: "Silenciar",
11
+ unmute: "Activar sonido",
12
+ fullscreen: "Pantalla completa",
13
+ position: (current, total) => `${current} de ${total}`,
14
+ };
15
+ /** `252` → `4:12`. */
16
+ const clock = (s) => {
17
+ const t = Math.max(0, Math.floor(s || 0));
18
+ return `${Math.floor(t / 60)}:${String(t % 60).padStart(2, "0")}`;
19
+ };
20
+ // Controles en blanco y negro fijos: van sobre el vídeo, no sobre la interfaz (excepción de la ficha).
21
+ const FOCUS = "focus-visible:shadow-focus-ring focus-visible:outline-none";
22
+ const BTN = cx("inline-flex cursor-pointer rounded-md border-0 bg-transparent p-1.5 text-[#ffffff] transition-colors duration-100 hover:bg-[#ffffff33]", FOCUS);
23
+ const THUMB = "[&::-webkit-slider-thumb]:size-3 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#ffffff] [&::-moz-range-thumb]:size-3 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-[#ffffff]";
24
+ /** Reproductor de vídeo 16:9 con botón central, barra de controles, tiempo, volumen y pantalla completa. */
25
+ export function VideoPlayer({ src, poster, duration = 0, labels, className, style }) {
26
+ const l = { ...DEFAULT_LABELS, ...labels };
27
+ const video = useRef(null);
28
+ const wrap = useRef(null);
29
+ const [playing, setPlaying] = useState(false);
30
+ const [time, setTime] = useState(0);
31
+ const [total, setTotal] = useState(duration);
32
+ const [muted, setMuted] = useState(false);
33
+ const toggle = () => {
34
+ const v = video.current;
35
+ if (!v)
36
+ setPlaying((p) => !p);
37
+ else if (v.paused)
38
+ void v.play();
39
+ else
40
+ v.pause();
41
+ };
42
+ return (_jsxs("div", { ref: wrap, className: cx("relative aspect-video overflow-hidden rounded-xl bg-[#000000] font-body", className), style: style, children: [src ? (_jsx("video", { ref: video, src: src, poster: poster, playsInline: true, muted: muted, onClick: toggle, onPlay: () => setPlaying(true), onPause: () => setPlaying(false), onTimeUpdate: (e) => setTime(e.currentTarget.currentTime), onLoadedMetadata: (e) => setTotal(e.currentTarget.duration), className: "block size-full object-cover" })) : (_jsx("div", { className: "block size-full bg-primary-solid bg-cover bg-center", style: poster ? { backgroundImage: `url(${poster})` } : undefined })), playing ? null : (_jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", onClick: toggle, className: "absolute top-1/2 left-1/2 -mt-8 -ml-8 inline-flex size-16 cursor-pointer items-center justify-center rounded-full border-0 bg-[#ffffff4d] text-[#ffffff] [backdrop-filter:var(--backdrop-blur-lg)]", children: _jsx(Icon, { name: "play", size: 28 }) })), _jsxs("div", { className: "absolute inset-x-0 bottom-0 flex items-center gap-2 bg-[linear-gradient(to_top,#000000b2,#00000000)] px-4 pt-6 pb-3 text-[#ffffff]", children: [_jsx("button", { type: "button", className: BTN, onClick: toggle, "aria-label": playing ? l.pause : l.play, children: _jsx(Icon, { name: playing ? "pause-circle" : "play", size: 20 }) }), _jsxs("span", { className: "text-sm font-medium whitespace-nowrap tabular-nums", children: [clock(time), " / ", clock(total)] }), _jsx("input", { type: "range", min: 0, max: total || 1, step: 0.1, value: time, "aria-label": l.seek, "aria-valuetext": l.position(clock(time), clock(total)), onChange: (e) => {
43
+ const n = Number(e.target.value);
44
+ setTime(n);
45
+ if (video.current)
46
+ video.current.currentTime = n;
47
+ }, className: cx("h-1 flex-1 cursor-pointer appearance-none rounded-full bg-[linear-gradient(to_right,#ffffff_var(--p),#ffffff4d_var(--p))]", FOCUS, THUMB), style: { "--p": `${total ? (time / total) * 100 : 0}%` } }), _jsx("button", { type: "button", className: BTN, onClick: () => {
48
+ if (video.current)
49
+ video.current.muted = !muted;
50
+ setMuted(!muted);
51
+ }, "aria-label": muted ? l.unmute : l.mute, children: _jsx(Icon, { name: muted ? "volume-x" : "volume-max", size: 20 }) }), _jsx("button", { type: "button", className: BTN, "aria-label": l.fullscreen, onClick: () => void wrap.current?.requestFullscreen?.(), children: _jsx(Icon, { name: "maximize-01", size: 20 }) })] })] }));
52
+ }
@@ -0,0 +1,80 @@
1
+ export { ActivityFeed, type ActivityFeedProps, type ActivityItem } from "./components/ActivityFeed.js";
2
+ export { Alert, type AlertProps } from "./components/Alert.js";
3
+ export { AppIcon, type AppIconHue, type AppIconProps, type AppIconShape, type AppIconVariant, type AppName, appIcons, } from "./components/AppIcon.js";
4
+ export { Avatar, type AvatarProps, type AvatarSize, type AvatarStatus } from "./components/Avatar.js";
5
+ export { AvatarGroup, type AvatarGroupProps } from "./components/AvatarGroup.js";
6
+ export { AvatarLabel, type AvatarLabelProps } from "./components/AvatarLabel.js";
7
+ export { BackgroundPattern, type BackgroundPatternMask, type BackgroundPatternName, type BackgroundPatternProps, } from "./components/BackgroundPattern.js";
8
+ export { Badge, type BadgeColor, type BadgeProps, type BadgeSize, type BadgeType, type StatusColor, } from "./components/Badge.js";
9
+ export { Banner, type BannerEmail, type BannerProps } from "./components/Banner.js";
10
+ export { BarChart, type BarChartProps } from "./components/BarChart.js";
11
+ export { type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps } from "./components/Breadcrumbs.js";
12
+ export { BrowserFrame, type BrowserFrameProps } from "./components/BrowserFrame.js";
13
+ export { Button, type ButtonHierarchy, type ButtonProps, type ButtonSize } from "./components/Button.js";
14
+ export { ButtonGroup, type ButtonGroupItem, type ButtonGroupProps } from "./components/ButtonGroup.js";
15
+ export { type CalendarEvent, CalendarView, type CalendarViewProps } from "./components/CalendarView.js";
16
+ export { CamFrame, type CamFrameProps, type CamFrameRatio } from "./components/CamFrame.js";
17
+ export { CardHeader, type CardHeaderProps } from "./components/CardHeader.js";
18
+ export { ChannelBanner, type ChannelBannerProps, type ChannelBannerSlot, } from "./components/ChannelBanner.js";
19
+ export { ChannelPanel, type ChannelPanelAction, type ChannelPanelProps, type ChannelPanelRow, } from "./components/ChannelPanel.js";
20
+ export { ChartLegend, type ChartLegendProps } from "./components/ChartLegend.js";
21
+ export { ChatBadge, type ChatBadgeKind, type ChatBadgeProps } from "./components/ChatBadge.js";
22
+ export { ChatBox, type ChatBoxProps, type ChatMessage, type ChatTone } from "./components/ChatBox.js";
23
+ export { Checkbox, type CheckboxProps } from "./components/Checkbox.js";
24
+ export { CloseButton, type CloseButtonProps } from "./components/CloseButton.js";
25
+ export { CodeSnippet, type CodeSnippetProps, type CodeSnippetTab } from "./components/CodeSnippet.js";
26
+ export { ColorPicker, type ColorPickerProps } from "./components/ColorPicker.js";
27
+ export { type CommandGroup, type CommandItem, CommandMenu, type CommandMenuLabels, type CommandMenuProps, } from "./components/CommandMenu.js";
28
+ export { ContentDivider, type ContentDividerProps } from "./components/ContentDivider.js";
29
+ export { CreditCard, type CreditCardProps, type CreditCardTheme } from "./components/CreditCard.js";
30
+ export { DatePicker, type DatePickerLabels, type DatePickerPreset, type DatePickerProps, type DateRange, } from "./components/DatePicker.js";
31
+ export { Dropdown, type DropdownAction, type DropdownItem, type DropdownProps, } from "./components/Dropdown.js";
32
+ export { EmptyState, type EmptyStateProps } from "./components/EmptyState.js";
33
+ export { FeaturedIcon, type FeaturedIconProps, type FeaturedIconSize, type FeaturedIconVariant, } from "./components/FeaturedIcon.js";
34
+ export { FileUpload, type FileUploadLabels, type FileUploadProps, type UploadFile, } from "./components/FileUpload.js";
35
+ export { FilterBar, type FilterBarLabels, type FilterBarProps } from "./components/FilterBar.js";
36
+ export { Icon, type IconName, type IconProps } from "./components/Icon.js";
37
+ export { InlineCTA, type InlineCTAProps } from "./components/InlineCTA.js";
38
+ export { Input, type InputProps } from "./components/Input.js";
39
+ export { LineChart, type LineChartProps } from "./components/LineChart.js";
40
+ export { Message, type MessageProps } from "./components/Message.js";
41
+ export { MessageInput, type MessageInputProps } from "./components/MessageInput.js";
42
+ export { MetricCard, type MetricCardProps } from "./components/MetricCard.js";
43
+ export { Modal, type ModalProps } from "./components/Modal.js";
44
+ export { Notification, type NotificationProps } from "./components/Notification.js";
45
+ export { NowPlaying, type NowPlayingLabels, type NowPlayingProps } from "./components/NowPlaying.js";
46
+ export { PageHeader, type PageHeaderProps } from "./components/PageHeader.js";
47
+ export { Pagination, type PaginationLabels, type PaginationProps, } from "./components/Pagination.js";
48
+ export { PieChart, type PieChartProps, type PieChartSlice } from "./components/PieChart.js";
49
+ export { ProgressBar, type ProgressBarProps } from "./components/ProgressBar.js";
50
+ export { Radio, type RadioProps } from "./components/Radio.js";
51
+ export { RadioGroup, type RadioGroupOption, type RadioGroupProps } from "./components/RadioGroup.js";
52
+ export { SectionFooter, type SectionFooterProps } from "./components/SectionFooter.js";
53
+ export { SectionHeader, type SectionHeaderProps } from "./components/SectionHeader.js";
54
+ export { Select, type SelectOption, type SelectProps } from "./components/Select.js";
55
+ export { SidebarNav, type SidebarNavItem, type SidebarNavLabels, type SidebarNavProps, } from "./components/SidebarNav.js";
56
+ export { Skeleton, type SkeletonProps } from "./components/Skeleton.js";
57
+ export { Slideout, type SlideoutProps } from "./components/Slideout.js";
58
+ export { Slider, type SliderProps } from "./components/Slider.js";
59
+ export { Spinner, type SpinnerProps, type SpinnerSize } from "./components/Spinner.js";
60
+ export { StreamAlert, type StreamAlertProps } from "./components/StreamAlert.js";
61
+ export { StreamCountdown, type StreamCountdownProps, type StreamCountdownSize, } from "./components/StreamCountdown.js";
62
+ export { type StreamEventItem, StreamEvents, type StreamEventsProps } from "./components/StreamEvents.js";
63
+ export { StreamGoal, type StreamGoalLabels, type StreamGoalProps } from "./components/StreamGoal.js";
64
+ export { StreamScene, type StreamSceneProps, type StreamSceneVariant } from "./components/StreamScene.js";
65
+ export { StreamStatus, type StreamStatusLabels, type StreamStatusProps, type StreamStatusValue, } from "./components/StreamStatus.js";
66
+ export { Table, type TableColumn, type TableProps } from "./components/Table.js";
67
+ export { type TabItem, Tabs, type TabsProps, type TabsSize, type TabsType } from "./components/Tabs.js";
68
+ export { Tag, type TagProps, type TagSize } from "./components/Tag.js";
69
+ export { Textarea, type TextareaProps } from "./components/Textarea.js";
70
+ export { TextEditor, type TextEditorLabels, type TextEditorProps } from "./components/TextEditor.js";
71
+ export { Toggle, type ToggleProps } from "./components/Toggle.js";
72
+ export { Tooltip, type TooltipProps } from "./components/Tooltip.js";
73
+ export { type TreeItem, TreeView, type TreeViewProps } from "./components/TreeView.js";
74
+ export { VideoPlayer, type VideoPlayerLabels, type VideoPlayerProps } from "./components/VideoPlayer.js";
75
+ export type { ChartSeries } from "./internal/chart.js";
76
+ export type { ChoiceSize } from "./internal/choice.js";
77
+ export type { FieldProps, FieldSize } from "./internal/field.js";
78
+ export { formatBytes, formatCurrency, formatDate, formatMonth, formatNumber, formatPercent, } from "./internal/format.js";
79
+ export type { StreamEventType, StreamMotion, StreamTime } from "./internal/stream.js";
80
+ export { defaultStrings, type Strings } from "./internal/strings.js";
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ export { ActivityFeed } from "./components/ActivityFeed.js";
2
+ export { Alert } from "./components/Alert.js";
3
+ export { AppIcon, appIcons, } from "./components/AppIcon.js";
4
+ export { Avatar } from "./components/Avatar.js";
5
+ export { AvatarGroup } from "./components/AvatarGroup.js";
6
+ export { AvatarLabel } from "./components/AvatarLabel.js";
7
+ export { BackgroundPattern, } from "./components/BackgroundPattern.js";
8
+ export { Badge, } from "./components/Badge.js";
9
+ export { Banner } from "./components/Banner.js";
10
+ export { BarChart } from "./components/BarChart.js";
11
+ export { Breadcrumbs } from "./components/Breadcrumbs.js";
12
+ export { BrowserFrame } from "./components/BrowserFrame.js";
13
+ export { Button } from "./components/Button.js";
14
+ export { ButtonGroup } from "./components/ButtonGroup.js";
15
+ export { CalendarView } from "./components/CalendarView.js";
16
+ export { CamFrame } from "./components/CamFrame.js";
17
+ export { CardHeader } from "./components/CardHeader.js";
18
+ export { ChannelBanner, } from "./components/ChannelBanner.js";
19
+ export { ChannelPanel, } from "./components/ChannelPanel.js";
20
+ export { ChartLegend } from "./components/ChartLegend.js";
21
+ export { ChatBadge } from "./components/ChatBadge.js";
22
+ export { ChatBox } from "./components/ChatBox.js";
23
+ export { Checkbox } from "./components/Checkbox.js";
24
+ export { CloseButton } from "./components/CloseButton.js";
25
+ export { CodeSnippet } from "./components/CodeSnippet.js";
26
+ export { ColorPicker } from "./components/ColorPicker.js";
27
+ export { CommandMenu, } from "./components/CommandMenu.js";
28
+ export { ContentDivider } from "./components/ContentDivider.js";
29
+ export { CreditCard } from "./components/CreditCard.js";
30
+ export { DatePicker, } from "./components/DatePicker.js";
31
+ export { Dropdown, } from "./components/Dropdown.js";
32
+ export { EmptyState } from "./components/EmptyState.js";
33
+ export { FeaturedIcon, } from "./components/FeaturedIcon.js";
34
+ export { FileUpload, } from "./components/FileUpload.js";
35
+ export { FilterBar } from "./components/FilterBar.js";
36
+ export { Icon } from "./components/Icon.js";
37
+ export { InlineCTA } from "./components/InlineCTA.js";
38
+ export { Input } from "./components/Input.js";
39
+ export { LineChart } from "./components/LineChart.js";
40
+ export { Message } from "./components/Message.js";
41
+ export { MessageInput } from "./components/MessageInput.js";
42
+ export { MetricCard } from "./components/MetricCard.js";
43
+ export { Modal } from "./components/Modal.js";
44
+ export { Notification } from "./components/Notification.js";
45
+ export { NowPlaying } from "./components/NowPlaying.js";
46
+ export { PageHeader } from "./components/PageHeader.js";
47
+ export { Pagination, } from "./components/Pagination.js";
48
+ export { PieChart } from "./components/PieChart.js";
49
+ export { ProgressBar } from "./components/ProgressBar.js";
50
+ export { Radio } from "./components/Radio.js";
51
+ export { RadioGroup } from "./components/RadioGroup.js";
52
+ export { SectionFooter } from "./components/SectionFooter.js";
53
+ export { SectionHeader } from "./components/SectionHeader.js";
54
+ export { Select } from "./components/Select.js";
55
+ export { SidebarNav, } from "./components/SidebarNav.js";
56
+ export { Skeleton } from "./components/Skeleton.js";
57
+ export { Slideout } from "./components/Slideout.js";
58
+ export { Slider } from "./components/Slider.js";
59
+ export { Spinner } from "./components/Spinner.js";
60
+ export { StreamAlert } from "./components/StreamAlert.js";
61
+ export { StreamCountdown, } from "./components/StreamCountdown.js";
62
+ export { StreamEvents } from "./components/StreamEvents.js";
63
+ export { StreamGoal } from "./components/StreamGoal.js";
64
+ export { StreamScene } from "./components/StreamScene.js";
65
+ export { StreamStatus, } from "./components/StreamStatus.js";
66
+ export { Table } from "./components/Table.js";
67
+ export { Tabs } from "./components/Tabs.js";
68
+ export { Tag } from "./components/Tag.js";
69
+ export { Textarea } from "./components/Textarea.js";
70
+ export { TextEditor } from "./components/TextEditor.js";
71
+ export { Toggle } from "./components/Toggle.js";
72
+ export { Tooltip } from "./components/Tooltip.js";
73
+ export { TreeView } from "./components/TreeView.js";
74
+ export { VideoPlayer } from "./components/VideoPlayer.js";
75
+ export { formatBytes, formatCurrency, formatDate, formatMonth, formatNumber, formatPercent, } from "./internal/format.js";
76
+ export { defaultStrings } from "./internal/strings.js";
@@ -0,0 +1,38 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ChartSeries {
3
+ /** Nombre en la leyenda y en el título de cada punto. */
4
+ name?: string;
5
+ data: number[];
6
+ /** Color propio (cualquier color CSS). Por defecto, el de su posición en la paleta de series. */
7
+ color?: string;
8
+ }
9
+ /** Paleta de series de la guía, en orden: la marca primero, en degradado. */
10
+ export declare const SERIES: readonly ["var(--utility-brand-600)", "var(--utility-brand-400)", "var(--utility-brand-200)", "var(--utility-sky-500)", "var(--utility-pink-500)", "var(--utility-neutral-400)"];
11
+ export declare const seriesColor: (i: number, color?: string) => string | undefined;
12
+ /** Techo "redondo" de la escala: 1, 1,2, 1,5, 2, 2,5, 3, 4, 5, 6, 8 o 10 por su potencia de diez. */
13
+ export declare function nice(max: number): number;
14
+ export interface Plot {
15
+ w: number;
16
+ h: number;
17
+ pad: {
18
+ t: number;
19
+ r: number;
20
+ b: number;
21
+ l: number;
22
+ };
23
+ }
24
+ export declare const PAD: {
25
+ t: number;
26
+ r: number;
27
+ b: number;
28
+ l: number;
29
+ };
30
+ /** Rejilla en `border-tertiary` (cinco líneas desde cero) y etiquetas de los ejes en `text-xs` `text-tertiary`. */
31
+ export declare function Axes({ plot: { w, h, pad }, top, labels, format, }: {
32
+ plot: Plot;
33
+ top: number;
34
+ labels: string[];
35
+ format?: (v: number) => string;
36
+ }): ReactNode;
37
+ /** "Gastos · Oct: 1.284 €" para el `<title>` de cada punto o barra. */
38
+ export declare const pointTitle: (name: string | undefined, label: string, value: string | number) => string;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Paleta de series de la guía, en orden: la marca primero, en degradado. */
3
+ export const SERIES = [
4
+ "var(--utility-brand-600)",
5
+ "var(--utility-brand-400)",
6
+ "var(--utility-brand-200)",
7
+ "var(--utility-sky-500)",
8
+ "var(--utility-pink-500)",
9
+ "var(--utility-neutral-400)",
10
+ ];
11
+ export const seriesColor = (i, color) => color ?? SERIES[i % SERIES.length];
12
+ /** Techo "redondo" de la escala: 1, 1,2, 1,5, 2, 2,5, 3, 4, 5, 6, 8 o 10 por su potencia de diez. */
13
+ export function nice(max) {
14
+ if (max <= 0)
15
+ return 1;
16
+ const e = 10 ** Math.floor(Math.log10(max));
17
+ const f = max / e;
18
+ for (const s of [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10])
19
+ if (f <= s)
20
+ return s * e;
21
+ return 10 * e;
22
+ }
23
+ export const PAD = { t: 12, r: 12, b: 28, l: 44 };
24
+ const TICK = { fontSize: 12, fill: "var(--text-tertiary)" };
25
+ /** Rejilla en `border-tertiary` (cinco líneas desde cero) y etiquetas de los ejes en `text-xs` `text-tertiary`. */
26
+ export function Axes({ plot: { w, h, pad }, top, labels, format, }) {
27
+ return (_jsxs("g", { children: [[0, 0.25, 0.5, 0.75, 1].map((t) => {
28
+ const y = pad.t + (h - pad.t - pad.b) * (1 - t);
29
+ return (_jsxs("g", { children: [_jsx("line", { x1: pad.l, x2: w - pad.r, y1: y, y2: y, stroke: "var(--border-tertiary)", strokeWidth: 1 }), _jsx("text", { x: pad.l - 8, y: y + 4, textAnchor: "end", ...TICK, children: format ? format(top * t) : Math.round(top * t) })] }, t));
30
+ }), labels.map((l, i) => (_jsx("text", { x: pad.l + (w - pad.l - pad.r) * (labels.length === 1 ? 0.5 : (i + 0.5) / labels.length), y: h - 6, textAnchor: "middle", ...TICK, children: l }, i)))] }));
31
+ }
32
+ /** "Gastos · Oct: 1.284 €" para el `<title>` de cada punto o barra. */
33
+ export const pointTitle = (name, label, value) => `${name ? `${name} · ` : ""}${label}: ${value}`;
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from "react";
2
+ export type ChoiceSize = "sm" | "md";
3
+ export declare const choiceLabel: (size: ChoiceSize) => string;
4
+ /** El input real, invisible pero enfocable; la caja de al lado se pinta con `peer-*`. */
5
+ export declare const hiddenInput = "peer absolute size-px opacity-0";
6
+ /** Caja de 16 o 20px que baja 2px para alinearse con la primera línea. */
7
+ export declare const choiceBox: (size: ChoiceSize, radio: boolean) => string;
8
+ /** Punto blanco del radio marcado: 6px, 8px en `md`. */
9
+ export declare const radioDot: (size: ChoiceSize) => string;
10
+ interface ChoiceTextProps {
11
+ label?: ReactNode;
12
+ hint?: ReactNode;
13
+ size: ChoiceSize;
14
+ className?: string;
15
+ }
16
+ /** Etiqueta (`text-secondary` medium) y apoyo (`text-tertiary`); 16px en `md`. */
17
+ export declare function ChoiceText({ label, hint, size, className }: ChoiceTextProps): import("react").JSX.Element | null;
18
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ // Piezas comunes de Checkbox, Radio, RadioGroup y Toggle: etiqueta envolvente, caja y textos.
4
+ export const choiceLabel = (size) => cx("group relative inline-flex cursor-pointer items-start font-body has-[:disabled]:cursor-not-allowed data-disabled:cursor-not-allowed", size === "md" ? "gap-3" : "gap-2");
5
+ /** El input real, invisible pero enfocable; la caja de al lado se pinta con `peer-*`. */
6
+ export const hiddenInput = "peer absolute size-px opacity-0";
7
+ /** Caja de 16 o 20px que baja 2px para alinearse con la primera línea. */
8
+ export const choiceBox = (size, radio) => cx("mt-0.5 inline-flex flex-none items-center justify-center bg-primary text-fg-white shadow-[inset_0_0_0_1px_var(--border-primary)]", size === "md" ? "size-5 rounded-md" : "size-4 rounded-sm", radio && "rounded-full");
9
+ /** Punto blanco del radio marcado: 6px, 8px en `md`. */
10
+ export const radioDot = (size) => cx("rounded-full bg-fg-white", size === "md" ? "size-2" : "size-1.5");
11
+ /** Etiqueta (`text-secondary` medium) y apoyo (`text-tertiary`); 16px en `md`. */
12
+ export function ChoiceText({ label, hint, size, className }) {
13
+ if (!label && !hint)
14
+ return null;
15
+ const text = size === "md" ? "text-md" : "text-sm";
16
+ return (_jsxs("span", { className: cx("flex flex-col", size === "md" && "gap-0.5", className), children: [label ? _jsx("span", { className: cx(text, "font-medium text-secondary"), children: label }) : null, hint ? _jsx("span", { className: cx(text, "font-normal text-tertiary"), children: hint }) : null] }));
17
+ }
@@ -0,0 +1,3 @@
1
+ import { type ClassValue } from "clsx";
2
+ /** Junta clases y resuelve conflictos de Tailwind: la última gana, así el `className` del consumidor manda. */
3
+ export declare function cx(...inputs: ClassValue[]): string;
@@ -0,0 +1,26 @@
1
+ import { clsx } from "clsx";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+ // tailwind-merge tiene que saber qué nombres propios del tema son tamaños de texto y sombras;
4
+ // si no, trataría `text-display-sm` como un color y lo quitaría al juntarlo con `text-primary`.
5
+ const twMerge = extendTailwindMerge({
6
+ extend: {
7
+ theme: {
8
+ text: [
9
+ "display-xs",
10
+ "display-sm",
11
+ "display-md",
12
+ "display-lg",
13
+ "display-xl",
14
+ "display-2xl",
15
+ "code-sm",
16
+ "code-xs",
17
+ ],
18
+ shadow: ["xs-skeuomorphic", "focus-ring", "focus-ring-error"],
19
+ font: ["body", "display", "mono"],
20
+ },
21
+ },
22
+ });
23
+ /** Junta clases y resuelve conflictos de Tailwind: la última gana, así el `className` del consumidor manda. */
24
+ export function cx(...inputs) {
25
+ return twMerge(clsx(inputs));
26
+ }
@@ -0,0 +1,29 @@
1
+ import type { ReactNode } from "react";
2
+ export type FieldSize = "sm" | "md" | "lg";
3
+ export interface FieldProps {
4
+ /** Etiqueta visible encima del campo. El placeholder nunca la sustituye. */
5
+ label?: ReactNode;
6
+ /** Añade el asterisco tras la etiqueta (`text-brand-tertiary`, rojo con error). */
7
+ required?: boolean;
8
+ /** Pista bajo el campo. Con `error` de texto, la sustituye. */
9
+ hint?: ReactNode;
10
+ /** Mensaje de error, o `true` para marcar el error sin texto. */
11
+ error?: string | boolean;
12
+ }
13
+ /** Clases del elemento nativo: sin borde propio, el control pinta el marco. */
14
+ export declare const nativeClasses: (size?: FieldSize) => string;
15
+ export declare const controlClasses: (size: FieldSize | undefined, error: FieldProps["error"], disabled?: boolean) => string;
16
+ interface FieldShellProps extends FieldProps {
17
+ id: string;
18
+ size?: FieldSize;
19
+ className?: string;
20
+ children: ReactNode;
21
+ }
22
+ /** Etiqueta, control y pista o error, enlazados por `id`. Compartido por Input, Textarea y Select. */
23
+ export declare function FieldShell({ id, label, required, hint, error, size, className, children, }: FieldShellProps): import("react").JSX.Element;
24
+ /** Atributos ARIA que enlazan el control con su pista o error. */
25
+ export declare const describedBy: (id: string, { hint, error }: FieldProps) => {
26
+ "aria-invalid": boolean | undefined;
27
+ "aria-describedby": string | undefined;
28
+ };
29
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ // Borde de 1px y shadow-xs; en foco, 2px en border-brand. Con teclado, además el anillo de foco.
4
+ const CONTROL = "flex items-center gap-2 rounded-lg bg-primary px-3 py-2 text-fg-quaternary transition-shadow duration-100 ease-linear shadow-[var(--shadow-xs),inset_0_0_0_1px_var(--border-primary)] focus-within:shadow-[var(--shadow-xs),inset_0_0_0_2px_var(--border-brand)] has-[:focus-visible]:shadow-[var(--shadow-xs),inset_0_0_0_2px_var(--border-brand),var(--focus-ring-shadow)]";
5
+ const CONTROL_ERROR = "shadow-[var(--shadow-xs),inset_0_0_0_1px_var(--border-error_subtle)] focus-within:shadow-[var(--shadow-xs),inset_0_0_0_2px_var(--border-error)] has-[:focus-visible]:shadow-[var(--shadow-xs),inset_0_0_0_2px_var(--border-error),var(--focus-ring-error-shadow)]";
6
+ const CONTROL_SIZE = {
7
+ sm: "",
8
+ md: "min-h-10",
9
+ lg: "min-h-11 px-3.5 py-2.5",
10
+ };
11
+ /** Clases del elemento nativo: sin borde propio, el control pinta el marco. */
12
+ export const nativeClasses = (size = "md") => cx("min-w-0 flex-1 border-0 bg-transparent p-0 font-body text-md font-normal text-primary outline-none placeholder:text-placeholder disabled:cursor-not-allowed", size === "sm" && "text-sm");
13
+ export const controlClasses = (size, error, disabled) => cx(CONTROL, size && CONTROL_SIZE[size], error && CONTROL_ERROR, disabled && "opacity-50");
14
+ /** Etiqueta, control y pista o error, enlazados por `id`. Compartido por Input, Textarea y Select. */
15
+ export function FieldShell({ id, label, required, hint, error, size = "md", className, children, }) {
16
+ const message = typeof error === "string" && error ? error : hint;
17
+ return (_jsxs("div", { className: cx("flex min-w-0 flex-col gap-1.5 font-body", className), children: [label ? (_jsxs("label", { htmlFor: id, className: "text-sm font-medium text-secondary", children: [label, required ? (_jsx("span", { "aria-hidden": "true", className: cx("ml-0.5", error ? "text-error-primary" : "text-brand-tertiary"), children: "*" })) : null] })) : null, children, message ? (_jsx("p", { id: `${id}-hint`, className: cx("m-0 text-sm font-normal", size === "sm" && "text-xs", error ? "text-error-primary" : "text-tertiary"), children: message })) : null] }));
18
+ }
19
+ /** Atributos ARIA que enlazan el control con su pista o error. */
20
+ export const describedBy = (id, { hint, error }) => ({
21
+ "aria-invalid": error ? true : undefined,
22
+ "aria-describedby": hint || (typeof error === "string" && error) ? `${id}-hint` : undefined,
23
+ });
@@ -0,0 +1,12 @@
1
+ /** `1284.5` → `1.284,50 €`. */
2
+ export declare function formatCurrency(value: number, currency?: string): string;
3
+ /** `1284.5` → `1.284,5`; con `decimals` fija los decimales. */
4
+ export declare function formatNumber(value: number, decimals?: number): string;
5
+ /** `0.08` → `8 %`. */
6
+ export declare function formatPercent(value: number, decimals?: number): string;
7
+ /** `new Date(2026, 9, 29)` → `29 oct 2026`. */
8
+ export declare function formatDate(date: Date): string;
9
+ /** Nombre del mes con mayúscula inicial: `Octubre 2026`. */
10
+ export declare function formatMonth(date: Date): string;
11
+ /** `204800` → `200 KB`. */
12
+ export declare function formatBytes(bytes: number): string;
@@ -0,0 +1,40 @@
1
+ // Formatos de la guía: castellano de España, `1.284,50 €`, `29 oct 2026`.
2
+ const LOCALE = "es-ES";
3
+ // En es-ES, Intl no agrupa los números de cuatro cifras (1284,50); la guía pide 1.284,50.
4
+ const number = (options) => new Intl.NumberFormat(LOCALE, { useGrouping: "always", ...options });
5
+ /** `1284.5` → `1.284,50 €`. */
6
+ export function formatCurrency(value, currency = "EUR") {
7
+ return number({ style: "currency", currency }).format(value);
8
+ }
9
+ /** `1284.5` → `1.284,5`; con `decimals` fija los decimales. */
10
+ export function formatNumber(value, decimals) {
11
+ return number(decimals === undefined ? {} : { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(value);
12
+ }
13
+ /** `0.08` → `8 %`. */
14
+ export function formatPercent(value, decimals = 0) {
15
+ return number({ style: "percent", maximumFractionDigits: decimals }).format(value);
16
+ }
17
+ /** `new Date(2026, 9, 29)` → `29 oct 2026`. */
18
+ export function formatDate(date) {
19
+ return new Intl.DateTimeFormat(LOCALE, { day: "numeric", month: "short", year: "numeric" }).format(date);
20
+ }
21
+ /** Nombre del mes con mayúscula inicial: `Octubre 2026`. */
22
+ export function formatMonth(date) {
23
+ const s = new Intl.DateTimeFormat(LOCALE, { month: "long", year: "numeric" })
24
+ .format(date)
25
+ .replace(" de ", " ");
26
+ return s.charAt(0).toUpperCase() + s.slice(1);
27
+ }
28
+ /** `204800` → `200 KB`. */
29
+ export function formatBytes(bytes) {
30
+ if (bytes < 1024)
31
+ return `${bytes} B`;
32
+ const units = ["KB", "MB", "GB"];
33
+ let v = bytes / 1024;
34
+ let i = 0;
35
+ while (v >= 1024 && i < units.length - 1) {
36
+ v /= 1024;
37
+ i++;
38
+ }
39
+ return `${formatNumber(Math.round(v * 10) / 10)} ${units[i]}`;
40
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ /** Tecla o atajo (`jm-menu-kbd` de la guía): texto de 12px con filo `border-secondary`. */
3
+ export declare function Kbd({ children, className }: {
4
+ children: ReactNode;
5
+ className?: string;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ /** Tecla o atajo (`jm-menu-kbd` de la guía): texto de 12px con filo `border-secondary`. */
4
+ export function Kbd({ children, className }) {
5
+ return (_jsx("kbd", { className: cx("rounded-sm px-1 font-body text-xs font-medium text-quaternary shadow-[inset_0_0_0_1px_var(--border-secondary)]", className), children: children }));
6
+ }
@@ -0,0 +1,21 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ export interface LayerProps {
3
+ /** Dentro de su contenedor (posición relativa) y sin atrapar el foco: para vistas previas. */
4
+ contained?: boolean;
5
+ /** Sin él, ni Escape ni el clic fuera cierran la capa. */
6
+ onClose?: () => void;
7
+ /** Clases del fondo, además de las comunes (colocación del panel). */
8
+ overlayClassName: string;
9
+ panelClassName: string;
10
+ panelStyle?: CSSProperties;
11
+ /** Id del título que da nombre al diálogo. */
12
+ labelledBy: string;
13
+ describedBy?: string;
14
+ children: ReactNode;
15
+ }
16
+ /**
17
+ * Capa de diálogo común a Modal y Slideout. Sobre la página usa React Aria: foco atrapado, Escape y clic fuera
18
+ * cierran, el foco vuelve a quien la abrió y la página no se desplaza debajo. Con `contained` es un diálogo no modal
19
+ * dentro de su contenedor; Escape y el clic en el fondo siguen cerrándolo.
20
+ */
21
+ export declare function Layer({ contained, onClose, overlayClassName, panelClassName, panelStyle, labelledBy, describedBy, children, }: LayerProps): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Dialog, Modal, ModalOverlay } from "react-aria-components";
4
+ import { cx } from "./cx.js";
5
+ import { OVERLAY, OVERLAY_POSITION } from "./overlay.js";
6
+ /**
7
+ * Capa de diálogo común a Modal y Slideout. Sobre la página usa React Aria: foco atrapado, Escape y clic fuera
8
+ * cierran, el foco vuelve a quien la abrió y la página no se desplaza debajo. Con `contained` es un diálogo no modal
9
+ * dentro de su contenedor; Escape y el clic en el fondo siguen cerrándolo.
10
+ */
11
+ export function Layer({ contained, onClose, overlayClassName, panelClassName, panelStyle, labelledBy, describedBy, children, }) {
12
+ if (contained) {
13
+ return (_jsx("div", { className: cx(OVERLAY, OVERLAY_POSITION(true), overlayClassName), onClick: (e) => {
14
+ if (e.target === e.currentTarget)
15
+ onClose?.();
16
+ }, children: _jsx("div", { role: "dialog", "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: panelClassName, style: panelStyle, onKeyDown: (e) => {
17
+ if (e.key === "Escape" && onClose) {
18
+ e.stopPropagation();
19
+ onClose();
20
+ }
21
+ }, children: children }) }));
22
+ }
23
+ return (_jsx(ModalOverlay, { isOpen: true, isDismissable: !!onClose, isKeyboardDismissDisabled: !onClose, onOpenChange: (open) => {
24
+ if (!open)
25
+ onClose?.();
26
+ }, className: cx(OVERLAY, OVERLAY_POSITION(false), overlayClassName), children: _jsx(Modal, { className: "contents", children: _jsx(Dialog, { "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: cx(panelClassName, "outline-none"), style: panelStyle, children: children }) }) }));
27
+ }
@@ -0,0 +1,2 @@
1
+ /** Línea de 1px en `border-secondary` bajo cabeceras y sobre pies (`.jm-ph-line` del bundle). Decorativa. */
2
+ export declare function Line(): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** Línea de 1px en `border-secondary` bajo cabeceras y sobre pies (`.jm-ph-line` del bundle). Decorativa. */
3
+ export function Line() {
4
+ return _jsx("div", { "aria-hidden": "true", className: "shrink-0 border-t border-secondary" });
5
+ }
@@ -0,0 +1,2 @@
1
+ export declare const OVERLAY = "z-50 flex bg-overlay/70 p-4 [backdrop-filter:var(--backdrop-blur-lg)]";
2
+ export declare const OVERLAY_POSITION: (contained?: boolean) => "absolute inset-0" | "fixed inset-0";
@@ -0,0 +1,4 @@
1
+ // Fondo de las capas (Modal, Slideout, CommandMenu): `bg-overlay` al 70 % con desenfoque de 16px.
2
+ // `contained` la pinta dentro de su contenedor (absoluta) en vez de sobre toda la ventana, para las vistas previas.
3
+ export const OVERLAY = "z-50 flex bg-overlay/70 p-4 [backdrop-filter:var(--backdrop-blur-lg)]";
4
+ export const OVERLAY_POSITION = (contained) => (contained ? "absolute inset-0" : "fixed inset-0");
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ export interface StreamStageProps {
3
+ /** Tamaño real del lienzo en px (StreamScene 1920×1080, ChannelBanner 1200×480). */
4
+ width: number;
5
+ height: number;
6
+ /** Escala fija: mide `width × scale`. Sin ella, se ajusta al ancho del contenedor. */
7
+ scale?: number;
8
+ className?: string;
9
+ /** Clases del lienzo interior, que mide siempre `width × height`. */
10
+ stageClassName?: string;
11
+ children?: ReactNode;
12
+ }
13
+ /**
14
+ * Lienzo de tamaño fijo escalado con `transform` (`stFit` + `.jm-st-fit` / `.jm-st-stage` del bundle).
15
+ * El alto del contenedor sale de `aspect-ratio`, así que el hueco es el correcto desde el primer pintado; el
16
+ * contenido espera a la primera medida para no verse a tamaño real un instante. Con `scale` no mide nada.
17
+ */
18
+ export declare function StreamStage({ width, height, scale, className, stageClassName, children }: StreamStageProps): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useLayoutEffect, useRef, useState } from "react";
4
+ import { cx } from "./cx.js";
5
+ import { STREAM_ROOT } from "./stream.js";
6
+ // En servidor useLayoutEffect avisa; allí no hay nada que medir.
7
+ const useIsoLayoutEffect = typeof window === "undefined" ? () => { } : useLayoutEffect;
8
+ /**
9
+ * Lienzo de tamaño fijo escalado con `transform` (`stFit` + `.jm-st-fit` / `.jm-st-stage` del bundle).
10
+ * El alto del contenedor sale de `aspect-ratio`, así que el hueco es el correcto desde el primer pintado; el
11
+ * contenido espera a la primera medida para no verse a tamaño real un instante. Con `scale` no mide nada.
12
+ */
13
+ export function StreamStage({ width, height, scale, className, stageClassName, children }) {
14
+ const ref = useRef(null);
15
+ const [measured, setMeasured] = useState(null);
16
+ useIsoLayoutEffect(() => {
17
+ if (scale)
18
+ return;
19
+ const el = ref.current;
20
+ if (!el)
21
+ return;
22
+ const update = () => {
23
+ if (el.clientWidth)
24
+ setMeasured(el.clientWidth / width);
25
+ };
26
+ update();
27
+ if (typeof ResizeObserver === "undefined")
28
+ return;
29
+ const ro = new ResizeObserver(update);
30
+ ro.observe(el);
31
+ return () => ro.disconnect();
32
+ }, [scale, width]);
33
+ const s = scale ?? measured;
34
+ const outer = scale
35
+ ? { width: width * scale, height: height * scale }
36
+ : { width: "100%", aspectRatio: `${width} / ${height}` };
37
+ return (_jsx("div", { ref: ref, className: cx(STREAM_ROOT, "relative overflow-hidden bg-primary", className), style: outer, children: _jsx("div", { className: cx("absolute top-0 left-0 origin-top-left overflow-hidden bg-primary text-primary", s == null && "invisible", stageClassName), style: { width, height, transform: s == null ? undefined : `scale(${s})` }, children: children }) }));
38
+ }