Panel.tsx153 lines · main
| 1 | import { Megaphone } from 'lucide-react' |
| 2 | import { forwardRef, type PropsWithChildren, type ReactNode } from 'react' |
| 3 | import { Badge, Button, cn, Loading } from 'ui' |
| 4 | |
| 5 | interface PanelProps { |
| 6 | className?: string |
| 7 | id?: string |
| 8 | footer?: ReactNode |
| 9 | loading?: boolean |
| 10 | noMargin?: boolean |
| 11 | title?: ReactNode | false |
| 12 | wrapWithLoading?: boolean |
| 13 | noHideOverflow?: boolean |
| 14 | titleClasses?: string |
| 15 | footerClasses?: string |
| 16 | } |
| 17 | |
| 18 | /** |
| 19 | * @deprecated Use Card component from ui package instead |
| 20 | */ |
| 21 | function Panel(props: PropsWithChildren<PanelProps>) { |
| 22 | const content = ( |
| 23 | <div |
| 24 | className={cn( |
| 25 | 'bg-surface-100', |
| 26 | 'rounded-md border shadow-xs', |
| 27 | props.noHideOverflow ? '' : 'overflow-hidden', |
| 28 | props.noMargin ? '' : 'mb-4 md:mb-8', |
| 29 | props.className |
| 30 | )} |
| 31 | id={props.id} |
| 32 | > |
| 33 | {props.title && ( |
| 34 | <div |
| 35 | className={cn( |
| 36 | 'bg-surface-100 border-b border-default flex items-center px-card py-4', |
| 37 | props.titleClasses |
| 38 | )} |
| 39 | > |
| 40 | {props.title} |
| 41 | </div> |
| 42 | )} |
| 43 | {props.children} |
| 44 | {props.footer && <Footer className={props.footerClasses}>{props.footer}</Footer>} |
| 45 | </div> |
| 46 | ) |
| 47 | |
| 48 | if (props.wrapWithLoading === false) { |
| 49 | return content |
| 50 | } |
| 51 | |
| 52 | return <Loading active={Boolean(props.loading)}>{content}</Loading> |
| 53 | } |
| 54 | |
| 55 | function Content({ children, className }: { children: ReactNode; className?: string | false }) { |
| 56 | return <div className={cn('px-card py-4', className)}>{children}</div> |
| 57 | } |
| 58 | |
| 59 | function Footer({ children, className }: { children: ReactNode; className?: string }) { |
| 60 | return ( |
| 61 | <div className={cn('bg-surface-100 border-t border-default', className)}> |
| 62 | <div className="flex h-12 items-center px-card">{children}</div> |
| 63 | </div> |
| 64 | ) |
| 65 | } |
| 66 | |
| 67 | const PanelNotice = forwardRef< |
| 68 | HTMLDivElement, |
| 69 | { |
| 70 | className?: string | false |
| 71 | title?: string |
| 72 | description?: ReactNode |
| 73 | href?: string |
| 74 | buttonText?: string |
| 75 | layout?: 'horizontal' | 'vertical' |
| 76 | badgeLabel?: string |
| 77 | } |
| 78 | >( |
| 79 | ( |
| 80 | { |
| 81 | className, |
| 82 | title, |
| 83 | description, |
| 84 | href, |
| 85 | buttonText, |
| 86 | layout = 'horizontal', |
| 87 | badgeLabel, |
| 88 | ...props |
| 89 | }, |
| 90 | ref |
| 91 | ) => { |
| 92 | return ( |
| 93 | <div |
| 94 | ref={ref} |
| 95 | {...props} |
| 96 | className={cn( |
| 97 | 'relative px-card py-5 bg-studio flex flex-col lg:flex-row lg:justify-between gap-6 overflow-hidden lg:items-center', |
| 98 | layout === 'vertical' && 'flex-col! items-start! gap-y-2', |
| 99 | className |
| 100 | )} |
| 101 | > |
| 102 | <div |
| 103 | className="absolute inset-0 mt-[-5px]" |
| 104 | style={{ |
| 105 | backgroundImage: ` |
| 106 | linear-gradient(to right, hsl(var(--background-200)/1) 0%, hsl(var(--background-200)/1) 30%, hsl(var(--background-200)/0) 100%), |
| 107 | linear-gradient(to right, hsl(var(--border-default)/0.33) 1px, transparent 1px), |
| 108 | linear-gradient(to bottom, hsl(var(--border-default)/0.33) 1px, transparent 1px) |
| 109 | `, |
| 110 | backgroundSize: '100% 100%, 15px 15px, 15px 15px', |
| 111 | backgroundPosition: '0 0, 0 0, 0 0', |
| 112 | }} |
| 113 | /> |
| 114 | <div className="relative flex flex-col gap-y-2"> |
| 115 | <div className="flex flex-row items-center -space-x-px"> |
| 116 | <Badge |
| 117 | variant={'default'} |
| 118 | className="rounded-r-none pr-2 shrink-0 gap-1.5 border-dashed bg-surface-400/0 text-foreground-lighter" |
| 119 | > |
| 120 | <Megaphone size={16} strokeWidth={1.2} /> |
| 121 | <span className="text-foreground-lighter">{badgeLabel ?? 'Upcoming change'}</span> |
| 122 | </Badge> |
| 123 | <Badge |
| 124 | variant="default" |
| 125 | className="rounded-l-none shrink-0 gap-1.5 bg-surface-400/0 text-foreground-lighter border-l-0" |
| 126 | > |
| 127 | <span className="text-foreground text-xs">{title}</span> |
| 128 | </Badge> |
| 129 | </div> |
| 130 | {description && ( |
| 131 | <div className="text-foreground-light text-sm flex flex-col gap-0"> |
| 132 | <div className="prose text-xs max-w-none [&_p]:mt-2 [&_p]:mb-0">{description}</div> |
| 133 | </div> |
| 134 | )} |
| 135 | </div> |
| 136 | {href && ( |
| 137 | <Button size="tiny" type="default" className="text-xs" asChild> |
| 138 | <a href={href} target="_blank" rel="noreferrer noopener"> |
| 139 | {buttonText ?? 'Read the accouncement'} |
| 140 | </a> |
| 141 | </Button> |
| 142 | )} |
| 143 | </div> |
| 144 | ) |
| 145 | } |
| 146 | ) |
| 147 | |
| 148 | PanelNotice.displayName = 'PanelNotice' |
| 149 | |
| 150 | Panel.Content = Content |
| 151 | Panel.Footer = Footer |
| 152 | Panel.Notice = PanelNotice |
| 153 | export default Panel |