pxengine 0.1.9 → 0.1.10
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/README.md +79 -122
- package/dist/index.cjs +35586 -8241
- package/dist/index.d.cts +574 -33
- package/dist/index.d.ts +574 -33
- package/dist/index.js +35525 -8199
- package/dist/registry.json +1776 -113
- package/package.json +1 -1
- package/src/atoms/BadgeAtom.tsx +11 -3
- package/src/atoms/ButtonAtom.tsx +2 -2
- package/src/atoms/CalendarAtom.tsx +1 -1
- package/src/atoms/CardAtom.tsx +1 -1
- package/src/atoms/ChartAtom.tsx +190 -0
- package/src/atoms/CheckboxAtom.tsx +33 -0
- package/src/atoms/ContextMenuAtom.tsx +49 -0
- package/src/atoms/DrawerAtom.tsx +49 -0
- package/src/atoms/DropdownMenuAtom.tsx +49 -0
- package/src/atoms/InputAtom.tsx +49 -23
- package/src/atoms/InputOTPAtom.tsx +49 -0
- package/src/atoms/KbdAtom.tsx +25 -0
- package/src/atoms/LabelAtom.tsx +23 -0
- package/src/atoms/RadioGroupAtom.tsx +31 -0
- package/src/atoms/RatingAtom.tsx +37 -0
- package/src/atoms/ResizableAtom.tsx +51 -0
- package/src/atoms/SliderAtom.tsx +32 -0
- package/src/atoms/SwitchAtom.tsx +32 -0
- package/src/atoms/TextareaAtom.tsx +42 -0
- package/src/atoms/TimelineAtom.tsx +77 -0
- package/src/atoms/ToggleAtom.tsx +36 -0
- package/src/atoms/VideoAtom.tsx +34 -0
- package/src/atoms/index.ts +17 -0
- package/src/components/ui/resizable.tsx +5 -6
- package/src/molecules/creator-discovery/AudienceDemographicsCard/AudienceDemographicsCard.tsx +44 -0
- package/src/molecules/creator-discovery/AudienceDemographicsCard/index.ts +1 -0
- package/src/molecules/creator-discovery/AudienceMetricCard/AudienceMetricCard.tsx +50 -0
- package/src/molecules/creator-discovery/AudienceMetricCard/index.ts +1 -0
- package/src/molecules/creator-discovery/BrandAffinityGroup/BrandAffinityGroup.tsx +36 -0
- package/src/molecules/creator-discovery/BrandAffinityGroup/index.ts +1 -0
- package/src/molecules/creator-discovery/ContentPreviewGallery/ContentPreviewGallery.tsx +41 -0
- package/src/molecules/creator-discovery/ContentPreviewGallery/index.ts +1 -0
- package/src/molecules/creator-discovery/CreatorActionHeader/CreatorActionHeader.tsx +77 -0
- package/src/molecules/creator-discovery/CreatorActionHeader/index.ts +1 -0
- package/src/molecules/creator-discovery/CreatorGridCard/CreatorGridCard.tsx +104 -0
- package/src/molecules/creator-discovery/CreatorGridCard/index.ts +1 -0
- package/src/molecules/creator-discovery/CreatorProfileSummary/CreatorProfileSummary.tsx +65 -0
- package/src/molecules/creator-discovery/CreatorProfileSummary/index.ts +1 -0
- package/src/molecules/creator-discovery/GrowthChartCard/GrowthChartCard.tsx +58 -0
- package/src/molecules/creator-discovery/GrowthChartCard/index.ts +1 -0
- package/src/molecules/creator-discovery/PlatformIconGroup/PlatformIconGroup.tsx +72 -0
- package/src/molecules/creator-discovery/PlatformIconGroup/index.ts +1 -0
- package/src/molecules/creator-discovery/TopPostsGrid/TopPostsGrid.tsx +49 -0
- package/src/molecules/creator-discovery/TopPostsGrid/index.ts +1 -0
- package/src/molecules/creator-discovery/index.ts +10 -0
- package/src/molecules/generic/DataGrid/DataGrid.tsx +102 -0
- package/src/molecules/generic/DataGrid/index.ts +1 -0
- package/src/molecules/generic/EmptyState/EmptyState.tsx +61 -0
- package/src/molecules/generic/EmptyState/index.ts +1 -0
- package/src/molecules/generic/FileUpload/FileUpload.tsx +62 -0
- package/src/molecules/generic/FileUpload/index.ts +1 -0
- package/src/molecules/generic/FilterBar/FilterBar.tsx +54 -0
- package/src/molecules/generic/FilterBar/index.ts +1 -0
- package/src/molecules/generic/LoadingOverlay/LoadingOverlay.tsx +39 -0
- package/src/molecules/generic/LoadingOverlay/index.ts +1 -0
- package/src/molecules/generic/NotificationList/NotificationList.tsx +80 -0
- package/src/molecules/generic/NotificationList/index.ts +1 -0
- package/src/molecules/generic/StatsGrid/StatsGrid.tsx +80 -0
- package/src/molecules/generic/StatsGrid/index.ts +1 -0
- package/src/molecules/generic/StepWizard/StepWizard.tsx +67 -0
- package/src/molecules/generic/StepWizard/index.ts +1 -0
- package/src/molecules/generic/TagCloud/TagCloud.tsx +32 -0
- package/src/molecules/generic/TagCloud/index.ts +1 -0
- package/src/molecules/generic/index.ts +9 -0
- package/src/render/PXEngineRenderer.tsx +1 -1
- package/src/types/atoms.ts +150 -2
- package/src/types/molecules.ts +226 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Label } from "@/components/ui/label";
|
|
3
|
+
import { LabelAtomType } from "../types/atoms";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* LabelAtom
|
|
8
|
+
* Wraps shadcn Label
|
|
9
|
+
*/
|
|
10
|
+
export const LabelAtom: React.FC<LabelAtomType> = ({
|
|
11
|
+
content,
|
|
12
|
+
htmlFor,
|
|
13
|
+
className,
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<Label
|
|
17
|
+
htmlFor={htmlFor}
|
|
18
|
+
className={cn("text-sm font-semibold text-gray-700", className)}
|
|
19
|
+
>
|
|
20
|
+
{content}
|
|
21
|
+
</Label>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
3
|
+
import { Label } from "@/components/ui/label";
|
|
4
|
+
import { RadioGroupAtomType } from "../types/atoms";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* RadioGroupAtom
|
|
9
|
+
* Wraps shadcn RadioGroup
|
|
10
|
+
*/
|
|
11
|
+
export const RadioGroupAtom: React.FC<
|
|
12
|
+
RadioGroupAtomType & { onValueChange?: (value: string) => void }
|
|
13
|
+
> = ({ options, defaultValue, disabled, className, onValueChange }) => {
|
|
14
|
+
return (
|
|
15
|
+
<RadioGroup
|
|
16
|
+
defaultValue={defaultValue}
|
|
17
|
+
disabled={disabled}
|
|
18
|
+
onValueChange={onValueChange}
|
|
19
|
+
className={cn("grid gap-2", className)}
|
|
20
|
+
>
|
|
21
|
+
{options.map((option) => (
|
|
22
|
+
<div key={option.value} className="flex items-center space-x-2">
|
|
23
|
+
<RadioGroupItem value={option.value} id={option.value} />
|
|
24
|
+
<Label htmlFor={option.value} className="cursor-pointer">
|
|
25
|
+
{option.label}
|
|
26
|
+
</Label>
|
|
27
|
+
</div>
|
|
28
|
+
))}
|
|
29
|
+
</RadioGroup>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Star } from "lucide-react";
|
|
3
|
+
import { RatingAtomType } from "../types/atoms";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RatingAtom
|
|
8
|
+
* A star rating component for display or user input.
|
|
9
|
+
*/
|
|
10
|
+
export const RatingAtom: React.FC<
|
|
11
|
+
RatingAtomType & { onChange?: (val: number) => void }
|
|
12
|
+
> = ({ value, max = 5, readonly = false, className, onChange }) => {
|
|
13
|
+
const [hoverValue, setHoverValue] = React.useState<number | null>(null);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={cn("flex items-center gap-1", className)}>
|
|
17
|
+
{Array.from({ length: max }, (_, i) => {
|
|
18
|
+
const starValue = i + 1;
|
|
19
|
+
const isActive = (hoverValue ?? value) >= starValue;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Star
|
|
23
|
+
key={i}
|
|
24
|
+
className={cn(
|
|
25
|
+
"h-5 w-5 transition-colors",
|
|
26
|
+
isActive ? "fill-yellow-400 text-yellow-400" : "text-gray-300",
|
|
27
|
+
!readonly && "cursor-pointer hover:scale-110",
|
|
28
|
+
)}
|
|
29
|
+
onClick={() => !readonly && onChange?.(starValue)}
|
|
30
|
+
onMouseEnter={() => !readonly && setHoverValue(starValue)}
|
|
31
|
+
onMouseLeave={() => !readonly && setHoverValue(null)}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
})}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ResizableHandle,
|
|
4
|
+
ResizablePanel,
|
|
5
|
+
ResizablePanelGroup,
|
|
6
|
+
} from "@/components/ui/resizable";
|
|
7
|
+
import { ResizableAtomType, UIComponent } from "../types/schema";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
|
|
10
|
+
interface Props extends ResizableAtomType {
|
|
11
|
+
renderComponent: (component: UIComponent, index?: number) => React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ResizableAtom
|
|
16
|
+
* Wraps shadcn Resizable panels for complex layouts
|
|
17
|
+
*/
|
|
18
|
+
export const ResizableAtom: React.FC<Props> = ({
|
|
19
|
+
direction,
|
|
20
|
+
panels,
|
|
21
|
+
className,
|
|
22
|
+
renderComponent,
|
|
23
|
+
}) => {
|
|
24
|
+
// Use a locally typed component to bypass DTS inference issues
|
|
25
|
+
const Group = ResizablePanelGroup as any;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Group
|
|
29
|
+
direction={direction}
|
|
30
|
+
className={cn(
|
|
31
|
+
"min-h-[200px] w-full rounded-lg border border-purple-100",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{panels.map((panel: any, i: number) => (
|
|
36
|
+
<React.Fragment key={i}>
|
|
37
|
+
<ResizablePanel defaultSize={panel.defaultSize} className="p-4">
|
|
38
|
+
{panel.children.map((child: any, childIdx: number) => (
|
|
39
|
+
<React.Fragment
|
|
40
|
+
key={child.id || `resizable-child-${i}-${childIdx}`}
|
|
41
|
+
>
|
|
42
|
+
{renderComponent(child)}
|
|
43
|
+
</React.Fragment>
|
|
44
|
+
))}
|
|
45
|
+
</ResizablePanel>
|
|
46
|
+
{i < panels.length - 1 && <ResizableHandle withHandle />}
|
|
47
|
+
</React.Fragment>
|
|
48
|
+
))}
|
|
49
|
+
</Group>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Slider } from "@/components/ui/slider";
|
|
3
|
+
import { SliderAtomType } from "../types/atoms";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SliderAtom
|
|
8
|
+
* Wraps shadcn Slider
|
|
9
|
+
*/
|
|
10
|
+
export const SliderAtom: React.FC<
|
|
11
|
+
SliderAtomType & { onValueChange?: (value: number[]) => void }
|
|
12
|
+
> = ({
|
|
13
|
+
defaultValue = [50],
|
|
14
|
+
max = 100,
|
|
15
|
+
min = 0,
|
|
16
|
+
step = 1,
|
|
17
|
+
disabled,
|
|
18
|
+
className,
|
|
19
|
+
onValueChange,
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<Slider
|
|
23
|
+
defaultValue={defaultValue}
|
|
24
|
+
max={max}
|
|
25
|
+
min={min}
|
|
26
|
+
step={step}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onValueChange={onValueChange}
|
|
29
|
+
className={cn("w-full py-4", className)}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Switch } from "@/components/ui/switch";
|
|
3
|
+
import { SwitchAtomType } from "../types/atoms";
|
|
4
|
+
import { Label } from "@/components/ui/label";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SwitchAtom
|
|
9
|
+
* Wraps shadcn Switch with label support
|
|
10
|
+
*/
|
|
11
|
+
export const SwitchAtom: React.FC<
|
|
12
|
+
SwitchAtomType & { onCheckedChange?: (checked: boolean) => void }
|
|
13
|
+
> = ({ id, label, checked, disabled, className, onCheckedChange }) => {
|
|
14
|
+
return (
|
|
15
|
+
<div className={cn("flex items-center space-x-2", className)}>
|
|
16
|
+
<Switch
|
|
17
|
+
id={id}
|
|
18
|
+
checked={checked}
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
onCheckedChange={onCheckedChange}
|
|
21
|
+
/>
|
|
22
|
+
{label && (
|
|
23
|
+
<Label
|
|
24
|
+
htmlFor={id}
|
|
25
|
+
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
26
|
+
>
|
|
27
|
+
{label}
|
|
28
|
+
</Label>
|
|
29
|
+
)}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
3
|
+
import { InputAtomType } from "../types/atoms";
|
|
4
|
+
import { Label } from "@/components/ui/label";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TextareaAtom
|
|
9
|
+
* Standalone textarea atom
|
|
10
|
+
*/
|
|
11
|
+
export const TextareaAtom: React.FC<
|
|
12
|
+
InputAtomType & { onChange?: (val: string) => void }
|
|
13
|
+
> = ({
|
|
14
|
+
id,
|
|
15
|
+
label,
|
|
16
|
+
placeholder,
|
|
17
|
+
defaultValue,
|
|
18
|
+
disabled,
|
|
19
|
+
required,
|
|
20
|
+
className,
|
|
21
|
+
onChange,
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<div className={cn("grid w-full gap-1.5", className)}>
|
|
25
|
+
{label && (
|
|
26
|
+
<Label htmlFor={id} className="font-semibold">
|
|
27
|
+
{label}
|
|
28
|
+
{required && <span className="text-red-500 ml-1">*</span>}
|
|
29
|
+
</Label>
|
|
30
|
+
)}
|
|
31
|
+
<Textarea
|
|
32
|
+
id={id}
|
|
33
|
+
placeholder={placeholder}
|
|
34
|
+
defaultValue={defaultValue}
|
|
35
|
+
disabled={disabled}
|
|
36
|
+
required={required}
|
|
37
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
38
|
+
className="min-h-[100px] border-purple-100 focus-visible:ring-purple-500 resize-none shadow-sm"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { TimelineAtomType } from "../types/atoms";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import * as Icons from "lucide-react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TimelineAtom
|
|
8
|
+
* A vertical timeline primitive for processes and steps.
|
|
9
|
+
*/
|
|
10
|
+
export const TimelineAtom: React.FC<TimelineAtomType> = ({
|
|
11
|
+
items,
|
|
12
|
+
className,
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={cn(
|
|
17
|
+
"space-y-6 relative before:absolute before:inset-0 before:ml-5 before:-translate-x-px before:h-full before:w-0.5 before:bg-gradient-to-b before:from-purple-500 before:via-indigo-500 before:to-gray-200 before:pointer-events-none",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
>
|
|
21
|
+
{items.map((item, i) => {
|
|
22
|
+
const Icon = item.icon ? (Icons as any)[item.icon] : null;
|
|
23
|
+
const isCompleted = item.status === "completed";
|
|
24
|
+
const isActive = item.status === "active";
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div key={i} className="relative flex items-start gap-6 group">
|
|
28
|
+
<div
|
|
29
|
+
className={cn(
|
|
30
|
+
"flex items-center justify-center w-10 h-10 rounded-full border-4 border-white shadow-sm shrink-0 z-10 transition-all group-hover:scale-110",
|
|
31
|
+
isCompleted
|
|
32
|
+
? "bg-purple-600 text-white"
|
|
33
|
+
: isActive
|
|
34
|
+
? "bg-indigo-600 text-white ring-4 ring-indigo-50"
|
|
35
|
+
: "bg-gray-100 text-gray-400",
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
{Icon ? (
|
|
39
|
+
<Icon className="w-5 h-5" />
|
|
40
|
+
) : isCompleted ? (
|
|
41
|
+
<Icons.Check className="w-5 h-5" />
|
|
42
|
+
) : (
|
|
43
|
+
<div className="w-2 h-2 rounded-full bg-current" />
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="flex flex-col gap-1 pt-0.5">
|
|
47
|
+
<div className="flex items-center gap-2">
|
|
48
|
+
<h4
|
|
49
|
+
className={cn(
|
|
50
|
+
"font-bold text-sm",
|
|
51
|
+
isCompleted
|
|
52
|
+
? "text-gray-900"
|
|
53
|
+
: isActive
|
|
54
|
+
? "text-indigo-600"
|
|
55
|
+
: "text-gray-500",
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{item.title}
|
|
59
|
+
</h4>
|
|
60
|
+
{item.timestamp && (
|
|
61
|
+
<span className="text-[10px] text-muted-foreground bg-gray-50 px-1.5 py-0.5 rounded-md font-medium border border-gray-100">
|
|
62
|
+
{item.timestamp}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
{item.description && (
|
|
67
|
+
<p className="text-xs text-muted-foreground leading-relaxed max-w-sm">
|
|
68
|
+
{item.description}
|
|
69
|
+
</p>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Toggle } from "@/components/ui/toggle";
|
|
3
|
+
import { ToggleAtomType } from "../types/atoms";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ToggleAtom
|
|
8
|
+
* Wraps shadcn Toggle
|
|
9
|
+
*/
|
|
10
|
+
export const ToggleAtom: React.FC<
|
|
11
|
+
ToggleAtomType & { onPressedChange?: (pressed: boolean) => void }
|
|
12
|
+
> = ({
|
|
13
|
+
label,
|
|
14
|
+
pressed,
|
|
15
|
+
disabled,
|
|
16
|
+
size = "md",
|
|
17
|
+
variant = "default",
|
|
18
|
+
className,
|
|
19
|
+
onPressedChange,
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<Toggle
|
|
23
|
+
pressed={pressed}
|
|
24
|
+
disabled={disabled}
|
|
25
|
+
size={size as any}
|
|
26
|
+
variant={variant as any}
|
|
27
|
+
onPressedChange={onPressedChange}
|
|
28
|
+
className={cn(
|
|
29
|
+
"data-[state=on]:bg-purple-100 data-[state=on]:text-purple-900",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{label}
|
|
34
|
+
</Toggle>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { VideoAtomType } from "../types/atoms";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* VideoAtom
|
|
7
|
+
* A simple, styled video player for content previews.
|
|
8
|
+
*/
|
|
9
|
+
export const VideoAtom: React.FC<VideoAtomType> = ({
|
|
10
|
+
src,
|
|
11
|
+
poster,
|
|
12
|
+
autoplay = false,
|
|
13
|
+
controls = true,
|
|
14
|
+
muted = false,
|
|
15
|
+
loop = false,
|
|
16
|
+
className,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className={cn("relative overflow-hidden rounded-2xl bg-black", className)}
|
|
21
|
+
>
|
|
22
|
+
<video
|
|
23
|
+
src={src}
|
|
24
|
+
poster={poster}
|
|
25
|
+
autoPlay={autoplay}
|
|
26
|
+
controls={controls}
|
|
27
|
+
muted={muted}
|
|
28
|
+
loop={loop}
|
|
29
|
+
className="h-full w-full object-cover"
|
|
30
|
+
playsInline
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
package/src/atoms/index.ts
CHANGED
|
@@ -30,3 +30,20 @@ export * from "./CommandAtom";
|
|
|
30
30
|
export * from "./FormInputAtom";
|
|
31
31
|
export * from "./FormSelectAtom";
|
|
32
32
|
export * from "./FormTextareaAtom";
|
|
33
|
+
export * from "./CheckboxAtom";
|
|
34
|
+
export * from "./SwitchAtom";
|
|
35
|
+
export * from "./LabelAtom";
|
|
36
|
+
export * from "./TextareaAtom";
|
|
37
|
+
export * from "./ToggleAtom";
|
|
38
|
+
export * from "./SliderAtom";
|
|
39
|
+
export * from "./RadioGroupAtom";
|
|
40
|
+
export * from "./DropdownMenuAtom";
|
|
41
|
+
export * from "./ContextMenuAtom";
|
|
42
|
+
export * from "./DrawerAtom";
|
|
43
|
+
export * from "./InputOTPAtom";
|
|
44
|
+
export * from "./KbdAtom";
|
|
45
|
+
export * from "./ResizableAtom";
|
|
46
|
+
export * from "./ChartAtom";
|
|
47
|
+
export * from "./VideoAtom";
|
|
48
|
+
export * from "./RatingAtom";
|
|
49
|
+
export * from "./TimelineAtom";
|
|
@@ -5,16 +5,15 @@ import * as ResizablePrimitive from "react-resizable-panels";
|
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
7
|
|
|
8
|
-
const ResizablePanelGroup = (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}: React.ComponentProps<typeof ResizablePrimitive.Group>) => (
|
|
8
|
+
const ResizablePanelGroup = (
|
|
9
|
+
props: React.ComponentProps<typeof ResizablePrimitive.Group>,
|
|
10
|
+
) => (
|
|
12
11
|
<ResizablePrimitive.Group
|
|
12
|
+
{...props}
|
|
13
13
|
className={cn(
|
|
14
14
|
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
|
15
|
-
className,
|
|
15
|
+
props.className,
|
|
16
16
|
)}
|
|
17
|
-
{...props}
|
|
18
17
|
/>
|
|
19
18
|
);
|
|
20
19
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|
3
|
+
import { ChartAtom } from "../../../atoms/ChartAtom";
|
|
4
|
+
import { AudienceDemographicsCardMolecule } from "../../../types/molecules";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AudienceDemographicsCard
|
|
9
|
+
* Visualizes audience data using appropriate chart types.
|
|
10
|
+
*/
|
|
11
|
+
export const AudienceDemographicsCard: React.FC<
|
|
12
|
+
AudienceDemographicsCardMolecule
|
|
13
|
+
> = ({ title, data, config, demographicType, className }) => {
|
|
14
|
+
const chartType = demographicType === "location" ? "bar" : "pie";
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Card
|
|
18
|
+
className={cn(
|
|
19
|
+
"rounded-[32px] border-purple-50 shadow-sm overflow-hidden",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
<CardHeader className="pb-2">
|
|
24
|
+
<CardTitle className="text-lg font-bold text-gray-900">
|
|
25
|
+
{title}
|
|
26
|
+
</CardTitle>
|
|
27
|
+
</CardHeader>
|
|
28
|
+
<CardContent>
|
|
29
|
+
<div className="h-[250px] w-full">
|
|
30
|
+
<ChartAtom
|
|
31
|
+
type="chart"
|
|
32
|
+
id="audience-demographics"
|
|
33
|
+
chartType={chartType}
|
|
34
|
+
data={data}
|
|
35
|
+
config={config}
|
|
36
|
+
XAxisKey="name"
|
|
37
|
+
YAxisKey="value"
|
|
38
|
+
showLegend={true}
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</CardContent>
|
|
42
|
+
</Card>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AudienceDemographicsCard";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Card, CardHeader, CardTitle, CardContent } from "@/components";
|
|
3
|
+
import { ProgressAtom } from "../../../atoms/ProgressAtom";
|
|
4
|
+
import { AudienceMetricCardMolecule } from "../../../types/molecules";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AudienceMetricCard
|
|
9
|
+
* A card displaying specific audience metrics with progress bars.
|
|
10
|
+
*/
|
|
11
|
+
export const AudienceMetricCard: React.FC<AudienceMetricCardMolecule> = ({
|
|
12
|
+
title,
|
|
13
|
+
metrics,
|
|
14
|
+
className,
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<Card
|
|
18
|
+
className={cn(
|
|
19
|
+
"bg-white border-purple-50 rounded-3xl overflow-hidden",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
<CardHeader className="pb-2">
|
|
24
|
+
<CardTitle className="text-lg font-bold text-gray-900">
|
|
25
|
+
{title}
|
|
26
|
+
</CardTitle>
|
|
27
|
+
</CardHeader>
|
|
28
|
+
<CardContent className="space-y-4">
|
|
29
|
+
{metrics.map((metric, i) => (
|
|
30
|
+
<div key={i} className="space-y-1.5">
|
|
31
|
+
<div className="flex justify-between items-center text-sm">
|
|
32
|
+
<span className="font-medium text-gray-700">{metric.label}</span>
|
|
33
|
+
<span className="font-bold text-gray-900">{metric.value}</span>
|
|
34
|
+
</div>
|
|
35
|
+
{metric.percentage !== undefined && (
|
|
36
|
+
<ProgressAtom
|
|
37
|
+
id={`progress-${i}`}
|
|
38
|
+
type="progress"
|
|
39
|
+
value={metric.percentage}
|
|
40
|
+
className="h-1.5 bg-purple-50"
|
|
41
|
+
/* @ts-ignore - custom className for progress indicator */
|
|
42
|
+
indicatorClassName="bg-gradient-to-r from-purple-500 to-indigo-500"
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
))}
|
|
47
|
+
</CardContent>
|
|
48
|
+
</Card>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AudienceMetricCard";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { BrandAffinityGroupMolecule } from "../../../types/molecules";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* BrandAffinityGroup
|
|
7
|
+
* Visual group of associated brand logos.
|
|
8
|
+
*/
|
|
9
|
+
export const BrandAffinityGroup: React.FC<BrandAffinityGroupMolecule> = ({
|
|
10
|
+
brands,
|
|
11
|
+
className,
|
|
12
|
+
}) => {
|
|
13
|
+
return (
|
|
14
|
+
<div className={cn("flex flex-col gap-3", className)}>
|
|
15
|
+
<h5 className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
|
|
16
|
+
Recent Collaborations
|
|
17
|
+
</h5>
|
|
18
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
19
|
+
{brands.map((brand, i) => (
|
|
20
|
+
<div key={i} className="group relative">
|
|
21
|
+
<div className="h-10 w-10 overflow-hidden rounded-xl bg-white border border-gray-100 p-1.5 flex items-center justify-center grayscale hover:grayscale-0 transition-all duration-300 hover:scale-110 shadow-sm hover:shadow-md">
|
|
22
|
+
<img
|
|
23
|
+
src={brand.logoSrc}
|
|
24
|
+
alt={brand.name}
|
|
25
|
+
className="max-h-full max-w-full object-contain"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="absolute -bottom-6 left-1/2 -translate-x-1/2 px-2 py-0.5 bg-gray-900 text-white text-[10px] rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-50 pointer-events-none">
|
|
29
|
+
{brand.name}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./BrandAffinityGroup";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ContentPreviewGalleryMolecule } from "../../../types/molecules";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Play } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ContentPreviewGallery
|
|
8
|
+
* Grid/Gallery of thumbnail previews.
|
|
9
|
+
*/
|
|
10
|
+
export const ContentPreviewGallery: React.FC<ContentPreviewGalleryMolecule> = ({
|
|
11
|
+
items,
|
|
12
|
+
className,
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<div className={cn("grid grid-cols-2 gap-2", className)}>
|
|
16
|
+
{items.map((item, i) => (
|
|
17
|
+
<a
|
|
18
|
+
key={i}
|
|
19
|
+
href={item.url || "#"}
|
|
20
|
+
target="_blank"
|
|
21
|
+
rel="noopener noreferrer"
|
|
22
|
+
className="group relative aspect-square overflow-hidden rounded-2xl bg-gray-100"
|
|
23
|
+
>
|
|
24
|
+
<img
|
|
25
|
+
src={item.thumbnail}
|
|
26
|
+
alt="Content preview"
|
|
27
|
+
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
28
|
+
/>
|
|
29
|
+
{item.type === "video" && (
|
|
30
|
+
<div className="absolute inset-0 flex items-center justify-center bg-black/10 group-hover:bg-black/20 transition-colors">
|
|
31
|
+
<div className="rounded-full bg-white/30 backdrop-blur-md p-3 text-white ring-1 ring-white/50">
|
|
32
|
+
<Play className="h-6 w-6 fill-white" />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
<div className="absolute inset-0 ring-1 ring-inset ring-black/10 rounded-2xl" />
|
|
37
|
+
</a>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ContentPreviewGallery";
|