@minutemailer/kit 1.1.6 → 1.2.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/components/donut-chart.d.ts +5 -0
- package/components/donut-chart.js +31 -0
- package/components/engagement-chart.d.ts +8 -0
- package/components/engagement-chart.js +41 -0
- package/components/image-slot.d.ts +24 -0
- package/components/image-slot.js +92 -0
- package/components/minutemailer-kit.d.ts +12 -3
- package/components/minutemailer-kit.js +10 -3
- package/components/more.d.ts +3 -0
- package/components/more.js +4 -3
- package/components/page-tabs.d.ts +12 -0
- package/components/page-tabs.js +8 -0
- package/components/s3-image.d.ts +16 -0
- package/components/s3-image.js +60 -0
- package/components/ui/accordion.d.ts +7 -0
- package/components/ui/accordion.js +17 -0
- package/components/ui/alert.js +2 -2
- package/components/ui/badge.d.ts +9 -0
- package/components/ui/badge.js +22 -0
- package/components/ui/chart.d.ts +40 -0
- package/components/ui/chart.js +112 -0
- package/components/ui/checkbox.js +1 -1
- package/components/ui/dialog.js +2 -2
- package/components/ui/pagination.d.ts +13 -0
- package/components/ui/pagination.js +25 -0
- package/components/ui/sidebar.js +4 -4
- package/components/ui/sonner.js +1 -3
- package/components/ui/table.d.ts +10 -0
- package/components/ui/table.js +27 -0
- package/components/ui/tabs.d.ts +7 -0
- package/components/ui/tabs.js +13 -0
- package/components/ui/tooltip.js +1 -1
- package/components/view-title.d.ts +7 -0
- package/components/view-title.js +7 -0
- package/hooks/use-mobile.js +1 -1
- package/hooks/use-s3-image.d.ts +2 -0
- package/hooks/use-s3-image.js +7 -0
- package/icons/MinutemailerLogo.d.ts +3 -0
- package/icons/MinutemailerLogo.js +3 -0
- package/icons/index.d.ts +1 -0
- package/icons/index.js +1 -0
- package/package.json +9 -4
- package/utils/s3Image/index.d.ts +2 -0
- package/utils/s3Image/index.js +37 -0
- package/utils/s3Image/s3ImageDimensions.d.ts +2 -0
- package/utils/s3Image/s3ImageDimensions.js +7 -0
- package/utils/s3Image/s3ImageSrc.d.ts +2 -0
- package/utils/s3Image/s3ImageSrc.js +51 -0
- package/utils/s3Image/types.d.ts +22 -0
- package/utils/s3Image/types.js +66 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Cell, Label, Pie, PieChart } from 'recharts';
|
|
3
|
+
import { ChartContainer, } from '../components/ui/chart.js';
|
|
4
|
+
export function DonutChart({ percentage, size = 120 }) {
|
|
5
|
+
const COLORS = {
|
|
6
|
+
filled: 'oklch(0.683 0.178 158.484)', // Primary green
|
|
7
|
+
unfilled: 'oklch(0.922 0 0 / 0.3)', // Light gray transparent
|
|
8
|
+
};
|
|
9
|
+
// Include both filled and unfilled portions in the same data
|
|
10
|
+
const chartData = [
|
|
11
|
+
{ name: 'filled', value: percentage },
|
|
12
|
+
{ name: 'unfilled', value: 100 - percentage },
|
|
13
|
+
];
|
|
14
|
+
const chartConfig = {
|
|
15
|
+
filled: {
|
|
16
|
+
label: 'Value',
|
|
17
|
+
color: COLORS.filled,
|
|
18
|
+
},
|
|
19
|
+
unfilled: {
|
|
20
|
+
label: 'Remaining',
|
|
21
|
+
color: COLORS.unfilled,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
return (_jsx(ChartContainer, { config: chartConfig, className: "aspect-square", style: { width: `${size}px`, height: `${size}px` }, children: _jsx(PieChart, { children: _jsxs(Pie, { data: chartData, dataKey: "value", nameKey: "name", innerRadius: "60%", outerRadius: "80%", startAngle: 90, endAngle: 450, paddingAngle: 0, stroke: "none", isAnimationActive: false, children: [_jsx(Cell, { fill: COLORS.filled, stroke: "none" }), _jsx(Cell, { fill: COLORS.unfilled, stroke: "none" }), _jsx(Label, { content: ({ viewBox }) => {
|
|
25
|
+
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
|
26
|
+
const centerX = Number(viewBox.cx);
|
|
27
|
+
const centerY = Number(viewBox.cy) + 4;
|
|
28
|
+
return (_jsx("text", { x: centerX, y: centerY, textAnchor: "middle", dominantBaseline: "middle", children: _jsxs("tspan", { x: centerX, y: centerY, fill: COLORS.filled, className: "text-base font-semibold", children: [percentage, "%"] }) }));
|
|
29
|
+
}
|
|
30
|
+
} })] }) }) }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Area, AreaChart, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
|
|
3
|
+
import { ChartContainer, ChartTooltipContent, } from '../components/ui/chart.js';
|
|
4
|
+
const defaultData = [
|
|
5
|
+
{ hour: '0h', opens: 245, clicks: 12 },
|
|
6
|
+
{ hour: '1h', opens: 520, clicks: 45 },
|
|
7
|
+
{ hour: '2h', opens: 890, clicks: 98 },
|
|
8
|
+
{ hour: '3h', opens: 1240, clicks: 156 },
|
|
9
|
+
{ hour: '4h', opens: 1580, clicks: 234 },
|
|
10
|
+
{ hour: '5h', opens: 1820, clicks: 298 },
|
|
11
|
+
{ hour: '6h', opens: 1950, clicks: 345 },
|
|
12
|
+
{ hour: '7h', opens: 2020, clicks: 378 },
|
|
13
|
+
{ hour: '8h', opens: 2050, clicks: 390 },
|
|
14
|
+
];
|
|
15
|
+
const chartConfig = {
|
|
16
|
+
opens: {
|
|
17
|
+
label: 'Opens',
|
|
18
|
+
color: 'oklch(0.683 0.178 158.484)', // Primary green
|
|
19
|
+
},
|
|
20
|
+
clicks: {
|
|
21
|
+
label: 'Clicks',
|
|
22
|
+
color: 'oklch(0.656 0.173 256.68)', // Secondary purple
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export function EngagementChart({ data = defaultData }) {
|
|
26
|
+
return (_jsx(ChartContainer, { config: chartConfig, className: "h-[300px] w-full [&_svg]:outline-none", children: _jsxs(AreaChart, { data: data, margin: {
|
|
27
|
+
left: 0,
|
|
28
|
+
right: 0,
|
|
29
|
+
top: 12,
|
|
30
|
+
}, children: [_jsxs("defs", { children: [_jsxs("linearGradient", { id: "opensGradient", x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: "var(--color-opens)", stopOpacity: 0.3 }), _jsx("stop", { offset: "100%", stopColor: "var(--color-opens)", stopOpacity: 0 })] }), _jsxs("linearGradient", { id: "clicksGradient", x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: "var(--color-clicks)", stopOpacity: 0.3 }), _jsx("stop", { offset: "100%", stopColor: "var(--color-clicks)", stopOpacity: 0 })] })] }), _jsx(CartesianGrid, { vertical: false, strokeOpacity: 0.2 }), _jsx(XAxis, { dataKey: "hour", tickLine: false, axisLine: false, tickMargin: 8 }), _jsx(YAxis, { tickLine: false, axisLine: false, tickMargin: 8 }), _jsx(Area, { dataKey: "opens", type: "linear", fill: "url(#opensGradient)", stroke: "var(--color-opens)", strokeWidth: 2, dot: false, activeDot: {
|
|
31
|
+
r: 4,
|
|
32
|
+
fill: 'var(--color-opens)',
|
|
33
|
+
stroke: 'var(--color-opens)',
|
|
34
|
+
strokeWidth: 2,
|
|
35
|
+
} }), _jsx(Area, { dataKey: "clicks", type: "linear", fill: "url(#clicksGradient)", stroke: "var(--color-clicks)", strokeWidth: 2, dot: false, activeDot: {
|
|
36
|
+
r: 4,
|
|
37
|
+
fill: 'var(--color-clicks)',
|
|
38
|
+
stroke: 'var(--color-clicks)',
|
|
39
|
+
strokeWidth: 2,
|
|
40
|
+
} }), _jsx(Tooltip, { content: _jsx(ChartTooltipContent, {}) })] }) }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type S3ImageProps } from '../components/s3-image.js';
|
|
3
|
+
export interface ImageSlotAction {
|
|
4
|
+
name: string;
|
|
5
|
+
value: string;
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
type?: 'action' | 'file';
|
|
8
|
+
}
|
|
9
|
+
export interface ImageSlotProps {
|
|
10
|
+
src?: string;
|
|
11
|
+
s3?: Omit<S3ImageProps, 'width' | 'height'>;
|
|
12
|
+
alt?: string;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
actions?: ImageSlotAction[];
|
|
16
|
+
onAction?: (action: string) => void;
|
|
17
|
+
onRemove?: () => void;
|
|
18
|
+
onFile?: (file: File) => void;
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
accept?: string[];
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
declare function ImageSlot({ src, s3, alt, width, height, actions, onAction, onRemove, onFile, loading, accept, className, }: ImageSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export { ImageSlot };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { cn } from '../utils/utils.js';
|
|
4
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../components/ui/dropdown-menu.js';
|
|
5
|
+
import { Button } from '../components/ui/button.js';
|
|
6
|
+
import { Spinner } from '../components/ui/spinner.js';
|
|
7
|
+
import { toast } from 'sonner';
|
|
8
|
+
import { ChevronDownIcon, ImageIcon, Trash2Icon } from 'lucide-react';
|
|
9
|
+
import { S3Image } from '../components/s3-image.js';
|
|
10
|
+
function ImageSlot({ src, s3, alt = '', width, height, actions = [], onAction, onRemove, onFile, loading = false, accept = ['image/gif', 'image/png', 'image/jpeg'], className, }) {
|
|
11
|
+
const aspectRatio = width / height;
|
|
12
|
+
const [dragState, setDragState] = useState('idle');
|
|
13
|
+
const dragCounterRef = useRef(0);
|
|
14
|
+
const fileInputRef = useRef(null);
|
|
15
|
+
const handleAction = (action) => {
|
|
16
|
+
onAction?.(action);
|
|
17
|
+
};
|
|
18
|
+
const handleFileInputChange = (e) => {
|
|
19
|
+
const file = e.target.files?.[0];
|
|
20
|
+
if (!file)
|
|
21
|
+
return;
|
|
22
|
+
if (isValidFile(file)) {
|
|
23
|
+
onFile?.(file);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
toast.error('Unsupported file type', {
|
|
27
|
+
description: `Please upload one of: ${accept.map((type) => type.split('/')[1]).join(', ')}`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// Reset input so the same file can be selected again
|
|
31
|
+
if (fileInputRef.current) {
|
|
32
|
+
fileInputRef.current.value = '';
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const handleActionClick = (action) => {
|
|
36
|
+
if (action.type === 'file') {
|
|
37
|
+
fileInputRef.current?.click();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
handleAction(action.value);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const isValidFile = (file) => {
|
|
44
|
+
return accept.includes(file.type);
|
|
45
|
+
};
|
|
46
|
+
const handleDragEnter = (e) => {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
setDragState('over');
|
|
50
|
+
};
|
|
51
|
+
const handleDragOver = (e) => {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
};
|
|
55
|
+
const handleDragLeave = (e) => {
|
|
56
|
+
if (e.currentTarget.contains(e.relatedTarget))
|
|
57
|
+
return;
|
|
58
|
+
dragCounterRef.current--;
|
|
59
|
+
if (dragCounterRef.current <= 0) {
|
|
60
|
+
dragCounterRef.current = 0;
|
|
61
|
+
setDragState('idle');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const handleDrop = (e) => {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
dragCounterRef.current = 0;
|
|
68
|
+
setDragState('idle');
|
|
69
|
+
const file = e.dataTransfer.files?.[0];
|
|
70
|
+
if (!file)
|
|
71
|
+
return;
|
|
72
|
+
if (isValidFile(file)) {
|
|
73
|
+
onFile?.(file);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
toast.error('Unsupported file type', {
|
|
77
|
+
description: `Please upload one of: ${accept.map((type) => type.split('/')[1]).join(', ')}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
if (src || s3) {
|
|
82
|
+
return (_jsxs("div", { className: cn('relative group', className), style: { width, height }, children: [s3 ? (_jsx(S3Image, { ...s3, width: width, height: height, alt: alt, className: "block w-full h-full object-cover" })) : (_jsx("img", { src: src, alt: alt, width: width, height: height, className: "block w-full h-full object-cover" })), onRemove && (_jsx("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center", children: _jsx(Button, { variant: "outline", className: "rounded-full", size: "icon", onClick: onRemove, children: _jsx(Trash2Icon, {}) }) }))] }));
|
|
83
|
+
}
|
|
84
|
+
// Placeholder view
|
|
85
|
+
return (_jsx("div", { "data-slot": "image-slot", className: cn('bg-muted flex items-center justify-center @container transition-colors', dragState === 'over' && 'bg-green-100 dark:bg-green-950', className), style: {
|
|
86
|
+
width: width,
|
|
87
|
+
maxHeight: height,
|
|
88
|
+
maxWidth: '100%',
|
|
89
|
+
height: width / aspectRatio,
|
|
90
|
+
}, onDragEnter: handleDragEnter, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: loading ? (_jsx(Spinner, { className: "size-8" })) : dragState === 'over' ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Drop image to upload" })) : (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", accept: accept.join(','), onChange: handleFileInputChange, className: "hidden" }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(ImageIcon, { className: "@xs:hidden" }), _jsx("span", { className: "hidden @xs:inline", children: "Add image" }), _jsx(ChevronDownIcon, { className: "hidden @xs:inline" })] }) }), _jsx(DropdownMenuContent, { children: actions.map((action) => (_jsxs(DropdownMenuItem, { onClick: () => handleActionClick(action), children: [action.icon, action.name] }, action.value))) })] })] })) }));
|
|
91
|
+
}
|
|
92
|
+
export { ImageSlot };
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
|
|
2
|
+
type S3Config = {
|
|
3
|
+
bucket: string;
|
|
4
|
+
cloudFrontUrl: string;
|
|
5
|
+
resizeCloudUrl: string;
|
|
6
|
+
};
|
|
7
|
+
type ContextValue = {
|
|
3
8
|
t: (key: string) => string;
|
|
4
|
-
|
|
9
|
+
s3?: S3Config;
|
|
10
|
+
};
|
|
11
|
+
export declare const Context: import("react").Context<ContextValue>;
|
|
5
12
|
type MinutemailerKitProps = {
|
|
6
13
|
children: ReactNode;
|
|
7
14
|
t: (key: string) => string;
|
|
15
|
+
s3?: S3Config;
|
|
8
16
|
};
|
|
9
|
-
export declare function MinutemailerKit({ children, t }: MinutemailerKitProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare function MinutemailerKit({ children, t, s3 }: MinutemailerKitProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function useS3Config(): S3Config;
|
|
10
19
|
export {};
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext } from 'react';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
3
|
export const Context = createContext({
|
|
4
4
|
t: (key) => key,
|
|
5
5
|
});
|
|
6
|
-
export function MinutemailerKit({ children, t }) {
|
|
7
|
-
return _jsx(Context.Provider, { value: { t }, children: children });
|
|
6
|
+
export function MinutemailerKit({ children, t, s3 }) {
|
|
7
|
+
return _jsx(Context.Provider, { value: { t, s3 }, children: children });
|
|
8
|
+
}
|
|
9
|
+
export function useS3Config() {
|
|
10
|
+
const context = useContext(Context);
|
|
11
|
+
if (!context.s3) {
|
|
12
|
+
throw new Error('S3 configuration not provided in MinutemailerKit');
|
|
13
|
+
}
|
|
14
|
+
return context.s3;
|
|
8
15
|
}
|
package/components/more.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
1
2
|
export type MoreOption = {
|
|
2
3
|
value: string;
|
|
3
4
|
name: string;
|
|
5
|
+
icon?: React.ComponentType<any>;
|
|
4
6
|
variant?: 'default' | 'destructive';
|
|
5
7
|
disabled?: boolean;
|
|
8
|
+
separator?: boolean;
|
|
6
9
|
};
|
|
7
10
|
export interface MoreProps {
|
|
8
11
|
options: MoreOption[];
|
package/components/more.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../components/ui/dropdown-menu.js';
|
|
3
4
|
import { Button } from '../components/ui/button.js';
|
|
4
|
-
import
|
|
5
|
+
import MoreIcon from '../icons/More.js';
|
|
5
6
|
import Spinner from '../icons/Spinner.js';
|
|
6
7
|
export function More({ options, loading = false, onChange, disabled = false, }) {
|
|
7
8
|
const handleSelect = (value) => {
|
|
@@ -9,5 +10,5 @@ export function More({ options, loading = false, onChange, disabled = false, })
|
|
|
9
10
|
onChange(value);
|
|
10
11
|
}
|
|
11
12
|
};
|
|
12
|
-
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", disabled: disabled || loading, "aria-label": "More options", children: loading ? _jsx(Spinner, {}) : _jsx(
|
|
13
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", disabled: disabled || loading, "aria-label": "More options", children: loading ? _jsx(Spinner, {}) : _jsx(MoreIcon, {}) }) }), _jsx(DropdownMenuContent, { align: "end", children: options.map((option, index) => (_jsxs(React.Fragment, { children: [option.separator && _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "py-2.5", variant: option.variant, disabled: option.disabled, onSelect: () => handleSelect(option.value), children: [option.icon && _jsx(option.icon, {}), option.name] })] }, option.value))) })] }));
|
|
13
14
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PageTab {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PageTabsProps {
|
|
7
|
+
tabs: PageTab[];
|
|
8
|
+
activeTab: string;
|
|
9
|
+
onTabChange?: (tabId: string) => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function PageTabs({ tabs, activeTab, onTabChange, className, }: PageTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../components/ui/button.js';
|
|
3
|
+
import { cn } from '../utils/utils.js';
|
|
4
|
+
export function PageTabs({ tabs, activeTab, onTabChange, className, }) {
|
|
5
|
+
return (_jsx("div", { className: cn('flex items-center border-b bg-background px-6', className), children: _jsx("div", { className: "flex h-12 items-center gap-6", children: tabs.map((tab) => (_jsx(Button, { variant: "ghost", className: cn('h-12 rounded-none border-b-2 px-2 font-medium hover:bg-transparent hover:text-foreground text-base', activeTab === tab.id
|
|
6
|
+
? 'border-black text-foreground'
|
|
7
|
+
: 'border-transparent text-muted-foreground'), onClick: () => onTabChange?.(tab.id), children: tab.label }, tab.id))) }) }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ImgHTMLAttributes } from 'react';
|
|
2
|
+
import type { FitOptions, ImageEdits, OverlayOptions } from '../utils/s3Image/types.js';
|
|
3
|
+
export interface S3ImageProps extends ImgHTMLAttributes<HTMLImageElement> {
|
|
4
|
+
imageKey: string;
|
|
5
|
+
width: number | string | 'auto';
|
|
6
|
+
height?: number | string | 'auto';
|
|
7
|
+
fit?: FitOptions;
|
|
8
|
+
retina?: boolean;
|
|
9
|
+
overlayWith?: OverlayOptions;
|
|
10
|
+
edits?: ImageEdits;
|
|
11
|
+
containerClassName?: string;
|
|
12
|
+
onLoaded?: () => void;
|
|
13
|
+
onError?: () => void;
|
|
14
|
+
}
|
|
15
|
+
declare function S3Image({ imageKey, width, height, fit, retina, overlayWith, edits, containerClassName, className, alt, onLoaded, onError, ...imgProps }: S3ImageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
16
|
+
export { S3Image };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { cn } from '../utils/utils.js';
|
|
4
|
+
import { Spinner } from '../components/ui/spinner.js';
|
|
5
|
+
import { useS3Image } from '../hooks/use-s3-image.js';
|
|
6
|
+
function S3Image({ imageKey, width, height = 0, fit = 'inside', retina = true, overlayWith, edits = {}, containerClassName, className, alt = '', onLoaded, onError, ...imgProps }) {
|
|
7
|
+
const s3Image = useS3Image();
|
|
8
|
+
const [imageData, setImageData] = useState(null);
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
const [error, setError] = useState(false);
|
|
11
|
+
// Calculate container dimensions
|
|
12
|
+
const containerWidth = width === 'auto'
|
|
13
|
+
? undefined
|
|
14
|
+
: typeof width === 'string'
|
|
15
|
+
? parseInt(width, 10)
|
|
16
|
+
: width;
|
|
17
|
+
const containerHeight = height === 'auto'
|
|
18
|
+
? undefined
|
|
19
|
+
: typeof height === 'string'
|
|
20
|
+
? parseInt(height, 10)
|
|
21
|
+
: height;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!imageKey) {
|
|
24
|
+
setLoading(false);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
setLoading(true);
|
|
28
|
+
setError(false);
|
|
29
|
+
const imageLoader = s3Image(imageKey, width, height, fit, retina, overlayWith, edits);
|
|
30
|
+
imageLoader()
|
|
31
|
+
.then((result) => {
|
|
32
|
+
setImageData(result);
|
|
33
|
+
setLoading(false);
|
|
34
|
+
onLoaded?.();
|
|
35
|
+
})
|
|
36
|
+
.catch(() => {
|
|
37
|
+
setError(true);
|
|
38
|
+
setLoading(false);
|
|
39
|
+
onError?.();
|
|
40
|
+
});
|
|
41
|
+
}, [
|
|
42
|
+
imageKey,
|
|
43
|
+
width,
|
|
44
|
+
height,
|
|
45
|
+
fit,
|
|
46
|
+
retina,
|
|
47
|
+
JSON.stringify(overlayWith),
|
|
48
|
+
JSON.stringify(edits),
|
|
49
|
+
s3Image,
|
|
50
|
+
]);
|
|
51
|
+
const containerStyle = {
|
|
52
|
+
width: imageData ? imageData.width : containerWidth || 'auto',
|
|
53
|
+
height: imageData ? imageData.height : containerHeight || 'auto',
|
|
54
|
+
};
|
|
55
|
+
if (!imageKey) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return (_jsxs("div", { className: cn('relative inline-block', containerClassName), style: containerStyle, children: [loading && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-muted", children: _jsx(Spinner, { className: "size-6" }) })), error && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-muted text-muted-foreground text-sm", children: "Failed to load image" })), imageData && !loading && (_jsx("img", { src: imageData.src, alt: alt, width: imageData.width, height: imageData.height, loading: "lazy", className: cn(className), ...imgProps }))] }));
|
|
59
|
+
}
|
|
60
|
+
export { S3Image };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
3
|
+
declare function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare function AccordionTrigger({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger>): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare function AccordionContent({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Content>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
3
|
+
import { ChevronDown } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../utils/utils.js';
|
|
5
|
+
function Accordion({ ...props }) {
|
|
6
|
+
return _jsx(AccordionPrimitive.Root, { "data-slot": "accordion", ...props });
|
|
7
|
+
}
|
|
8
|
+
function AccordionItem({ className, ...props }) {
|
|
9
|
+
return (_jsx(AccordionPrimitive.Item, { "data-slot": "accordion-item", className: cn('border-b', className), ...props }));
|
|
10
|
+
}
|
|
11
|
+
function AccordionTrigger({ className, children, ...props }) {
|
|
12
|
+
return (_jsx(AccordionPrimitive.Header, { className: "flex", "data-slot": "accordion-header", children: _jsxs(AccordionPrimitive.Trigger, { "data-slot": "accordion-trigger", className: cn('flex flex-1 cursor-pointer items-center justify-between rounded-md px-4 py-5 font-medium transition-all hover:bg-accent/50 [&[data-state=open]>svg]:rotate-180', className), ...props, children: [children, _jsx(ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform duration-200" })] }) }));
|
|
13
|
+
}
|
|
14
|
+
function AccordionContent({ className, children, ...props }) {
|
|
15
|
+
return (_jsx(AccordionPrimitive.Content, { "data-slot": "accordion-content", className: "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden px-4 py-4 text-sm transition-all", ...props, children: _jsx("div", { className: cn('pb-4 pt-0', className), children: children }) }));
|
|
16
|
+
}
|
|
17
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
package/components/ui/alert.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cva } from 'class-variance-authority';
|
|
3
3
|
import { cn } from '../../utils/utils.js';
|
|
4
|
-
const alertVariants = cva('relative w-full rounded-lg border px-4 py-
|
|
4
|
+
const alertVariants = cva('relative w-full rounded-lg border px-4 py-4 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', {
|
|
5
5
|
variants: {
|
|
6
6
|
variant: {
|
|
7
7
|
default: 'bg-card text-card-foreground',
|
|
8
|
-
destructive: 'text-destructive bg-
|
|
8
|
+
destructive: 'text-destructive bg-destructive/5 border-destructive/20 [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
|
|
9
9
|
},
|
|
10
10
|
},
|
|
11
11
|
defaultVariants: {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
declare const badgeVariants: (props?: ({
|
|
4
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | null | undefined;
|
|
5
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
|
+
declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & {
|
|
7
|
+
asChild?: boolean;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { cn } from '../../utils/utils.js';
|
|
5
|
+
const badgeVariants = cva('inline-flex items-center justify-center rounded-full border px-4 py-2 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
|
9
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
|
10
|
+
destructive: 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
11
|
+
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
variant: 'default',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
function Badge({ className, variant, asChild = false, ...props }) {
|
|
19
|
+
const Comp = asChild ? Slot : 'span';
|
|
20
|
+
return (_jsx(Comp, { "data-slot": "badge", className: cn(badgeVariants({ variant }), className), ...props }));
|
|
21
|
+
}
|
|
22
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as RechartsPrimitive from "recharts";
|
|
3
|
+
export type ChartConfig = {
|
|
4
|
+
[k in string]: {
|
|
5
|
+
label?: React.ReactNode;
|
|
6
|
+
icon?: React.ComponentType;
|
|
7
|
+
} & ({
|
|
8
|
+
color?: string;
|
|
9
|
+
theme?: never;
|
|
10
|
+
} | {
|
|
11
|
+
color?: never;
|
|
12
|
+
theme: Record<keyof typeof THEMES | "light" | "dark", string>;
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
declare const THEMES: {
|
|
16
|
+
readonly light: "";
|
|
17
|
+
readonly dark: "";
|
|
18
|
+
};
|
|
19
|
+
export interface ChartContainerProps extends React.ComponentProps<"div"> {
|
|
20
|
+
config: ChartConfig;
|
|
21
|
+
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
|
|
22
|
+
}
|
|
23
|
+
export declare function ChartContainer({ id, className, children, config, ...props }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export interface ChartTooltipProps extends React.ComponentProps<typeof RechartsPrimitive.Tooltip> {
|
|
25
|
+
hideLabel?: boolean;
|
|
26
|
+
hideIndicator?: boolean;
|
|
27
|
+
indicator?: "line" | "dot" | "dashed";
|
|
28
|
+
nameKey?: string;
|
|
29
|
+
labelKey?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function ChartTooltip({ ...props }: ChartTooltipProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export interface ChartTooltipContentProps extends Omit<React.ComponentProps<typeof RechartsPrimitive.Tooltip>, "content">, React.ComponentProps<"div"> {
|
|
33
|
+
hideLabel?: boolean;
|
|
34
|
+
hideIndicator?: boolean;
|
|
35
|
+
indicator?: "line" | "dot" | "dashed";
|
|
36
|
+
nameKey?: string;
|
|
37
|
+
labelKey?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function ChartTooltipContent({ active, payload, className, indicator, hideLabel, hideIndicator, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, }: ChartTooltipContentProps): import("react/jsx-runtime").JSX.Element | null;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as RechartsPrimitive from "recharts";
|
|
5
|
+
import { cn } from "../../utils/utils.js";
|
|
6
|
+
const THEMES = { light: "", dark: "" };
|
|
7
|
+
export function ChartContainer({ id, className, children, config, ...props }) {
|
|
8
|
+
const uniqueId = React.useId();
|
|
9
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
|
10
|
+
return (_jsx(ChartContext.Provider, { value: { config }, children: _jsxs("div", { "data-chart": chartId, className: cn("flex aspect-video justify-center text-xs", className), ...props, children: [_jsx(ChartStyle, { id: chartId, config: config }), _jsx(RechartsPrimitive.ResponsiveContainer, { children: children })] }) }));
|
|
11
|
+
}
|
|
12
|
+
function ChartStyle({ id, config }) {
|
|
13
|
+
const colorConfig = Object.entries(config).filter(([_, config]) => config.color || config.theme);
|
|
14
|
+
if (!colorConfig.length) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return (_jsx("style", { dangerouslySetInnerHTML: {
|
|
18
|
+
__html: `
|
|
19
|
+
[data-chart=${id}] {
|
|
20
|
+
${colorConfig
|
|
21
|
+
.map(([key, itemConfig]) => {
|
|
22
|
+
const color = itemConfig.theme?.light || itemConfig.color || `hsl(var(--chart-${key.charAt(key.length - 1)}))`;
|
|
23
|
+
return ` --color-${key}: ${color};`;
|
|
24
|
+
})
|
|
25
|
+
.join("\n")}
|
|
26
|
+
}
|
|
27
|
+
`,
|
|
28
|
+
} }));
|
|
29
|
+
}
|
|
30
|
+
export function ChartTooltip({ ...props }) {
|
|
31
|
+
return _jsx(RechartsPrimitive.Tooltip, { ...props });
|
|
32
|
+
}
|
|
33
|
+
export function ChartTooltipContent({ active, payload, className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, }) {
|
|
34
|
+
const { config } = useChart();
|
|
35
|
+
const tooltipLabel = React.useMemo(() => {
|
|
36
|
+
if (hideLabel || !payload?.length) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const [item] = payload;
|
|
40
|
+
const key = `${labelKey || item.dataKey || item.name || "value"}`;
|
|
41
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
42
|
+
const value = !labelKey && typeof label === "string"
|
|
43
|
+
? config[label]?.label || label
|
|
44
|
+
: itemConfig?.label;
|
|
45
|
+
if (labelFormatter) {
|
|
46
|
+
return (_jsx("div", { className: cn("font-medium", labelClassName), children: labelFormatter(value, payload) }));
|
|
47
|
+
}
|
|
48
|
+
if (!value) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return _jsx("div", { className: cn("font-medium", labelClassName), children: value });
|
|
52
|
+
}, [
|
|
53
|
+
label,
|
|
54
|
+
labelFormatter,
|
|
55
|
+
payload,
|
|
56
|
+
hideLabel,
|
|
57
|
+
labelClassName,
|
|
58
|
+
config,
|
|
59
|
+
labelKey,
|
|
60
|
+
]);
|
|
61
|
+
if (!active || !payload?.length) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const nestLabel = payload.length === 1 && indicator !== "dot";
|
|
65
|
+
return (_jsxs("div", { className: cn("grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl", className), children: [!nestLabel ? tooltipLabel : null, _jsx("div", { className: "grid gap-1.5", children: payload.map((item, index) => {
|
|
66
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
|
67
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
68
|
+
const indicatorColor = color || item.payload.fill || item.color;
|
|
69
|
+
return (_jsx("div", { className: cn("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center"), children: formatter && item?.value !== undefined && item.name ? (formatter(item.value, item.name, item, index, item.payload)) : (_jsxs(_Fragment, { children: [itemConfig?.icon ? (_jsx(itemConfig.icon, {})) : (!hideIndicator && (_jsx("div", { className: cn("shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]", {
|
|
70
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
71
|
+
"w-1": indicator === "line",
|
|
72
|
+
"w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
|
|
73
|
+
"my-0.5": nestLabel && indicator === "dashed",
|
|
74
|
+
}), style: {
|
|
75
|
+
"--color-bg": indicatorColor,
|
|
76
|
+
"--color-border": indicatorColor,
|
|
77
|
+
} }))), _jsxs("div", { className: cn("flex flex-1 justify-between leading-none", nestLabel ? "items-end" : "items-center"), children: [_jsxs("div", { className: "grid gap-1.5", children: [nestLabel ? tooltipLabel : null, _jsx("span", { className: "text-muted-foreground", children: itemConfig?.label || item.name })] }), item.value && (_jsx("span", { className: "font-mono font-medium tabular-nums text-foreground", children: item.value.toLocaleString() }))] })] })) }, item.dataKey));
|
|
78
|
+
}) })] }));
|
|
79
|
+
}
|
|
80
|
+
// Chart Context
|
|
81
|
+
const ChartContext = React.createContext(null);
|
|
82
|
+
function useChart() {
|
|
83
|
+
const context = React.useContext(ChartContext);
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error("useChart must be used within a <ChartContainer />");
|
|
86
|
+
}
|
|
87
|
+
return context;
|
|
88
|
+
}
|
|
89
|
+
// Utility function
|
|
90
|
+
function getPayloadConfigFromPayload(config, payload, key) {
|
|
91
|
+
if (typeof payload !== "object" || payload === null) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const payloadPayload = "payload" in payload &&
|
|
95
|
+
typeof payload.payload === "object" &&
|
|
96
|
+
payload.payload !== null
|
|
97
|
+
? payload.payload
|
|
98
|
+
: undefined;
|
|
99
|
+
let configLabelKey = key;
|
|
100
|
+
if (key in payload &&
|
|
101
|
+
typeof payload[key] === "string") {
|
|
102
|
+
configLabelKey = payload[key];
|
|
103
|
+
}
|
|
104
|
+
else if (payloadPayload &&
|
|
105
|
+
key in payloadPayload &&
|
|
106
|
+
typeof payloadPayload[key] === "string") {
|
|
107
|
+
configLabelKey = payloadPayload[key];
|
|
108
|
+
}
|
|
109
|
+
return configLabelKey in config
|
|
110
|
+
? config[configLabelKey]
|
|
111
|
+
: config[key];
|
|
112
|
+
}
|
|
@@ -4,6 +4,6 @@ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
|
4
4
|
import { CheckIcon } from 'lucide-react';
|
|
5
5
|
import { cn } from '../../utils/utils.js';
|
|
6
6
|
function Checkbox({ className, ...props }) {
|
|
7
|
-
return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn('peer border-
|
|
7
|
+
return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn('peer border-black/20 cursor-pointer hover:border-foreground/40 dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border-2 shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "flex items-center justify-center transition-none", children: _jsx(CheckIcon, { className: "size-3.5 !text-white" }) }) }));
|
|
8
8
|
}
|
|
9
9
|
export { Checkbox };
|
package/components/ui/dialog.js
CHANGED
|
@@ -18,7 +18,7 @@ function DialogOverlay({ className, ...props }) {
|
|
|
18
18
|
return (_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50', className), ...props }));
|
|
19
19
|
}
|
|
20
20
|
function DialogContent({ className, children, showCloseButton = true, ...props }) {
|
|
21
|
-
return (_jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-
|
|
21
|
+
return (_jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-8 shadow-lg duration-200 sm:max-w-lg', className), ...props, children: [children, showCloseButton && (_jsxs(DialogPrimitive.Close, { "data-slot": "dialog-close", className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }))] })] }));
|
|
22
22
|
}
|
|
23
23
|
function DialogHeader({ className, ...props }) {
|
|
24
24
|
return (_jsx("div", { "data-slot": "dialog-header", className: cn('flex flex-col gap-2 text-center sm:text-left', className), ...props }));
|
|
@@ -27,7 +27,7 @@ function DialogFooter({ className, ...props }) {
|
|
|
27
27
|
return (_jsx("div", { "data-slot": "dialog-footer", className: cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className), ...props }));
|
|
28
28
|
}
|
|
29
29
|
function DialogTitle({ className, ...props }) {
|
|
30
|
-
return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn('text-lg leading-none font-semibold', className), ...props }));
|
|
30
|
+
return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn('text-lg leading-none font-semibold mb-4', className), ...props }));
|
|
31
31
|
}
|
|
32
32
|
function DialogDescription({ className, ...props }) {
|
|
33
33
|
return (_jsx(DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn('text-muted-foreground text-sm', className), ...props }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Button } from '../../components/ui/button.js';
|
|
3
|
+
declare function Pagination({ className, ...props }: React.ComponentProps<'nav'>): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function PaginationContent({ className, ...props }: React.ComponentProps<'ul'>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare function PaginationItem({ className, ...props }: React.ComponentProps<'li'>): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
type PaginationLinkProps = {
|
|
7
|
+
isActive?: boolean;
|
|
8
|
+
} & Pick<React.ComponentProps<typeof Button>, 'size'> & React.ComponentProps<'a'>;
|
|
9
|
+
declare function PaginationLink({ className, isActive, size, ...props }: PaginationLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
declare function PaginationPrevious({ className, ...props }: React.ComponentProps<typeof PaginationLink>): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
declare function PaginationNext({ className, ...props }: React.ComponentProps<typeof PaginationLink>): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
declare function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../utils/utils.js';
|
|
4
|
+
function Pagination({ className, ...props }) {
|
|
5
|
+
return (_jsx("nav", { role: "navigation", "aria-label": "pagination", "data-slot": "pagination", className: cn('mx-auto flex w-full justify-center', className), ...props }));
|
|
6
|
+
}
|
|
7
|
+
function PaginationContent({ className, ...props }) {
|
|
8
|
+
return (_jsx("ul", { "data-slot": "pagination-content", className: cn('flex flex-row items-center gap-1', className), ...props }));
|
|
9
|
+
}
|
|
10
|
+
function PaginationItem({ className, ...props }) {
|
|
11
|
+
return (_jsx("li", { "data-slot": "pagination-item", className: cn('', className), ...props }));
|
|
12
|
+
}
|
|
13
|
+
function PaginationLink({ className, isActive, size = 'icon', ...props }) {
|
|
14
|
+
return (_jsx("a", { "aria-current": isActive ? 'page' : undefined, "data-slot": "pagination-link", className: cn('inline-flex h-10 w-10 items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', isActive && 'bg-accent text-accent-foreground', className), ...props }));
|
|
15
|
+
}
|
|
16
|
+
function PaginationPrevious({ className, ...props }) {
|
|
17
|
+
return (_jsxs(PaginationLink, { "aria-label": "Go to previous page", "data-slot": "pagination-previous", size: "default", className: cn('w-auto gap-1 px-2.5', className), ...props, children: [_jsx(ChevronLeft, { className: "h-4 w-4" }), _jsx("span", { children: "Previous" })] }));
|
|
18
|
+
}
|
|
19
|
+
function PaginationNext({ className, ...props }) {
|
|
20
|
+
return (_jsxs(PaginationLink, { "aria-label": "Go to next page", "data-slot": "pagination-next", size: "default", className: cn('w-auto gap-1 px-2.5', className), ...props, children: [_jsx("span", { children: "Next" }), _jsx(ChevronRight, { className: "h-4 w-4" })] }));
|
|
21
|
+
}
|
|
22
|
+
function PaginationEllipsis({ className, ...props }) {
|
|
23
|
+
return (_jsxs("span", { "aria-hidden": true, "data-slot": "pagination-ellipsis", className: cn('flex h-9 w-9 items-center justify-center', className), ...props, children: [_jsx(MoreHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "More pages" })] }));
|
|
24
|
+
}
|
|
25
|
+
export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, };
|
package/components/ui/sidebar.js
CHANGED
|
@@ -16,7 +16,7 @@ const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
|
|
16
16
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
17
17
|
const SIDEBAR_WIDTH = '16rem';
|
|
18
18
|
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
19
|
-
const SIDEBAR_WIDTH_ICON = '
|
|
19
|
+
const SIDEBAR_WIDTH_ICON = '4.5rem';
|
|
20
20
|
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
|
21
21
|
const SidebarContext = React.createContext(null);
|
|
22
22
|
function useSidebar() {
|
|
@@ -98,9 +98,9 @@ function Sidebar({ side = 'left', variant = 'sidebar', collapsible = 'offcanvas'
|
|
|
98
98
|
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
|
99
99
|
}, side: side, children: [_jsxs(SheetHeader, { className: "sr-only", children: [_jsx(SheetTitle, { children: "Sidebar" }), _jsx(SheetDescription, { children: "Displays the mobile sidebar." })] }), _jsx("div", { className: "flex h-full w-full flex-col", children: children })] }) }));
|
|
100
100
|
}
|
|
101
|
-
return (_jsxs("div", { className: "group peer text-sidebar-foreground hidden
|
|
101
|
+
return (_jsxs("div", { className: "group peer text-sidebar-foreground hidden sm:block", "data-state": state, "data-collapsible": state === 'collapsed' ? collapsible : '', "data-variant": variant, "data-side": side, "data-slot": "sidebar", children: [_jsx("div", { "data-slot": "sidebar-gap", className: cn('relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear', 'group-data-[collapsible=offcanvas]:w-0', 'group-data-[side=right]:rotate-180', variant === 'floating' || variant === 'inset'
|
|
102
102
|
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
|
|
103
|
-
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)') }), _jsx("div", { "data-slot": "sidebar-container", className: cn('fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear
|
|
103
|
+
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)') }), _jsx("div", { "data-slot": "sidebar-container", className: cn('fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear sm:flex', side === 'left'
|
|
104
104
|
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
|
105
105
|
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
|
106
106
|
// Adjust the padding for floating and inset variants.
|
|
@@ -159,7 +159,7 @@ function SidebarMenu({ className, ...props }) {
|
|
|
159
159
|
function SidebarMenuItem({ className, ...props }) {
|
|
160
160
|
return (_jsx("li", { "data-slot": "sidebar-menu-item", "data-sidebar": "menu-item", className: cn('group/menu-item relative', className), ...props }));
|
|
161
161
|
}
|
|
162
|
-
const sidebarMenuButtonVariants = cva('peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', {
|
|
162
|
+
const sidebarMenuButtonVariants = cva('peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:mx-auto! group-data-[collapsible=icon]:p-2! group-data-[collapsible=icon]:justify-center [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', {
|
|
163
163
|
variants: {
|
|
164
164
|
variant: {
|
|
165
165
|
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
package/components/ui/sonner.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useTheme } from 'next-themes';
|
|
3
2
|
import { Toaster as Sonner } from 'sonner';
|
|
4
3
|
const Toaster = ({ ...props }) => {
|
|
5
|
-
|
|
6
|
-
return (_jsx(Sonner, { theme: theme, className: "toaster group", style: {
|
|
4
|
+
return (_jsx(Sonner, { theme: "light", className: "toaster group", style: {
|
|
7
5
|
'--normal-bg': 'var(--popover)',
|
|
8
6
|
'--normal-text': 'var(--popover-foreground)',
|
|
9
7
|
'--normal-border': 'var(--border)',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
declare function Table({ className, ...props }: React.HTMLAttributes<HTMLTableElement>): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
declare function TableHeader({ className, ...props }: React.HTMLAttributes<HTMLTableSectionElement>): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function TableBody({ className, ...props }: React.HTMLAttributes<HTMLTableSectionElement>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare function TableFooter({ className, ...props }: React.HTMLAttributes<HTMLTableSectionElement>): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare function TableRow({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
declare function TableHead({ className, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function TableCell({ className, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
declare function TableCaption({ className, ...props }: React.HTMLAttributes<HTMLTableCaptionElement>): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption, };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/utils.js';
|
|
3
|
+
function Table({ className, ...props }) {
|
|
4
|
+
return (_jsx("div", { className: "relative w-full overflow-auto", children: _jsx("table", { "data-slot": "table", className: cn('w-full caption-bottom text-sm', className), ...props }) }));
|
|
5
|
+
}
|
|
6
|
+
function TableHeader({ className, ...props }) {
|
|
7
|
+
return (_jsx("thead", { "data-slot": "table-header", className: cn('[&_tr]:border-b', className), ...props }));
|
|
8
|
+
}
|
|
9
|
+
function TableBody({ className, ...props }) {
|
|
10
|
+
return (_jsx("tbody", { "data-slot": "table-body", className: cn('[&_tr:last-child]:border-0', className), ...props }));
|
|
11
|
+
}
|
|
12
|
+
function TableFooter({ className, ...props }) {
|
|
13
|
+
return (_jsx("tfoot", { "data-slot": "table-footer", className: cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className), ...props }));
|
|
14
|
+
}
|
|
15
|
+
function TableRow({ className, ...props }) {
|
|
16
|
+
return (_jsx("tr", { "data-slot": "table-row", className: cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className), ...props }));
|
|
17
|
+
}
|
|
18
|
+
function TableHead({ className, ...props }) {
|
|
19
|
+
return (_jsx("th", { "data-slot": "table-head", className: cn('h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', className), ...props }));
|
|
20
|
+
}
|
|
21
|
+
function TableCell({ className, ...props }) {
|
|
22
|
+
return (_jsx("td", { "data-slot": "table-cell", className: cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className), ...props }));
|
|
23
|
+
}
|
|
24
|
+
function TableCaption({ className, ...props }) {
|
|
25
|
+
return (_jsx("caption", { "data-slot": "table-caption", className: cn('mt-4 text-sm text-muted-foreground', className), ...props }));
|
|
26
|
+
}
|
|
27
|
+
export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption, };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
3
|
+
declare const Tabs: React.ForwardRefExoticComponent<TabsPrimitive.TabsProps & React.RefAttributes<HTMLDivElement>>;
|
|
4
|
+
declare const TabsList: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsListProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
5
|
+
declare const TabsTrigger: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
6
|
+
declare const TabsContent: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
5
|
+
import { cn } from '../../utils/utils.js';
|
|
6
|
+
const Tabs = TabsPrimitive.Root;
|
|
7
|
+
const TabsList = React.forwardRef(({ className, ...props }, ref) => (_jsx(TabsPrimitive.List, { ref: ref, className: cn('inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground', className), ...props })));
|
|
8
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
9
|
+
const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (_jsx(TabsPrimitive.Trigger, { ref: ref, className: cn('inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow', className), ...props })));
|
|
10
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
11
|
+
const TabsContent = React.forwardRef(({ className, ...props }, ref) => (_jsx(TabsPrimitive.Content, { ref: ref, className: cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', className), ...props })));
|
|
12
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
13
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
package/components/ui/tooltip.js
CHANGED
|
@@ -11,6 +11,6 @@ function TooltipTrigger({ ...props }) {
|
|
|
11
11
|
return _jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
12
12
|
}
|
|
13
13
|
function TooltipContent({ className, sideOffset = 0, children, ...props }) {
|
|
14
|
-
return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn('bg-
|
|
14
|
+
return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn('bg-gray-800 text-white animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance shadow-md', className), ...props, children: [children, _jsx(TooltipPrimitive.Arrow, { className: "bg-gray-800 fill-gray-800 z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })] }) }));
|
|
15
15
|
}
|
|
16
16
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ViewTitleProps {
|
|
2
|
+
title: string;
|
|
3
|
+
showBackButton?: boolean;
|
|
4
|
+
onBackClick?: () => void;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function ViewTitle({ title, showBackButton, onBackClick, className, }: ViewTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowLeft } from 'lucide-react';
|
|
3
|
+
import { Button } from '../components/ui/button.js';
|
|
4
|
+
import { cn } from '../utils/utils.js';
|
|
5
|
+
export function ViewTitle({ title, showBackButton = false, onBackClick, className, }) {
|
|
6
|
+
return (_jsxs("div", { className: cn('flex items-center gap-3', className), children: [showBackButton && (_jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Go back", onClick: onBackClick, children: _jsx(ArrowLeft, {}) })), _jsx("h1", { className: "text-lg font-semibold lg:text-xl", children: title })] }));
|
|
7
|
+
}
|
package/hooks/use-mobile.js
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FitOptions, ImageEdits, OverlayOptions } from '../utils/s3Image/types.js';
|
|
2
|
+
export declare function useS3Image(): (key: string, width: number | string | "auto", height?: number | string | "auto", fit?: FitOptions, retina?: boolean, overlayWith?: OverlayOptions | undefined, edits?: ImageEdits) => () => Promise<import("../utils/s3Image/types.js").S3ImageResult>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useS3Config } from '../components/minutemailer-kit.js';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { s3Image } from '../utils/s3Image/index.js';
|
|
4
|
+
export function useS3Image() {
|
|
5
|
+
const { bucket, resizeCloudUrl, cloudFrontUrl } = useS3Config();
|
|
6
|
+
return useCallback((key, width, height = 0, fit = 'inside', retina = true, overlayWith = undefined, edits = {}) => s3Image(key, width, height, fit, retina, overlayWith, edits, bucket, resizeCloudUrl, cloudFrontUrl), [bucket, resizeCloudUrl, cloudFrontUrl]);
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const SvgMinutemailerLogo = (props) => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", fill: "none", viewBox: "0 0 120 14", ...props, children: [_jsx("path", { fill: "currentColor", fillRule: "evenodd", d: "M15.958 13.938 22.283 0 0 7.373 8.539 9.27 8.579 14l2.561-2.529-1.993-2.006z", clipRule: "evenodd" }), _jsx("path", { fill: "currentColor", d: "M39.66 2.872c0-.65.52-1.138 1.146-1.138.677 0 1.145.487 1.145 1.083 0 .65-.468 1.192-1.145 1.192a1.133 1.133 0 0 1-1.146-1.137m.26 2.492h1.823v6.935H39.92zM99.273 2.872c0-.65.521-1.138 1.146-1.138.677 0 1.145.487 1.145 1.083 0 .65-.468 1.192-1.145 1.192a1.133 1.133 0 0 1-1.146-1.137m.26 2.492h1.823v6.935h-1.822zM51.22 7.64v4.66h-1.822V7.91c0-1.462-.677-2.112-1.875-2.112-1.25-.054-2.134.758-2.134 2.384v4.117h-1.823V4.172h1.719v1.03c.572-.759 1.562-1.138 2.707-1.138 1.822.054 3.228 1.138 3.228 3.576M60.483 4.172V12.3h-1.718v-1.03c-.573.76-1.51 1.139-2.5 1.139-1.978 0-3.331-1.138-3.331-3.576v-4.66h1.822v4.389c0 1.463.677 2.167 1.822 2.167 1.25 0 2.135-.813 2.135-2.438V4.172zM67.095 11.865c-.417.38-1.094.542-1.77.542-1.719 0-2.708-.92-2.708-2.709v-3.9h-1.25V4.28h1.302V1.734h1.822v2.492h2.083v1.517H64.49V9.59c0 .813.365 1.192 1.094 1.192.364 0 .729-.109 1.04-.325zM75.117 8.832h-6.092c.209 1.192 1.146 1.95 2.5 1.95.885 0 1.562-.27 2.134-.867l.99 1.192c-.677.867-1.77 1.3-3.124 1.3-2.604 0-4.322-1.733-4.322-4.171s1.718-4.118 4.01-4.118c2.29 0 3.956 1.68 3.956 4.172 0 .162-.052.38-.052.542m-6.092-1.246h4.426c-.156-1.192-.99-1.95-2.187-1.95-1.25 0-2.082.758-2.239 1.95M89.436 7.64v4.66h-1.822V7.91c0-1.463-.625-2.113-1.77-2.113-1.198 0-2.031.813-2.031 2.438v4.118H81.99V7.965c0-1.462-.624-2.113-1.77-2.113-1.197 0-2.03.813-2.03 2.438v4.118h-1.823V4.281h1.718v1.03c.573-.705 1.51-1.138 2.552-1.138 1.145 0 2.082.433 2.603 1.354.625-.812 1.666-1.354 2.916-1.354 1.978-.054 3.28 1.03 3.28 3.467M37.942 7.64v4.66H36.12V7.91c0-1.462-.625-2.112-1.77-2.112-1.198 0-2.03.812-2.03 2.438v4.117h-1.823V7.965c0-1.463-.625-2.113-1.77-2.113-1.146-.108-1.979.704-1.979 2.33v4.117h-1.822V4.172h1.718v1.03c.573-.705 1.51-1.138 2.551-1.138 1.146 0 2.083.433 2.604 1.354.624-.812 1.666-1.354 2.915-1.354 1.927.054 3.228 1.138 3.228 3.576M97.765 7.532V12.3h-1.719v-.976c-.416.705-1.301 1.084-2.447 1.084-1.77 0-2.915-1.03-2.915-2.438 0-1.354.885-2.438 3.228-2.438h2.03v-.108c0-1.084-.624-1.734-1.926-1.734-.885 0-1.77.325-2.343.813l-.73-1.355c.834-.65 2.032-.975 3.281-.975 2.239-.054 3.54 1.03 3.54 3.359m-1.823 2.221v-.92h-1.874c-1.25 0-1.562.487-1.562 1.083 0 .704.573 1.138 1.51 1.138.885-.055 1.614-.434 1.926-1.3M103.18 1.68h1.822V12.3h-1.822zM114.164 8.832h-6.092c.209 1.192 1.146 1.95 2.5 1.95.885 0 1.562-.27 2.134-.867l.99 1.192c-.677.867-1.771 1.3-3.124 1.3-2.604 0-4.322-1.733-4.322-4.171s1.718-4.118 4.009-4.118 3.957 1.68 3.957 4.172c0 .162 0 .38-.052.542m-6.092-1.246h4.426c-.156-1.192-.989-1.95-2.187-1.95s-2.082.758-2.239 1.95M120.001 4.118v1.788c-.156-.054-.312-.054-.416-.054-1.406 0-2.239.867-2.239 2.492V12.3h-1.823V4.172h1.719v1.192c.52-.812 1.458-1.246 2.759-1.246" })] }));
|
|
3
|
+
export default SvgMinutemailerLogo;
|
package/icons/index.d.ts
CHANGED
package/icons/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minutemailer/kit",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Minutemailer UI Kit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Minutemailer",
|
|
@@ -22,18 +22,20 @@
|
|
|
22
22
|
"react-dom": "^19.0.0 || ^18.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@storybook/addon-docs": "^10.0.
|
|
26
|
-
"@storybook/react-vite": "^10.0.
|
|
25
|
+
"@storybook/addon-docs": "^10.0.8",
|
|
26
|
+
"@storybook/react-vite": "^10.0.8",
|
|
27
27
|
"@svgr/cli": "^8.1.0",
|
|
28
|
+
"@types/node": "^24.10.1",
|
|
28
29
|
"@types/react": "^19.2.0",
|
|
29
30
|
"@types/react-dom": "^19.2.0",
|
|
30
31
|
"@vitest/coverage-v8": "^3.2.4",
|
|
32
|
+
"baseline-browser-mapping": "^2.8.29",
|
|
31
33
|
"prettier": "3.6.2",
|
|
32
34
|
"react": "^19.2.0",
|
|
33
35
|
"react-dom": "^19.2.0",
|
|
34
36
|
"sass": "^1.93.2",
|
|
35
37
|
"shadcn": "^3.3.1",
|
|
36
|
-
"storybook": "^10.0.
|
|
38
|
+
"storybook": "^10.0.8",
|
|
37
39
|
"tsc-alias": "^1.8.16",
|
|
38
40
|
"typedoc": "^0.28.13",
|
|
39
41
|
"typedoc-material-theme": "^1.4.0",
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
},
|
|
43
45
|
"dependencies": {
|
|
44
46
|
"@hookform/resolvers": "^5.2.2",
|
|
47
|
+
"@radix-ui/react-accordion": "^1.2.12",
|
|
45
48
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
46
49
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
47
50
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
@@ -50,6 +53,7 @@
|
|
|
50
53
|
"@radix-ui/react-popover": "^1.1.15",
|
|
51
54
|
"@radix-ui/react-separator": "^1.1.7",
|
|
52
55
|
"@radix-ui/react-slot": "^1.2.3",
|
|
56
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
53
57
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
54
58
|
"@tailwindcss/vite": "^4.1.14",
|
|
55
59
|
"class-variance-authority": "^0.7.1",
|
|
@@ -59,6 +63,7 @@
|
|
|
59
63
|
"next-themes": "^0.4.6",
|
|
60
64
|
"react-day-picker": "^9.11.1",
|
|
61
65
|
"react-hook-form": "^7.63.0",
|
|
66
|
+
"recharts": "^2.15.4",
|
|
62
67
|
"sonner": "^2.0.7",
|
|
63
68
|
"tailwind-merge": "^3.3.1",
|
|
64
69
|
"tailwindcss": "^4.1.14",
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FitOptions, ImageEdits, OverlayOptions, S3ImageResult } from './types.js';
|
|
2
|
+
export declare function s3Image(key: string, width: number | string | 'auto', height: number | string | "auto" | undefined, fit: FitOptions | undefined, retina: boolean | undefined, overlayWith: OverlayOptions | undefined, edits: ImageEdits | undefined, bucket: string, resizeCloudUrl: string, cloudFrontUrl: string): () => Promise<S3ImageResult>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { s3ImageDimensions } from './s3ImageDimensions.js';
|
|
2
|
+
import { s3ImageSrc } from './s3ImageSrc.js';
|
|
3
|
+
export function s3Image(key, width, height = 0, fit = 'inside', retina = true, overlayWith = undefined, edits = {}, bucket, resizeCloudUrl, cloudFrontUrl) {
|
|
4
|
+
if (!key) {
|
|
5
|
+
return () => new Promise((resolve) => {
|
|
6
|
+
resolve({ src: '', width: 0, height: 0 });
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
return () => new Promise((resolve, reject) => {
|
|
10
|
+
const imageSrc = s3ImageSrc(key, width, height, fit, retina, overlayWith, edits, bucket, resizeCloudUrl, cloudFrontUrl);
|
|
11
|
+
const extension = key.split('.').pop();
|
|
12
|
+
const resizeWidth = width === 'auto' ? 0 : parseInt(width.toString(), 10);
|
|
13
|
+
const resizeHeight = height === 'auto' ? 0 : parseInt(height.toString(), 10);
|
|
14
|
+
const image = new Image();
|
|
15
|
+
image.onload = (ev) => {
|
|
16
|
+
const img = ev.currentTarget;
|
|
17
|
+
const [realWidth, realHeight] = s3ImageDimensions(img.width, img.height, resizeWidth, resizeHeight);
|
|
18
|
+
if (extension === 'gif') {
|
|
19
|
+
const dimensions = s3ImageDimensions(realWidth, realHeight, resizeWidth, resizeHeight, fit);
|
|
20
|
+
resolve({
|
|
21
|
+
src: img.src,
|
|
22
|
+
width: dimensions[0],
|
|
23
|
+
height: dimensions[1],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
resolve({
|
|
28
|
+
src: img.src,
|
|
29
|
+
width: realWidth,
|
|
30
|
+
height: realHeight,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
image.onerror = reject;
|
|
35
|
+
image.src = imageSrc;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function s3ImageDimensions(width, height, resizeWidth, resizeHeight, fit = 'inside') {
|
|
2
|
+
if (['fill', 'contain', 'cover'].indexOf(fit) > -1) {
|
|
3
|
+
return [resizeWidth, resizeHeight];
|
|
4
|
+
}
|
|
5
|
+
const ratio = Math.min(Math.min(resizeWidth === 0 ? 9999 : resizeWidth, width) / width, Math.min(resizeHeight === 0 ? 9999 : resizeHeight, height) / height);
|
|
6
|
+
return [width * ratio, height * ratio];
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FitOptions, ImageEdits, OverlayOptions } from './types.js';
|
|
2
|
+
export declare function s3ImageSrc(key: string | null | undefined, width: number | string | 'auto', height: number | string | "auto" | undefined, fit: FitOptions | undefined, retina: boolean | undefined, overlayWith: OverlayOptions | undefined, edits: ImageEdits | undefined, bucket: string, resizeCloudUrl: string, cloudFrontUrl: string): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function s3ImageSrc(key, width, height = 0, fit = 'inside', retina = true, overlayWith = undefined, edits = {}, bucket, resizeCloudUrl, cloudFrontUrl) {
|
|
2
|
+
if (!key) {
|
|
3
|
+
return '';
|
|
4
|
+
}
|
|
5
|
+
const url = resizeCloudUrl;
|
|
6
|
+
const extension = key.split('.').pop();
|
|
7
|
+
const path = key.replace(/^\/|\/$/g, '');
|
|
8
|
+
const resizeWidth = width === 'auto' ? 0 : parseInt(width.toString(), 10);
|
|
9
|
+
const resizeHeight = height === 'auto' ? 0 : parseInt(height.toString(), 10);
|
|
10
|
+
let imageSrc;
|
|
11
|
+
if (extension === 'gif') {
|
|
12
|
+
imageSrc = `${cloudFrontUrl.replace('%s', bucket)}/${path}`;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const req = {
|
|
16
|
+
bucket,
|
|
17
|
+
key: path,
|
|
18
|
+
edits: {
|
|
19
|
+
resize: {
|
|
20
|
+
width: retina ? resizeWidth * 2 : resizeWidth,
|
|
21
|
+
height: retina ? resizeHeight * 2 : resizeHeight,
|
|
22
|
+
fit,
|
|
23
|
+
background: {
|
|
24
|
+
r: 255,
|
|
25
|
+
g: 255,
|
|
26
|
+
b: 255,
|
|
27
|
+
alpha: 1,
|
|
28
|
+
},
|
|
29
|
+
withoutEnlargement: true,
|
|
30
|
+
},
|
|
31
|
+
jpeg: {
|
|
32
|
+
quality: 70,
|
|
33
|
+
force: false,
|
|
34
|
+
},
|
|
35
|
+
png: {
|
|
36
|
+
quality: 70,
|
|
37
|
+
force: edits.roundCrop,
|
|
38
|
+
},
|
|
39
|
+
overlayWith: overlayWith
|
|
40
|
+
? {
|
|
41
|
+
...overlayWith,
|
|
42
|
+
bucket,
|
|
43
|
+
}
|
|
44
|
+
: undefined,
|
|
45
|
+
...edits,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
imageSrc = `${url}/${btoa(JSON.stringify(req))}`;
|
|
49
|
+
}
|
|
50
|
+
return imageSrc;
|
|
51
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const ALTERNATE_EDIT_ALLOWLIST_ARRAY: readonly ["overlayWith", "smartCrop", "roundCrop", "contentModeration", "crop", "animated"];
|
|
2
|
+
export declare const SHARP_EDIT_ALLOWLIST_ARRAY: string[];
|
|
3
|
+
type AllowlistedEdit = (typeof SHARP_EDIT_ALLOWLIST_ARRAY)[number] | (typeof ALTERNATE_EDIT_ALLOWLIST_ARRAY)[number];
|
|
4
|
+
export type ImageEdits = Partial<Record<AllowlistedEdit, any>>;
|
|
5
|
+
export type OverlayOptions = {
|
|
6
|
+
key: string;
|
|
7
|
+
bucket: string;
|
|
8
|
+
wRatio: number;
|
|
9
|
+
hRatio: number;
|
|
10
|
+
alpha: number;
|
|
11
|
+
options?: {
|
|
12
|
+
top: number;
|
|
13
|
+
left: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type FitOptions = 'inside' | 'contain' | 'cover' | 'fill' | 'outside';
|
|
17
|
+
export interface S3ImageResult {
|
|
18
|
+
src: string;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const ALTERNATE_EDIT_ALLOWLIST_ARRAY = [
|
|
2
|
+
'overlayWith',
|
|
3
|
+
'smartCrop',
|
|
4
|
+
'roundCrop',
|
|
5
|
+
'contentModeration',
|
|
6
|
+
'crop',
|
|
7
|
+
'animated',
|
|
8
|
+
];
|
|
9
|
+
const SHARP_IMAGE_OPERATIONS = {
|
|
10
|
+
CHANNEL_FUNCTIONS: [
|
|
11
|
+
'removeAlpha',
|
|
12
|
+
'ensureAlpha',
|
|
13
|
+
'extractChannel',
|
|
14
|
+
'joinChannel',
|
|
15
|
+
'bandbool',
|
|
16
|
+
],
|
|
17
|
+
COLOR_FUNCTIONS: [
|
|
18
|
+
'tint',
|
|
19
|
+
'greyscale',
|
|
20
|
+
'grayscale',
|
|
21
|
+
'pipelineColourspace',
|
|
22
|
+
'pipelineColorspace',
|
|
23
|
+
'toColourspace',
|
|
24
|
+
'toColorspace',
|
|
25
|
+
],
|
|
26
|
+
OPERATION_FUNCTIONS: [
|
|
27
|
+
'rotate',
|
|
28
|
+
'flip',
|
|
29
|
+
'flop',
|
|
30
|
+
'affine',
|
|
31
|
+
'sharpen',
|
|
32
|
+
'median',
|
|
33
|
+
'blur',
|
|
34
|
+
'flatten',
|
|
35
|
+
'unflatten',
|
|
36
|
+
'gamma',
|
|
37
|
+
'negate',
|
|
38
|
+
'normalise',
|
|
39
|
+
'normalize',
|
|
40
|
+
'clahe',
|
|
41
|
+
'convolve',
|
|
42
|
+
'threshold',
|
|
43
|
+
'boolean',
|
|
44
|
+
'linear',
|
|
45
|
+
'recomb',
|
|
46
|
+
'modulate',
|
|
47
|
+
],
|
|
48
|
+
FORMAT_OPERATIONS: [
|
|
49
|
+
'jpeg',
|
|
50
|
+
'png',
|
|
51
|
+
'webp',
|
|
52
|
+
'gif',
|
|
53
|
+
'avif',
|
|
54
|
+
'tiff',
|
|
55
|
+
'heif',
|
|
56
|
+
'toFormat',
|
|
57
|
+
],
|
|
58
|
+
RESIZE_OPERATIONS: ['resize', 'extend', 'extract', 'trim'],
|
|
59
|
+
};
|
|
60
|
+
export const SHARP_EDIT_ALLOWLIST_ARRAY = [
|
|
61
|
+
...SHARP_IMAGE_OPERATIONS.CHANNEL_FUNCTIONS,
|
|
62
|
+
...SHARP_IMAGE_OPERATIONS.COLOR_FUNCTIONS,
|
|
63
|
+
...SHARP_IMAGE_OPERATIONS.OPERATION_FUNCTIONS,
|
|
64
|
+
...SHARP_IMAGE_OPERATIONS.FORMAT_OPERATIONS,
|
|
65
|
+
...SHARP_IMAGE_OPERATIONS.RESIZE_OPERATIONS,
|
|
66
|
+
];
|