| 1 | import { cva } from "class-variance-authority"; |
| 2 | import { SlateElement, type SlateElementProps } from "platejs/static"; |
| 3 | |
| 4 | import { cn } from "@/lib/utils"; |
| 5 | |
| 6 | // Quote variant styles (matching editor component) |
| 7 | const quoteVariants = cva("relative my-3 w-full max-w-full overflow-hidden", { |
| 8 | variants: { |
| 9 | variant: { |
| 10 | large: "flex flex-col items-center py-4 text-center", |
| 11 | "sidequote-icon": |
| 12 | "rounded-r-lg border-l-4 border-(--presentation-primary) bg-(--presentation-primary)/5 py-3 pr-4 pl-12", |
| 13 | sidequote: "border-l-4 border-(--presentation-primary) py-2 pr-4 pl-4", |
| 14 | }, |
| 15 | }, |
| 16 | defaultVariants: { |
| 17 | variant: "large", |
| 18 | }, |
| 19 | }); |
| 20 | |
| 21 | // Large decorative quote mark SVG |
| 22 | const QuoteMark = ({ |
| 23 | className, |
| 24 | flip = false, |
| 25 | }: { |
| 26 | className?: string; |
| 27 | flip?: boolean; |
| 28 | }) => ( |
| 29 | <svg |
| 30 | viewBox="0 0 24 24" |
| 31 | fill="currentColor" |
| 32 | className={cn( |
| 33 | "size-12 text-(--presentation-primary)/30", |
| 34 | flip && "rotate-180", |
| 35 | className, |
| 36 | )} |
| 37 | > |
| 38 | <path d="M6.5 10c-.223 0-.437.034-.65.065.069-.232.14-.468.254-.68.114-.308.292-.575.469-.844.148-.291.409-.488.601-.737.201-.242.475-.403.692-.604.213-.21.492-.315.714-.463.232-.133.434-.28.65-.35l.539-.222.474-.197-.485-1.938-.597.144c-.191.048-.424.104-.689.171-.271.05-.56.187-.882.312-.317.143-.686.238-1.028.467-.344.218-.741.4-1.091.692-.339.301-.748.562-1.05.944-.33.358-.656.734-.909 1.162-.293.408-.492.856-.702 1.299-.19.443-.343.896-.468 1.336-.237.882-.343 1.72-.384 2.437-.034.718-.014 1.315.028 1.747.015.204.043.402.063.539l.025.168.026-.006A4.5 4.5 0 1 0 6.5 10zm11 0c-.223 0-.437.034-.65.065.069-.232.14-.468.254-.68.114-.308.292-.575.469-.844.148-.291.409-.488.601-.737.201-.242.475-.403.692-.604.213-.21.492-.315.714-.463.232-.133.434-.28.65-.35l.539-.222.474-.197-.485-1.938-.597.144c-.191.048-.424.104-.689.171-.271.05-.56.187-.882.312-.317.143-.686.238-1.028.467-.344.218-.741.4-1.091.692-.339.301-.748.562-1.05.944-.33.358-.656.734-.909 1.162-.293.408-.492.856-.702 1.299-.19.443-.343.896-.468 1.336-.237.882-.343 1.72-.384 2.437-.034.718-.014 1.315.028 1.747.015.204.043.402.063.539l.025.168.026-.006A4.5 4.5 0 1 0 17.5 10z" /> |
| 39 | </svg> |
| 40 | ); |
| 41 | |
| 42 | // Quote icon for sidequote-icon variant |
| 43 | const QuoteIcon = ({ className }: { className?: string }) => ( |
| 44 | <svg |
| 45 | viewBox="0 0 24 24" |
| 46 | fill="currentColor" |
| 47 | className={cn("size-6 text-(--presentation-primary)", className)} |
| 48 | > |
| 49 | <path d="M6.5 10c-.223 0-.437.034-.65.065.069-.232.14-.468.254-.68.114-.308.292-.575.469-.844.148-.291.409-.488.601-.737.201-.242.475-.403.692-.604.213-.21.492-.315.714-.463.232-.133.434-.28.65-.35l.539-.222.474-.197-.485-1.938-.597.144c-.191.048-.424.104-.689.171-.271.05-.56.187-.882.312-.317.143-.686.238-1.028.467-.344.218-.741.4-1.091.692-.339.301-.748.562-1.05.944-.33.358-.656.734-.909 1.162-.293.408-.492.856-.702 1.299-.19.443-.343.896-.468 1.336-.237.882-.343 1.72-.384 2.437-.034.718-.014 1.315.028 1.747.015.204.043.402.063.539l.025.168.026-.006A4.5 4.5 0 1 0 6.5 10zm11 0c-.223 0-.437.034-.65.065.069-.232.14-.468.254-.68.114-.308.292-.575.469-.844.148-.291.409-.488.601-.737.201-.242.475-.403.692-.604.213-.21.492-.315.714-.463.232-.133.434-.28.65-.35l.539-.222.474-.197-.485-1.938-.597.144c-.191.048-.424.104-.689.171-.271.05-.56.187-.882.312-.317.143-.686.238-1.028.467-.344.218-.741.4-1.091.692-.339.301-.748.562-1.05.944-.33.358-.656.734-.909 1.162-.293.408-.492.856-.702 1.299-.19.443-.343.896-.468 1.336-.237.882-.343 1.72-.384 2.437-.034.718-.014 1.315.028 1.747.015.204.043.402.063.539l.025.168.026-.006A4.5 4.5 0 1 0 17.5 10z" /> |
| 50 | </svg> |
| 51 | ); |
| 52 | |
| 53 | export function QuoteStatic(props: SlateElementProps) { |
| 54 | const { variant = "large", author } = props.element as { |
| 55 | variant?: "large" | "sidequote-icon" | "sidequote"; |
| 56 | author?: string; |
| 57 | }; |
| 58 | |
| 59 | if (variant === "large") { |
| 60 | return ( |
| 61 | <SlateElement |
| 62 | {...props} |
| 63 | className={cn(quoteVariants({ variant }), props.className)} |
| 64 | > |
| 65 | <div className="relative w-full max-w-3xl px-10"> |
| 66 | {/* Opening quote mark */} |
| 67 | <QuoteMark className="absolute top-0 left-0 size-8" /> |
| 68 | |
| 69 | <div className="flex flex-1 flex-col items-center"> |
| 70 | {/* Quote text */} |
| 71 | <blockquote className="font-serif text-xl text-(--presentation-foreground) italic md:text-2xl"> |
| 72 | {props.children} |
| 73 | </blockquote> |
| 74 | |
| 75 | {/* Author */} |
| 76 | {author && ( |
| 77 | <p className="mt-4 text-sm font-medium text-(--presentation-muted-foreground)"> |
| 78 |  {author} |
| 79 | </p> |
| 80 | )} |
| 81 | </div> |
| 82 | |
| 83 | {/* Closing quote mark */} |
| 84 | <QuoteMark className="absolute top-0 right-0 size-8" flip /> |
| 85 | </div> |
| 86 | </SlateElement> |
| 87 | ); |
| 88 | } |
| 89 | |
| 90 | if (variant === "sidequote-icon") { |
| 91 | return ( |
| 92 | <SlateElement |
| 93 | {...props} |
| 94 | className={cn(quoteVariants({ variant }), props.className)} |
| 95 | > |
| 96 | {/* Quote icon */} |
| 97 | <QuoteIcon className="absolute top-3 left-4" /> |
| 98 | |
| 99 | {/* Quote text */} |
| 100 | <blockquote className="text-base text-(--presentation-foreground) italic md:text-lg"> |
| 101 | {props.children} |
| 102 | </blockquote> |
| 103 | |
| 104 | {/* Author */} |
| 105 | {author && ( |
| 106 | <p className="mt-3 text-sm font-semibold text-(--presentation-foreground)"> |
| 107 | {author} |
| 108 | </p> |
| 109 | )} |
| 110 | </SlateElement> |
| 111 | ); |
| 112 | } |
| 113 | |
| 114 | // Default: sidequote (simple border left) |
| 115 | return ( |
| 116 | <SlateElement |
| 117 | {...props} |
| 118 | className={cn(quoteVariants({ variant: "sidequote" }), props.className)} |
| 119 | > |
| 120 | {/* Quote text */} |
| 121 | <blockquote className="text-base text-(--presentation-foreground)"> |
| 122 | {props.children} |
| 123 | </blockquote> |
| 124 | |
| 125 | {/* Author */} |
| 126 | {author && ( |
| 127 | <p className="mt-2 text-sm text-(--presentation-muted-foreground)"> |
| 128 |  {author} |
| 129 | </p> |
| 130 | )} |
| 131 | </SlateElement> |
| 132 | ); |
| 133 | } |
| 134 |