- {youtubeLink && (
+
+ {isVideo(youtubeLink, media) ? (
+
+ ) : (
+
+ )}
+
+ {/* {youtubeLink && (
)}
{!hasHeroGraphic && (
diff --git a/packages/components-library/package.json b/packages/components-library/package.json
index 049e06da4..b4f1e5d3e 100644
--- a/packages/components-library/package.json
+++ b/packages/components-library/package.json
@@ -65,14 +65,14 @@
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-form": "^0.0.3",
- "@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.5",
- "@radix-ui/react-select": "^2.0.0",
+ "@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
- "@radix-ui/react-switch": "^1.0.3",
- "@radix-ui/react-tabs": "^1.0.4",
+ "@radix-ui/react-switch": "^1.1.3",
+ "@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.0.7",
"class-variance-authority": "^0.7.0",
diff --git a/packages/components-library/src/components/ui/switch.tsx b/packages/components-library/src/components/ui/switch.tsx
new file mode 100644
index 000000000..ac4a2a8b0
--- /dev/null
+++ b/packages/components-library/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import * as React from "react";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
+
+import { cn } from "@/lib/utils";
+
+const Switch = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
+
+export { Switch };
diff --git a/packages/components-library/src/components/ui/tabs.tsx b/packages/components-library/src/components/ui/tabs.tsx
new file mode 100644
index 000000000..ea5e4505c
--- /dev/null
+++ b/packages/components-library/src/components/ui/tabs.tsx
@@ -0,0 +1,53 @@
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+
+import { cn } from "@/lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/packages/components-library/src/content-card.tsx b/packages/components-library/src/content-card.tsx
index a739aa7c0..dde6dc8be 100644
--- a/packages/components-library/src/content-card.tsx
+++ b/packages/components-library/src/content-card.tsx
@@ -1,7 +1,7 @@
import { Card, CardContent } from "@/components/ui/card";
import Link from "./link";
import { cn } from "@/lib/utils";
-import Image from "./image";
+import { Image } from "./image";
interface ContentCardImageProps extends React.HTMLAttributes {
src: string;
diff --git a/packages/components-library/src/course-item.tsx b/packages/components-library/src/course-item.tsx
index 3bd31a816..eee78b803 100644
--- a/packages/components-library/src/course-item.tsx
+++ b/packages/components-library/src/course-item.tsx
@@ -1,4 +1,4 @@
-import Image from "./image";
+import { Image } from "./image";
import type { Course, SiteInfo } from "@courselit/common-models";
import { Constants } from "@courselit/common-models";
import PriceTag from "./pricetag";
diff --git a/packages/components-library/src/image.tsx b/packages/components-library/src/image.tsx
index 2eb6cc983..a9ab5be9e 100644
--- a/packages/components-library/src/image.tsx
+++ b/packages/components-library/src/image.tsx
@@ -1,5 +1,12 @@
import NextImage from "next/legacy/image";
+export type ImageObjectFit =
+ | "cover"
+ | "contain"
+ | "fill"
+ | "none"
+ | "scale-down";
+
interface ImgProps {
src: string;
isThumbnail?: boolean;
@@ -13,6 +20,7 @@ interface ImgProps {
borderRadius?: number;
noDefaultImage?: boolean;
className?: string;
+ objectFit?: ImageObjectFit;
}
// Copied from: https://github.com/vercel/next.js/blob/canary/examples/image-component/pages/shimmer.js
@@ -35,7 +43,7 @@ const toBase64 = (str: string) =>
? Buffer.from(str).toString("base64")
: window.btoa(str);
-const Image = (props: ImgProps) => {
+export const Image = (props: ImgProps) => {
const {
src,
alt,
@@ -46,6 +54,7 @@ const Image = (props: ImgProps) => {
className = "",
width = "w-full",
height = "h-auto",
+ objectFit = "cover",
} = props;
const source = src || defaultImage || "/courselit_backdrop.webp";
@@ -67,9 +76,8 @@ const Image = (props: ImgProps) => {
priority={loading === "eager"}
sizes={sizes}
layout="fill"
+ objectFit={objectFit}
/>
);
};
-
-export default Image;
diff --git a/packages/components-library/src/index.ts b/packages/components-library/src/index.ts
index 9790202ce..1764c8a1e 100644
--- a/packages/components-library/src/index.ts
+++ b/packages/components-library/src/index.ts
@@ -5,7 +5,6 @@ import "./styles.css";
import PriceTag from "./pricetag";
import Section from "./section";
import CourseItem from "./course-item";
-import Image from "./image";
import Link from "./link";
import MediaSelector from "./media-selector";
import TextEditor, { emptyDoc as TextEditorEmptyDoc } from "./text-editor";
@@ -57,13 +56,14 @@ import getSymbolFromCurrency from "currency-symbol-map";
export * from "./content-card";
export * from "./skeleton-card";
export * from "./product-card";
+export * from "./video-with-preview";
+export * from "./image";
export {
PriceTag,
Section,
// WidgetHelpers,
CourseItem,
- Image,
Select,
Link,
MediaSelector,
diff --git a/packages/components-library/src/media-selector/index.tsx b/packages/components-library/src/media-selector/index.tsx
index 84a9a8b5b..aed9ef765 100644
--- a/packages/components-library/src/media-selector/index.tsx
+++ b/packages/components-library/src/media-selector/index.tsx
@@ -1,7 +1,7 @@
"use client";
import { ChangeEvent, useEffect, useState } from "react";
-import Image from "../image";
+import { Image } from "../image";
import { Address, Media, Profile } from "@courselit/common-models";
import Access from "./access";
import Dialog2 from "../dialog2";
diff --git a/packages/components-library/src/product-card.tsx b/packages/components-library/src/product-card.tsx
index bf5b53851..b59a8c56d 100644
--- a/packages/components-library/src/product-card.tsx
+++ b/packages/components-library/src/product-card.tsx
@@ -2,7 +2,7 @@ import { Badge } from "@/components/ui/badge";
import { Course, SiteInfo } from "@courselit/common-models";
import { getPlanPrice, truncate } from "@courselit/utils";
import getSymbolFromCurrency from "currency-symbol-map";
-import Image from "./image";
+import { Image } from "./image";
import {
ContentCard,
ContentCardHeader,
diff --git a/packages/components-library/src/video-with-preview.tsx b/packages/components-library/src/video-with-preview.tsx
new file mode 100644
index 000000000..d1a7c29af
--- /dev/null
+++ b/packages/components-library/src/video-with-preview.tsx
@@ -0,0 +1,315 @@
+import { useState, useEffect } from "react";
+import { Play, X } from "lucide-react";
+
+type VideoType = "youtube" | "vimeo" | "self-hosted";
+
+export type AspectRatio =
+ | "16/9" // Widescreen
+ | "4/3" // Standard
+ | "1/1" // Square
+ | "21/9" // Ultrawide
+ | "9/16" // Vertical
+ | "2/1" // Panoramic
+ | "3/2" // Classic
+ | string; // Allow custom aspect ratios
+
+export interface VideoThumbnailProps {
+ title?: string;
+ thumbnailUrl?: string;
+ videoUrl: string;
+ aspectRatio?: AspectRatio;
+ modal?: boolean;
+}
+
+export function VideoWithPreview({
+ title = "Video",
+ thumbnailUrl,
+ videoUrl = "https://www.youtube.com/watch?v=VLVcZB2-udk",
+ aspectRatio = "16/9",
+ modal = false,
+}: VideoThumbnailProps) {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [vimeoThumbnail, setVimeoThumbnail] = useState