tembro 6.0.3 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/components/actions/action-menu.d.ts +11 -1
- package/dist/components/calendar/calendar-range-scheduler.cjs +1 -0
- package/dist/components/calendar/calendar-range-scheduler.d.ts +20 -0
- package/dist/components/calendar/calendar-range-scheduler.js +1 -0
- package/dist/components/calendar/date-time-picker.cjs +1 -0
- package/dist/components/calendar/date-time-picker.d.ts +15 -0
- package/dist/components/calendar/date-time-picker.js +1 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/charts/chart-card.cjs +1 -0
- package/dist/components/charts/chart-card.d.ts +24 -0
- package/dist/components/charts/chart-card.js +1 -0
- package/dist/components/charts/index.d.ts +1 -0
- package/dist/components/command/advanced-command-menu.cjs +1 -0
- package/dist/components/command/advanced-command-menu.d.ts +25 -0
- package/dist/components/command/advanced-command-menu.js +1 -0
- package/dist/components/command/index.d.ts +1 -0
- package/dist/components/data-table/data-grid.cjs +1 -0
- package/dist/components/data-table/data-grid.d.ts +15 -0
- package/dist/components/data-table/data-grid.js +1 -0
- package/dist/components/data-table/index.d.ts +1 -0
- package/dist/components/data-table/public.d.ts +1 -0
- package/dist/components/display/index.d.ts +0 -2
- package/dist/components/feedback/async-boundary.cjs +1 -0
- package/dist/components/feedback/async-boundary.d.ts +12 -0
- package/dist/components/feedback/async-boundary.js +1 -0
- package/dist/components/feedback/index.d.ts +1 -0
- package/dist/components/filters/index.d.ts +0 -1
- package/dist/components/inputs/index.d.ts +1 -0
- package/dist/components/inputs/multi-select.cjs +1 -0
- package/dist/components/inputs/multi-select.d.ts +16 -0
- package/dist/components/inputs/multi-select.js +1 -0
- package/dist/components/inputs/public.d.ts +1 -0
- package/dist/components/inputs/time-picker.d.ts +11 -3
- package/dist/components/modern/file-manager.cjs +1 -0
- package/dist/components/modern/file-manager.d.ts +20 -0
- package/dist/components/modern/file-manager.js +1 -0
- package/dist/components/modern/index.d.ts +4 -1
- package/dist/components/modern/map.cjs +1 -0
- package/dist/components/modern/map.d.ts +29 -0
- package/dist/components/modern/map.js +1 -0
- package/dist/components/modern/media-player.cjs +1 -0
- package/dist/components/modern/media-player.d.ts +21 -0
- package/dist/components/modern/media-player.js +1 -0
- package/dist/components/modern/spreadsheet.cjs +1 -0
- package/dist/components/modern/spreadsheet.d.ts +35 -0
- package/dist/components/modern/spreadsheet.js +1 -0
- package/dist/components/notifications/action-toast.cjs +1 -0
- package/dist/components/notifications/action-toast.d.ts +16 -0
- package/dist/components/notifications/action-toast.js +1 -0
- package/dist/components/notifications/index.d.ts +1 -0
- package/dist/components/overlay/drawer.d.ts +3 -1
- package/dist/components/patterns/index.d.ts +1 -0
- package/dist/components/patterns/permissions-matrix.cjs +1 -0
- package/dist/components/patterns/permissions-matrix.d.ts +19 -0
- package/dist/components/patterns/permissions-matrix.js +1 -0
- package/dist/components/patterns/public.d.ts +1 -0
- package/dist/index.d.ts +0 -2
- package/dist/src/components/actions/action-menu.cjs +1 -1
- package/dist/src/components/actions/action-menu.js +47 -39
- package/dist/src/components/actions/copy-button.cjs +1 -1
- package/dist/src/components/actions/copy-button.js +4 -1
- package/dist/src/components/calendar/calendar-range-scheduler.cjs +1 -0
- package/dist/src/components/calendar/calendar-range-scheduler.js +75 -0
- package/dist/src/components/calendar/date-picker.cjs +1 -1
- package/dist/src/components/calendar/date-picker.js +5 -8
- package/dist/src/components/calendar/date-range-picker.cjs +1 -1
- package/dist/src/components/calendar/date-range-picker.js +12 -27
- package/dist/src/components/calendar/date-time-picker.cjs +1 -0
- package/dist/src/components/calendar/date-time-picker.js +36 -0
- package/dist/src/components/calendar/index.cjs +1 -1
- package/dist/src/components/calendar/index.js +5 -3
- package/dist/src/components/charts/chart-card.cjs +1 -0
- package/dist/src/components/charts/chart-card.js +55 -0
- package/dist/src/components/charts/index.cjs +1 -1
- package/dist/src/components/charts/index.js +3 -2
- package/dist/src/components/command/advanced-command-menu.cjs +1 -0
- package/dist/src/components/command/advanced-command-menu.js +57 -0
- package/dist/src/components/command/index.cjs +1 -1
- package/dist/src/components/command/index.js +3 -2
- package/dist/src/components/data-table/data-grid.cjs +1 -0
- package/dist/src/components/data-table/data-grid.js +32 -0
- package/dist/src/components/data-table/data-table-saved-filters.cjs +1 -1
- package/dist/src/components/data-table/data-table-saved-filters.js +11 -11
- package/dist/src/components/data-table/data-table.cjs +1 -1
- package/dist/src/components/data-table/data-table.js +175 -174
- package/dist/src/components/data-table/index.cjs +1 -1
- package/dist/src/components/data-table/index.js +15 -14
- package/dist/src/components/data-table/public.cjs +1 -1
- package/dist/src/components/data-table/public.js +3 -2
- package/dist/src/components/display/index.cjs +1 -1
- package/dist/src/components/display/index.js +9 -11
- package/dist/src/components/feedback/async-boundary.cjs +1 -0
- package/dist/src/components/feedback/async-boundary.js +22 -0
- package/dist/src/components/feedback/index.cjs +1 -1
- package/dist/src/components/feedback/index.js +2 -1
- package/dist/src/components/filters/index.cjs +1 -1
- package/dist/src/components/filters/index.js +2 -3
- package/dist/src/components/inputs/color-picker.cjs +1 -1
- package/dist/src/components/inputs/color-picker.js +63 -46
- package/dist/src/components/inputs/index.cjs +1 -1
- package/dist/src/components/inputs/index.js +13 -12
- package/dist/src/components/inputs/multi-select.cjs +1 -0
- package/dist/src/components/inputs/multi-select.js +80 -0
- package/dist/src/components/inputs/public.cjs +1 -1
- package/dist/src/components/inputs/public.js +13 -12
- package/dist/src/components/inputs/slider.cjs +1 -1
- package/dist/src/components/inputs/slider.js +3 -3
- package/dist/src/components/inputs/tag-input.cjs +1 -1
- package/dist/src/components/inputs/tag-input.js +7 -4
- package/dist/src/components/inputs/time-picker.cjs +1 -1
- package/dist/src/components/inputs/time-picker.js +90 -34
- package/dist/src/components/modern/file-manager.cjs +1 -0
- package/dist/src/components/modern/file-manager.js +75 -0
- package/dist/src/components/modern/index.cjs +1 -1
- package/dist/src/components/modern/index.js +7 -4
- package/dist/src/components/modern/map.cjs +1 -0
- package/dist/src/components/modern/map.js +158 -0
- package/dist/src/components/modern/media-player.cjs +1 -0
- package/dist/src/components/modern/media-player.js +176 -0
- package/dist/src/components/modern/resizable-panel.cjs +1 -1
- package/dist/src/components/modern/resizable-panel.js +39 -32
- package/dist/src/components/modern/spreadsheet.cjs +1 -0
- package/dist/src/components/modern/spreadsheet.js +97 -0
- package/dist/src/components/navigation/pagination.cjs +1 -1
- package/dist/src/components/navigation/pagination.js +5 -5
- package/dist/src/components/notifications/action-toast.cjs +1 -0
- package/dist/src/components/notifications/action-toast.js +49 -0
- package/dist/src/components/notifications/index.cjs +1 -1
- package/dist/src/components/notifications/index.js +3 -2
- package/dist/src/components/notifications/toast.cjs +1 -1
- package/dist/src/components/notifications/toast.js +68 -63
- package/dist/src/components/overlay/alert-dialog.cjs +1 -1
- package/dist/src/components/overlay/alert-dialog.js +8 -6
- package/dist/src/components/overlay/drawer.cjs +1 -1
- package/dist/src/components/overlay/drawer.js +38 -20
- package/dist/src/components/patterns/index.cjs +1 -1
- package/dist/src/components/patterns/index.js +2 -1
- package/dist/src/components/patterns/permissions-matrix.cjs +1 -0
- package/dist/src/components/patterns/permissions-matrix.js +43 -0
- package/dist/src/components/patterns/public.cjs +1 -1
- package/dist/src/components/patterns/public.js +2 -1
- package/dist/src/components/ui/checkbox/index.cjs +1 -1
- package/dist/src/components/ui/checkbox/index.js +2 -1
- package/dist/src/components/ui/dialog/index.cjs +1 -1
- package/dist/src/components/ui/dialog/index.js +8 -8
- package/dist/src/components/ui/dropdown-menu/index.cjs +1 -1
- package/dist/src/components/ui/dropdown-menu/index.js +21 -19
- package/dist/src/components/ui/input/group.cjs +1 -1
- package/dist/src/components/ui/input/group.js +8 -8
- package/dist/src/components/ui/popover/index.cjs +1 -1
- package/dist/src/components/ui/popover/index.js +10 -10
- package/dist/src/components/ui/tabs/index.cjs +1 -1
- package/dist/src/components/ui/tabs/index.js +6 -6
- package/dist/src/components/ui/toggle-group/index.cjs +1 -1
- package/dist/src/components/ui/toggle-group/index.js +2 -2
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +111 -104
- package/dist/src/public-component-surface.cjs +1 -1
- package/dist/src/public-component-surface.js +59 -14
- package/package.json +1 -2
- package/packages/cli/dist/index.cjs +45 -165
- package/packages/cli/vendor/src/components/actions/action-menu.tsx +47 -19
- package/packages/cli/vendor/src/components/actions/copy-button.tsx +2 -2
- package/packages/cli/vendor/src/components/calendar/calendar-range-scheduler.tsx +72 -0
- package/packages/cli/vendor/src/components/calendar/date-picker.tsx +2 -5
- package/packages/cli/vendor/src/components/calendar/date-range-picker.tsx +5 -12
- package/packages/cli/vendor/src/components/calendar/date-time-picker.tsx +53 -0
- package/packages/cli/vendor/src/components/calendar/index.ts +5 -3
- package/packages/cli/vendor/src/components/charts/chart-card.tsx +81 -0
- package/packages/cli/vendor/src/components/charts/index.ts +1 -0
- package/packages/cli/vendor/src/components/command/advanced-command-menu.tsx +88 -0
- package/packages/cli/vendor/src/components/command/index.ts +2 -1
- package/packages/cli/vendor/src/components/data-table/data-grid.tsx +59 -0
- package/packages/cli/vendor/src/components/data-table/data-table.tsx +45 -34
- package/packages/cli/vendor/src/components/data-table/index.ts +3 -2
- package/packages/cli/vendor/src/components/data-table/public.ts +1 -0
- package/packages/cli/vendor/src/components/display/index.ts +3 -5
- package/packages/cli/vendor/src/components/feedback/async-boundary.tsx +36 -0
- package/packages/cli/vendor/src/components/feedback/index.ts +1 -0
- package/packages/cli/vendor/src/components/filters/index.ts +0 -1
- package/packages/cli/vendor/src/components/inputs/color-picker.tsx +15 -1
- package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/multi-select.tsx +75 -0
- package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/slider.tsx +3 -3
- package/packages/cli/vendor/src/components/inputs/tag-input.tsx +4 -4
- package/packages/cli/vendor/src/components/inputs/time-picker.tsx +91 -38
- package/packages/cli/vendor/src/components/modern/file-manager.tsx +79 -0
- package/packages/cli/vendor/src/components/modern/index.ts +4 -1
- package/packages/cli/vendor/src/components/modern/map.tsx +146 -0
- package/packages/cli/vendor/src/components/modern/media-player.tsx +182 -0
- package/packages/cli/vendor/src/components/modern/resizable-panel.tsx +15 -5
- package/packages/cli/vendor/src/components/modern/spreadsheet.tsx +155 -0
- package/packages/cli/vendor/src/components/navigation/pagination.tsx +9 -5
- package/packages/cli/vendor/src/components/notifications/action-toast.tsx +78 -0
- package/packages/cli/vendor/src/components/notifications/index.ts +2 -1
- package/packages/cli/vendor/src/components/notifications/toast.tsx +20 -10
- package/packages/cli/vendor/src/components/overlay/alert-dialog.tsx +3 -5
- package/packages/cli/vendor/src/components/overlay/drawer.tsx +22 -11
- package/packages/cli/vendor/src/components/patterns/index.ts +1 -0
- package/packages/cli/vendor/src/components/patterns/permissions-matrix.tsx +71 -0
- package/packages/cli/vendor/src/components/patterns/public.ts +1 -0
- package/packages/cli/vendor/src/components/ui/checkbox/index.tsx +3 -1
- package/packages/cli/vendor/src/components/ui/dialog/index.tsx +4 -4
- package/packages/cli/vendor/src/components/ui/dropdown-menu/index.tsx +18 -22
- package/packages/cli/vendor/src/components/ui/popover/index.tsx +4 -4
- package/packages/cli/vendor/src/components/ui/tabs/index.tsx +8 -3
- package/packages/cli/vendor/src/components/ui/toggle-group/index.tsx +6 -6
- package/packages/cli/vendor/src/index.ts +0 -2
- package/packages/cli/vendor/src/public-component-surface.ts +13 -4
- package/packages/cli/vendor/templates/showcase/src/showcase/data/registry.ts +6 -6
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +23 -8
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
- package/packages/cli/vendor/templates/showcase/src/showcase/sections/PatternsSection.tsx +1 -1
- package/packages/cli/vendor/templates/styles/globals.css +0 -120
- package/registry.json +3197 -2794
- package/dist/components/display/info-card.cjs +0 -1
- package/dist/components/display/info-card.d.ts +0 -51
- package/dist/components/display/info-card.js +0 -1
- package/dist/components/display/statistic.cjs +0 -1
- package/dist/components/display/statistic.d.ts +0 -25
- package/dist/components/display/statistic.js +0 -1
- package/dist/components/filters/filter-bar.cjs +0 -1
- package/dist/components/filters/filter-bar.d.ts +0 -28
- package/dist/components/filters/filter-bar.js +0 -1
- package/dist/components/modern/image-cropper.cjs +0 -1
- package/dist/components/modern/image-cropper.d.ts +0 -39
- package/dist/components/modern/image-cropper.js +0 -1
- package/dist/components/ui/number-field/index.cjs +0 -1
- package/dist/components/ui/number-field/index.d.ts +0 -18
- package/dist/components/ui/number-field/index.js +0 -1
- package/dist/components/ui/toolbar/index.cjs +0 -1
- package/dist/components/ui/toolbar/index.d.ts +0 -14
- package/dist/components/ui/toolbar/index.js +0 -1
- package/dist/src/components/display/info-card.cjs +0 -1
- package/dist/src/components/display/info-card.js +0 -124
- package/dist/src/components/display/statistic.cjs +0 -1
- package/dist/src/components/display/statistic.js +0 -104
- package/dist/src/components/filters/filter-bar.cjs +0 -1
- package/dist/src/components/filters/filter-bar.js +0 -103
- package/dist/src/components/modern/image-cropper.cjs +0 -1
- package/dist/src/components/modern/image-cropper.js +0 -143
- package/dist/src/components/ui/number-field/index.cjs +0 -1
- package/dist/src/components/ui/number-field/index.js +0 -73
- package/dist/src/components/ui/toolbar/index.cjs +0 -1
- package/dist/src/components/ui/toolbar/index.js +0 -50
- package/packages/cli/vendor/src/components/display/info-card.tsx +0 -195
- package/packages/cli/vendor/src/components/display/statistic.tsx +0 -114
- package/packages/cli/vendor/src/components/filters/filter-bar.tsx +0 -163
- package/packages/cli/vendor/src/components/modern/image-cropper.tsx +0 -226
- package/packages/cli/vendor/src/components/ui/number-field/index.tsx +0 -89
- package/packages/cli/vendor/src/components/ui/toolbar/index.tsx +0 -67
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { MaximizeIcon, PauseIcon, PlayIcon, RotateCcwIcon, Volume1Icon, Volume2Icon, VolumeXIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { Badge } from "@/components/ui/badge"
|
|
7
|
+
import { Button } from "@/components/ui/button"
|
|
8
|
+
import { cn } from "@/lib/utils"
|
|
9
|
+
|
|
10
|
+
export type MediaTrack = {
|
|
11
|
+
src: string
|
|
12
|
+
kind?: React.TrackHTMLAttributes<HTMLTrackElement>["kind"]
|
|
13
|
+
srcLang?: string
|
|
14
|
+
label?: string
|
|
15
|
+
default?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type MediaPlayerProps = Omit<React.ComponentProps<"video">, "controls" | "children" | "onTimeUpdate" | "onVolumeChange"> & {
|
|
19
|
+
src: string
|
|
20
|
+
title?: React.ReactNode
|
|
21
|
+
description?: React.ReactNode
|
|
22
|
+
tracks?: MediaTrack[]
|
|
23
|
+
actions?: React.ReactNode
|
|
24
|
+
aspectRatio?: "video" | "square" | "wide" | string
|
|
25
|
+
showMeta?: boolean
|
|
26
|
+
onTimeChange?: (time: number, duration: number) => void
|
|
27
|
+
onVolumeValueChange?: (volume: number, muted: boolean) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatTime(seconds: number) {
|
|
31
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "0:00"
|
|
32
|
+
const minutes = Math.floor(seconds / 60)
|
|
33
|
+
const rest = Math.floor(seconds % 60).toString().padStart(2, "0")
|
|
34
|
+
return `${minutes}:${rest}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getAspectRatioClassName(value: NonNullable<MediaPlayerProps["aspectRatio"]>) {
|
|
38
|
+
if (value === "video") return "aspect-video"
|
|
39
|
+
if (value === "square") return "aspect-square"
|
|
40
|
+
if (value === "wide") return "aspect-[21/9]"
|
|
41
|
+
return ""
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function MediaPlayer({
|
|
45
|
+
src,
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
tracks = [],
|
|
49
|
+
actions,
|
|
50
|
+
aspectRatio = "video",
|
|
51
|
+
showMeta = true,
|
|
52
|
+
poster,
|
|
53
|
+
autoPlay,
|
|
54
|
+
muted: mutedProp,
|
|
55
|
+
loop,
|
|
56
|
+
playsInline = true,
|
|
57
|
+
preload = "metadata",
|
|
58
|
+
onTimeChange,
|
|
59
|
+
onVolumeValueChange,
|
|
60
|
+
className,
|
|
61
|
+
...props
|
|
62
|
+
}: MediaPlayerProps) {
|
|
63
|
+
const ref = React.useRef<HTMLVideoElement>(null)
|
|
64
|
+
const [playing, setPlaying] = React.useState(Boolean(autoPlay))
|
|
65
|
+
const [duration, setDuration] = React.useState(0)
|
|
66
|
+
const [time, setTime] = React.useState(0)
|
|
67
|
+
const [volume, setVolume] = React.useState(1)
|
|
68
|
+
const [muted, setMuted] = React.useState(Boolean(mutedProp))
|
|
69
|
+
const [ready, setReady] = React.useState(false)
|
|
70
|
+
|
|
71
|
+
const syncFromElement = React.useCallback(() => {
|
|
72
|
+
const element = ref.current
|
|
73
|
+
if (!element) return
|
|
74
|
+
setPlaying(!element.paused)
|
|
75
|
+
setDuration(element.duration || 0)
|
|
76
|
+
setTime(element.currentTime || 0)
|
|
77
|
+
setVolume(element.volume)
|
|
78
|
+
setMuted(element.muted)
|
|
79
|
+
onTimeChange?.(element.currentTime || 0, element.duration || 0)
|
|
80
|
+
}, [onTimeChange])
|
|
81
|
+
|
|
82
|
+
const togglePlaying = async () => {
|
|
83
|
+
const element = ref.current
|
|
84
|
+
if (!element) return
|
|
85
|
+
if (element.paused) await element.play()
|
|
86
|
+
else element.pause()
|
|
87
|
+
syncFromElement()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const seek = (value: number) => {
|
|
91
|
+
const element = ref.current
|
|
92
|
+
if (!element) return
|
|
93
|
+
element.currentTime = value
|
|
94
|
+
setTime(value)
|
|
95
|
+
onTimeChange?.(value, duration)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const updateVolume = (nextVolume: number) => {
|
|
99
|
+
const element = ref.current
|
|
100
|
+
const safeVolume = Math.max(0, Math.min(1, nextVolume))
|
|
101
|
+
if (element) {
|
|
102
|
+
element.volume = safeVolume
|
|
103
|
+
element.muted = safeVolume === 0 ? true : muted
|
|
104
|
+
}
|
|
105
|
+
setVolume(safeVolume)
|
|
106
|
+
const nextMuted = safeVolume === 0 ? true : muted
|
|
107
|
+
setMuted(nextMuted)
|
|
108
|
+
onVolumeValueChange?.(safeVolume, nextMuted)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const toggleMuted = () => {
|
|
112
|
+
const element = ref.current
|
|
113
|
+
const nextMuted = !muted
|
|
114
|
+
if (element) element.muted = nextMuted
|
|
115
|
+
setMuted(nextMuted)
|
|
116
|
+
onVolumeValueChange?.(volume, nextMuted)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const VolumeIcon = muted || volume === 0 ? VolumeXIcon : volume < 0.6 ? Volume1Icon : Volume2Icon
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<section data-slot="media-player" className={cn("overflow-hidden rounded-lg border bg-card shadow-sm", className)}>
|
|
123
|
+
<div className={cn("relative bg-black", getAspectRatioClassName(aspectRatio))} style={!["video", "square", "wide"].includes(String(aspectRatio)) ? { aspectRatio: String(aspectRatio) } : undefined}>
|
|
124
|
+
<video
|
|
125
|
+
ref={ref}
|
|
126
|
+
src={src}
|
|
127
|
+
poster={poster}
|
|
128
|
+
autoPlay={autoPlay}
|
|
129
|
+
muted={muted}
|
|
130
|
+
loop={loop}
|
|
131
|
+
playsInline={playsInline}
|
|
132
|
+
preload={preload}
|
|
133
|
+
className="size-full object-cover"
|
|
134
|
+
onLoadedMetadata={() => { setReady(true); syncFromElement() }}
|
|
135
|
+
onPlay={() => setPlaying(true)}
|
|
136
|
+
onPause={() => setPlaying(false)}
|
|
137
|
+
onTimeUpdate={syncFromElement}
|
|
138
|
+
onVolumeChange={syncFromElement}
|
|
139
|
+
{...props}
|
|
140
|
+
>
|
|
141
|
+
{tracks.map((track) => <track key={`${track.src}-${track.srcLang ?? ""}`} {...track} />)}
|
|
142
|
+
</video>
|
|
143
|
+
<button type="button" aria-label={playing ? "Pause media" : "Play media"} className="absolute inset-0 grid place-items-center bg-black/0 text-white opacity-0 transition hover:bg-black/20 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" onClick={() => void togglePlaying()}>
|
|
144
|
+
<span className="grid size-14 place-items-center rounded-full bg-black/55 backdrop-blur">{playing ? <PauseIcon className="size-6" /> : <PlayIcon className="size-6 translate-x-0.5" />}</span>
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="grid gap-3 p-3">
|
|
149
|
+
{showMeta && (title || description || actions) ? (
|
|
150
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
151
|
+
<div className="min-w-0">
|
|
152
|
+
{title ? <h3 className="truncate text-sm font-semibold">{title}</h3> : null}
|
|
153
|
+
{description ? <p className="mt-1 text-xs text-muted-foreground">{description}</p> : null}
|
|
154
|
+
</div>
|
|
155
|
+
<div className="flex items-center gap-2">
|
|
156
|
+
{!ready ? <Badge tone="muted" variant="soft">loading</Badge> : null}
|
|
157
|
+
{actions}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
) : null}
|
|
161
|
+
|
|
162
|
+
<div className="grid gap-2">
|
|
163
|
+
<input aria-label="Media progress" type="range" min={0} max={duration || 0} step={0.1} value={Math.min(time, duration || time)} onChange={(event) => seek(Number(event.target.value))} className="h-2 w-full cursor-pointer accent-primary" />
|
|
164
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
165
|
+
<div className="flex items-center gap-1">
|
|
166
|
+
<Button type="button" size="icon-sm" variant="outline" iconOnly aria-label={playing ? "Pause" : "Play"} onClick={() => void togglePlaying()}>{playing ? <PauseIcon /> : <PlayIcon />}</Button>
|
|
167
|
+
<Button type="button" size="icon-sm" variant="ghost" iconOnly aria-label="Restart" onClick={() => seek(0)}><RotateCcwIcon /></Button>
|
|
168
|
+
<span className="min-w-20 text-xs tabular-nums text-muted-foreground">{formatTime(time)} / {formatTime(duration)}</span>
|
|
169
|
+
</div>
|
|
170
|
+
<div className="flex items-center gap-2">
|
|
171
|
+
<Button type="button" size="icon-sm" variant="ghost" iconOnly aria-label={muted ? "Unmute" : "Mute"} onClick={toggleMuted}><VolumeIcon /></Button>
|
|
172
|
+
<input aria-label="Volume" type="range" min={0} max={1} step={0.01} value={muted ? 0 : volume} onChange={(event) => updateVolume(Number(event.target.value))} className="h-2 w-24 cursor-pointer accent-primary" />
|
|
173
|
+
<Button type="button" size="icon-sm" variant="ghost" iconOnly aria-label="Fullscreen" onClick={() => ref.current?.requestFullscreen?.()}><MaximizeIcon /></Button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</section>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { MediaPlayer }
|
|
@@ -123,7 +123,7 @@ function ResizablePanelGroup({ direction = "horizontal", className, children, ..
|
|
|
123
123
|
ref={groupRef}
|
|
124
124
|
data-slot="resizable-panel-group"
|
|
125
125
|
data-direction={direction}
|
|
126
|
-
className={cn("flex
|
|
126
|
+
className={cn("flex", direction === "horizontal" ? "flex-row items-stretch" : "flex-col", className)}
|
|
127
127
|
{...props}
|
|
128
128
|
>
|
|
129
129
|
{processedChildren}
|
|
@@ -140,7 +140,7 @@ function ResizablePanel({ className, style, defaultSize, minSize: _minSize, ...p
|
|
|
140
140
|
return (
|
|
141
141
|
<div
|
|
142
142
|
data-slot="resizable-panel"
|
|
143
|
-
className={cn("min-h-24 overflow-auto rounded-lg border bg-card p-3", className)}
|
|
143
|
+
className={cn("min-h-24 min-w-0 overflow-auto rounded-[var(--aui-card-radius,var(--radius-lg))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-3 shadow-[var(--aui-card-shadow,0_10px_24px_rgba(15,23,42,0.07))]", className)}
|
|
144
144
|
style={{ flexBasis: `${basis}%`, flexGrow: 0, flexShrink: 0, ...style }}
|
|
145
145
|
{...props}
|
|
146
146
|
/>
|
|
@@ -157,9 +157,10 @@ function ResizableHandle({ className, ...props }: ResizableHandleProps) {
|
|
|
157
157
|
type="button"
|
|
158
158
|
data-slot="resizable-handle"
|
|
159
159
|
aria-label="Resize panels"
|
|
160
|
+
aria-orientation={context?.direction ?? "horizontal"}
|
|
160
161
|
className={cn(
|
|
161
|
-
"relative flex shrink-0 items-center justify-center
|
|
162
|
-
context?.direction === "horizontal" ? "w-
|
|
162
|
+
"group relative flex shrink-0 items-center justify-center text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring",
|
|
163
|
+
context?.direction === "horizontal" ? "w-4 cursor-col-resize px-1" : "h-4 cursor-row-resize py-1",
|
|
163
164
|
className
|
|
164
165
|
)}
|
|
165
166
|
onPointerDown={(event) => {
|
|
@@ -206,7 +207,16 @@ function ResizableHandle({ className, ...props }: ResizableHandleProps) {
|
|
|
206
207
|
}}
|
|
207
208
|
{...props}
|
|
208
209
|
>
|
|
209
|
-
<
|
|
210
|
+
<span
|
|
211
|
+
aria-hidden="true"
|
|
212
|
+
className={cn(
|
|
213
|
+
"absolute rounded-full bg-border transition-colors group-hover:bg-ring/55",
|
|
214
|
+
context?.direction === "horizontal" ? "inset-y-2 left-1/2 w-px -translate-x-1/2" : "inset-x-2 top-1/2 h-px -translate-y-1/2"
|
|
215
|
+
)}
|
|
216
|
+
/>
|
|
217
|
+
<span className="relative grid size-5 place-items-center rounded-full border border-border/75 bg-background shadow-sm transition-colors group-hover:border-ring/45">
|
|
218
|
+
<GripVerticalIcon className={cn("size-3", context?.direction === "vertical" && "rotate-90")} />
|
|
219
|
+
</span>
|
|
210
220
|
</button>
|
|
211
221
|
)
|
|
212
222
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { PlusIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button"
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
export type SpreadsheetColumn = {
|
|
10
|
+
key: string
|
|
11
|
+
label: React.ReactNode
|
|
12
|
+
width?: number | string
|
|
13
|
+
align?: "left" | "center" | "right"
|
|
14
|
+
readOnly?: boolean
|
|
15
|
+
formatter?: (value: string, row: SpreadsheetRow) => React.ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SpreadsheetRow = {
|
|
19
|
+
key: string
|
|
20
|
+
cells: Record<string, string>
|
|
21
|
+
readOnly?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SpreadsheetSelection = {
|
|
25
|
+
rowKey: string
|
|
26
|
+
columnKey: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SpreadsheetProps = Omit<React.ComponentProps<"div">, "onChange"> & {
|
|
30
|
+
columns: SpreadsheetColumn[]
|
|
31
|
+
rows?: SpreadsheetRow[]
|
|
32
|
+
defaultRows?: SpreadsheetRow[]
|
|
33
|
+
onRowsChange?: (rows: SpreadsheetRow[], selection?: SpreadsheetSelection) => void
|
|
34
|
+
onCellChange?: (value: string, selection: SpreadsheetSelection, rows: SpreadsheetRow[]) => void
|
|
35
|
+
selectedCell?: SpreadsheetSelection
|
|
36
|
+
defaultSelectedCell?: SpreadsheetSelection
|
|
37
|
+
onSelectedCellChange?: (selection: SpreadsheetSelection) => void
|
|
38
|
+
readOnly?: boolean
|
|
39
|
+
showRowNumbers?: boolean
|
|
40
|
+
showAddRow?: boolean
|
|
41
|
+
addRowLabel?: React.ReactNode
|
|
42
|
+
empty?: React.ReactNode
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function alignClassName(align: SpreadsheetColumn["align"]) {
|
|
46
|
+
if (align === "center") return "text-center"
|
|
47
|
+
if (align === "right") return "text-right"
|
|
48
|
+
return "text-left"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createEmptyRow(columns: SpreadsheetColumn[], index: number): SpreadsheetRow {
|
|
52
|
+
return {
|
|
53
|
+
key: `row-${Date.now()}-${index}`,
|
|
54
|
+
cells: Object.fromEntries(columns.map((column) => [column.key, ""])),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Spreadsheet({
|
|
59
|
+
columns,
|
|
60
|
+
rows: rowsProp,
|
|
61
|
+
defaultRows = [],
|
|
62
|
+
onRowsChange,
|
|
63
|
+
onCellChange,
|
|
64
|
+
selectedCell,
|
|
65
|
+
defaultSelectedCell,
|
|
66
|
+
onSelectedCellChange,
|
|
67
|
+
readOnly = false,
|
|
68
|
+
showRowNumbers = true,
|
|
69
|
+
showAddRow = true,
|
|
70
|
+
addRowLabel = "Add row",
|
|
71
|
+
empty = "No rows yet.",
|
|
72
|
+
className,
|
|
73
|
+
...props
|
|
74
|
+
}: SpreadsheetProps) {
|
|
75
|
+
const [internalRows, setInternalRows] = React.useState(defaultRows)
|
|
76
|
+
const [internalSelectedCell, setInternalSelectedCell] = React.useState<SpreadsheetSelection | undefined>(defaultSelectedCell)
|
|
77
|
+
const rows = rowsProp ?? internalRows
|
|
78
|
+
const activeCell = selectedCell ?? internalSelectedCell
|
|
79
|
+
|
|
80
|
+
const commitRows = (nextRows: SpreadsheetRow[], selection?: SpreadsheetSelection) => {
|
|
81
|
+
if (rowsProp === undefined) setInternalRows(nextRows)
|
|
82
|
+
onRowsChange?.(nextRows, selection)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const selectCell = (selection: SpreadsheetSelection) => {
|
|
86
|
+
if (selectedCell === undefined) setInternalSelectedCell(selection)
|
|
87
|
+
onSelectedCellChange?.(selection)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const updateCell = (selection: SpreadsheetSelection, value: string) => {
|
|
91
|
+
const nextRows = rows.map((row) => row.key === selection.rowKey ? { ...row, cells: { ...row.cells, [selection.columnKey]: value } } : row)
|
|
92
|
+
commitRows(nextRows, selection)
|
|
93
|
+
onCellChange?.(value, selection, nextRows)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const addRow = () => {
|
|
97
|
+
const nextRows = [...rows, createEmptyRow(columns, rows.length + 1)]
|
|
98
|
+
commitRows(nextRows)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div data-slot="spreadsheet" className={cn("grid min-w-0 gap-3 rounded-lg border bg-card p-3 shadow-sm", className)} {...props}>
|
|
103
|
+
<div className="overflow-auto rounded-lg border">
|
|
104
|
+
<table className="w-full min-w-[48rem] border-collapse text-sm">
|
|
105
|
+
<thead className="sticky top-0 z-10 bg-muted/80 backdrop-blur">
|
|
106
|
+
<tr>
|
|
107
|
+
{showRowNumbers ? <th className="w-12 border-b border-r px-3 py-2 text-left text-xs font-medium text-muted-foreground">#</th> : null}
|
|
108
|
+
{columns.map((column) => (
|
|
109
|
+
<th key={column.key} className={cn("border-b border-r px-3 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground last:border-r-0", alignClassName(column.align))} style={{ width: column.width }}>
|
|
110
|
+
{column.label}
|
|
111
|
+
</th>
|
|
112
|
+
))}
|
|
113
|
+
</tr>
|
|
114
|
+
</thead>
|
|
115
|
+
<tbody>
|
|
116
|
+
{rows.length ? rows.map((row, rowIndex) => (
|
|
117
|
+
<tr key={row.key} data-readonly={row.readOnly || undefined} className="odd:bg-background even:bg-muted/18">
|
|
118
|
+
{showRowNumbers ? <th className="border-r px-3 py-1.5 text-left text-xs font-medium text-muted-foreground">{rowIndex + 1}</th> : null}
|
|
119
|
+
{columns.map((column) => {
|
|
120
|
+
const selection = { rowKey: row.key, columnKey: column.key }
|
|
121
|
+
const selected = activeCell?.rowKey === row.key && activeCell.columnKey === column.key
|
|
122
|
+
const value = row.cells[column.key] ?? ""
|
|
123
|
+
const cellReadOnly = readOnly || row.readOnly || column.readOnly
|
|
124
|
+
return (
|
|
125
|
+
<td key={column.key} data-selected={selected || undefined} className="border-r border-t p-0 last:border-r-0 data-[selected=true]:ring-2 data-[selected=true]:ring-inset data-[selected=true]:ring-primary/55">
|
|
126
|
+
{cellReadOnly && column.formatter ? (
|
|
127
|
+
<div className={cn("min-h-10 px-3 py-2", alignClassName(column.align))}>{column.formatter(value, row)}</div>
|
|
128
|
+
) : (
|
|
129
|
+
<input
|
|
130
|
+
aria-label={`${String(column.label)} row ${rowIndex + 1}`}
|
|
131
|
+
value={value}
|
|
132
|
+
readOnly={cellReadOnly}
|
|
133
|
+
className={cn("h-10 w-full bg-transparent px-3 outline-none transition focus:bg-background read-only:cursor-default", alignClassName(column.align))}
|
|
134
|
+
onFocus={() => selectCell(selection)}
|
|
135
|
+
onChange={(event) => updateCell(selection, event.target.value)}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
</td>
|
|
139
|
+
)
|
|
140
|
+
})}
|
|
141
|
+
</tr>
|
|
142
|
+
)) : (
|
|
143
|
+
<tr>
|
|
144
|
+
<td colSpan={columns.length + (showRowNumbers ? 1 : 0)} className="h-32 text-center text-sm text-muted-foreground">{empty}</td>
|
|
145
|
+
</tr>
|
|
146
|
+
)}
|
|
147
|
+
</tbody>
|
|
148
|
+
</table>
|
|
149
|
+
</div>
|
|
150
|
+
{showAddRow && !readOnly ? <Button type="button" className="w-fit" variant="outline" size="sm" leftIcon={<PlusIcon />} onClick={addRow}>{addRowLabel}</Button> : null}
|
|
151
|
+
</div>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { Spreadsheet }
|
|
@@ -105,13 +105,13 @@ function Pagination({
|
|
|
105
105
|
data-slot="pagination"
|
|
106
106
|
aria-label="Pagination"
|
|
107
107
|
className={cn(
|
|
108
|
-
"flex flex-wrap items-center justify-center gap-1 rounded-
|
|
108
|
+
"flex flex-wrap items-center justify-center gap-1.5 rounded-[var(--radius-lg)] border border-border/72 bg-background/84 p-1.5 shadow-[0_1px_2px_rgba(15,23,42,0.04)] backdrop-blur",
|
|
109
109
|
className
|
|
110
110
|
)}
|
|
111
111
|
{...props}
|
|
112
112
|
>
|
|
113
113
|
{showSummary && totalCount !== undefined && pageSize ? (
|
|
114
|
-
<div className="px-2 text-xs text-muted-foreground">
|
|
114
|
+
<div className="mr-1 rounded-[var(--radius-md)] bg-muted/55 px-2.5 py-1.5 text-xs font-medium text-muted-foreground">
|
|
115
115
|
{Math.min((currentPage - 1) * pageSize + 1, totalCount)}-{Math.min(currentPage * pageSize, totalCount)} of {totalCount}
|
|
116
116
|
</div>
|
|
117
117
|
) : null}
|
|
@@ -163,7 +163,11 @@ function Pagination({
|
|
|
163
163
|
aria-current={item === currentPage ? "page" : undefined}
|
|
164
164
|
aria-label={labels?.page?.(item) ?? `Page ${item}`}
|
|
165
165
|
onClick={() => goToPage(item)}
|
|
166
|
-
className={cn(
|
|
166
|
+
className={cn(
|
|
167
|
+
"tabular-nums",
|
|
168
|
+
item === currentPage && "shadow-[0_8px_20px_rgba(15,23,42,0.14)]",
|
|
169
|
+
item !== currentPage && "border-border/70 bg-background/80 shadow-none hover:bg-muted/70"
|
|
170
|
+
)}
|
|
167
171
|
>
|
|
168
172
|
{item}
|
|
169
173
|
</Button>
|
|
@@ -194,10 +198,10 @@ function Pagination({
|
|
|
194
198
|
</Button>
|
|
195
199
|
)}
|
|
196
200
|
{pageSizeOptions && pageSize && onPageSizeChange ? (
|
|
197
|
-
<label className="ml-1 inline-flex items-center gap-2 rounded-
|
|
201
|
+
<label className="ml-1 inline-flex h-9 items-center gap-2 rounded-[var(--radius-md)] border border-border/70 bg-background/90 px-3 text-xs font-medium text-muted-foreground">
|
|
198
202
|
<span>Rows</span>
|
|
199
203
|
<select
|
|
200
|
-
className="bg-transparent text-foreground outline-none"
|
|
204
|
+
className="rounded-sm bg-transparent text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
201
205
|
value={pageSize}
|
|
202
206
|
onChange={(event) => onPageSizeChange(Number(event.target.value))}
|
|
203
207
|
disabled={disabled}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { RotateCcwIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button"
|
|
7
|
+
import { useToast, type CreateToastInput, type ToastShortcutInput } from "@/components/notifications/toast"
|
|
8
|
+
|
|
9
|
+
export type ActionToastOptions = Omit<CreateToastInput, "action"> & {
|
|
10
|
+
actionLabel?: React.ReactNode
|
|
11
|
+
onAction?: () => void
|
|
12
|
+
undoLabel?: React.ReactNode
|
|
13
|
+
onUndo?: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ActionToastButtonProps = React.ComponentProps<typeof Button> & {
|
|
17
|
+
toast: ActionToastOptions
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ActionToastButton({ toast, children, onClick, ...props }: ActionToastButtonProps) {
|
|
21
|
+
const toaster = useToast()
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Button
|
|
25
|
+
{...props}
|
|
26
|
+
onClick={(event) => {
|
|
27
|
+
onClick?.(event)
|
|
28
|
+
if (event.defaultPrevented) return
|
|
29
|
+
showActionToast(toaster, toast)
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</Button>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ActionToastUndoButton({ onUndo, label = "Undo" }: { onUndo?: () => void; label?: React.ReactNode }) {
|
|
38
|
+
return (
|
|
39
|
+
<Button type="button" size="sm" variant="outline" leftIcon={<RotateCcwIcon className="size-3.5" />} onClick={onUndo}>
|
|
40
|
+
{label}
|
|
41
|
+
</Button>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function showActionToast(
|
|
46
|
+
toaster: ReturnType<typeof useToast>,
|
|
47
|
+
{
|
|
48
|
+
actionLabel = "Action",
|
|
49
|
+
onAction,
|
|
50
|
+
undoLabel,
|
|
51
|
+
onUndo,
|
|
52
|
+
...toast
|
|
53
|
+
}: ActionToastOptions
|
|
54
|
+
) {
|
|
55
|
+
const action = onUndo ? (
|
|
56
|
+
<ActionToastUndoButton onUndo={onUndo} label={undoLabel} />
|
|
57
|
+
) : onAction ? (
|
|
58
|
+
<Button type="button" size="sm" variant="outline" onClick={onAction}>{actionLabel}</Button>
|
|
59
|
+
) : undefined
|
|
60
|
+
|
|
61
|
+
return toaster.addToast({ ...toast, action })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function useActionToast() {
|
|
65
|
+
const toaster = useToast()
|
|
66
|
+
|
|
67
|
+
return React.useCallback(
|
|
68
|
+
(toast: ActionToastOptions | ToastShortcutInput) => {
|
|
69
|
+
if (typeof toast === "object" && toast !== null && !React.isValidElement(toast) && !Array.isArray(toast)) {
|
|
70
|
+
return showActionToast(toaster, toast as ActionToastOptions)
|
|
71
|
+
}
|
|
72
|
+
return toaster.info(toast)
|
|
73
|
+
},
|
|
74
|
+
[toaster]
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { ActionToastButton, showActionToast, useActionToast }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import {
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { createPortal } from "react-dom"
|
|
5
|
+
import { AlertCircleIcon, CheckCircle2Icon, InfoIcon, Loader2Icon, TriangleAlertIcon, XIcon } from "lucide-react"
|
|
5
6
|
|
|
6
7
|
import { Button } from "@/components/ui/button"
|
|
7
8
|
import { cn } from "@/lib/utils"
|
|
@@ -108,7 +109,12 @@ function ToastProvider({
|
|
|
108
109
|
pauseOnHover = true,
|
|
109
110
|
position = "top-right",
|
|
110
111
|
}: ToastProviderProps) {
|
|
111
|
-
const [toasts, setToasts] = React.useState<ToastItem[]>([])
|
|
112
|
+
const [toasts, setToasts] = React.useState<ToastItem[]>([])
|
|
113
|
+
const [portalTarget, setPortalTarget] = React.useState<HTMLElement | null>(null)
|
|
114
|
+
|
|
115
|
+
React.useEffect(() => {
|
|
116
|
+
setPortalTarget(document.body)
|
|
117
|
+
}, [])
|
|
112
118
|
|
|
113
119
|
const dismissToast = React.useCallback((id: string) => {
|
|
114
120
|
setToasts((current) => current.filter((toast) => toast.id !== id))
|
|
@@ -187,17 +193,21 @@ function ToastProvider({
|
|
|
187
193
|
[addToast, clearToasts, dismissToast, error, info, loading, promise, success, toasts, updateToast, warning]
|
|
188
194
|
)
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
<ToastContext.Provider value={value}>
|
|
192
|
-
{children}
|
|
196
|
+
const viewport = (
|
|
193
197
|
<div
|
|
194
198
|
data-slot="toast-viewport"
|
|
195
|
-
className={cn("fixed z-[100] flex w-[min(
|
|
199
|
+
className={cn("fixed z-[100] flex w-[min(calc(100vw-2rem),24rem)] flex-col gap-2 pointer-events-none", positionClassName[position])}
|
|
196
200
|
>
|
|
197
201
|
{toasts.map((toast) => (
|
|
198
202
|
<ToastCard key={toast.id} toast={toast} pauseOnHover={pauseOnHover} onDismiss={dismissToast} />
|
|
199
203
|
))}
|
|
200
204
|
</div>
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<ToastContext.Provider value={value}>
|
|
209
|
+
{children}
|
|
210
|
+
{portalTarget ? createPortal(viewport, portalTarget) : null}
|
|
201
211
|
</ToastContext.Provider>
|
|
202
212
|
)
|
|
203
213
|
}
|
|
@@ -225,9 +235,9 @@ function ToastCard({
|
|
|
225
235
|
|
|
226
236
|
return (
|
|
227
237
|
<div
|
|
228
|
-
data-slot="toast"
|
|
229
|
-
data-tone={tone}
|
|
230
|
-
className={cn("flex gap-3 rounded-lg border p-3 shadow-lg backdrop-blur", toneClassName[tone])}
|
|
238
|
+
data-slot="toast"
|
|
239
|
+
data-tone={tone}
|
|
240
|
+
className={cn("pointer-events-auto flex gap-3 rounded-lg border p-3 shadow-lg backdrop-blur", toneClassName[tone])}
|
|
231
241
|
role="status"
|
|
232
242
|
aria-live="polite"
|
|
233
243
|
onMouseEnter={() => setHovered(true)}
|
|
@@ -167,11 +167,9 @@ function AlertDialog({
|
|
|
167
167
|
</div>
|
|
168
168
|
) : null}
|
|
169
169
|
<DialogFooter>
|
|
170
|
-
<DialogClose render={
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
</Button>
|
|
174
|
-
)} />
|
|
170
|
+
<DialogClose render={<Button type="button" variant={cancelVariant} disabled={resolvedLoading} {...cancelButtonProps} />}>
|
|
171
|
+
{cancelLabel}
|
|
172
|
+
</DialogClose>
|
|
175
173
|
<Button
|
|
176
174
|
type="button"
|
|
177
175
|
variant={actionTone === "destructive" ? "destructive" : "default"}
|