mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: Add advanced configuration and styling for Callout components
This commit is contained in:
parent
10b1e89bf0
commit
632cc79838
2 changed files with 246 additions and 48 deletions
|
|
@ -1,29 +1,71 @@
|
|||
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||
import { AlertCircle } from 'lucide-react'
|
||||
import React from 'react'
|
||||
import { AlertCircle, X } from 'lucide-react'
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
function InfoCalloutComponent(props: any) {
|
||||
const editorState = useEditorProvider() as any
|
||||
const isEditable = editorState.isEditable
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<InfoCalloutWrapper
|
||||
className="flex space-x-2 items-center bg-blue-200 rounded-lg text-blue-900 px-3 shadow-inner"
|
||||
contentEditable={isEditable}
|
||||
>
|
||||
<AlertCircle />{' '}
|
||||
<NodeViewContent contentEditable={isEditable} className="content" />
|
||||
</InfoCalloutWrapper>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
interface CalloutOptions {
|
||||
dismissible?: boolean;
|
||||
variant?: 'default' | 'filled' | 'outlined';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
const InfoCalloutWrapper = styled.div`
|
||||
const IconWrapper = styled.div<{ size?: string }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
svg {
|
||||
padding: 3px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
margin-right: 0.25rem;
|
||||
padding-left: 0.375rem;
|
||||
padding-top: ${props => props.size === 'sm' ? '0' : '0.5rem'};
|
||||
align-self: ${props => props.size === 'sm' ? 'center' : 'flex-start'};
|
||||
}
|
||||
`
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
`
|
||||
|
||||
const DismissButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
margin-left: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
|
||||
const InfoCalloutWrapper = styled.div<{ size?: string }>`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: 1rem 0;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
flex-direction: ${props => props.size === 'sm' ? 'row' : 'column'};
|
||||
align-items: ${props => props.size === 'sm' ? 'center' : 'flex-start'};
|
||||
}
|
||||
|
||||
svg {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
@ -32,7 +74,69 @@ const InfoCalloutWrapper = styled.div`
|
|||
border: ${(props) =>
|
||||
props.contentEditable ? '2px dashed #1f3a8a12' : 'none'};
|
||||
border-radius: 0.5rem;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
margin: ${props => props.size === 'sm' ? '3px' : '5px 0'};
|
||||
padding: ${props => props.size === 'sm' ? '0.25rem' : '0.5rem'};
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
function InfoCalloutComponent(props: any) {
|
||||
const editorState = useEditorProvider() as any
|
||||
const isEditable = editorState.isEditable
|
||||
const [dismissed, setDismissed] = useState(false)
|
||||
|
||||
// Extract options from props or use defaults
|
||||
const options: CalloutOptions = {
|
||||
dismissible: props.node?.attrs?.dismissible || false,
|
||||
variant: props.node?.attrs?.variant || 'default',
|
||||
size: props.node?.attrs?.size || 'md',
|
||||
}
|
||||
|
||||
if (dismissed) return null;
|
||||
|
||||
const getVariantClasses = () => {
|
||||
switch(options.variant) {
|
||||
case 'filled':
|
||||
return 'bg-blue-500 text-white';
|
||||
case 'outlined':
|
||||
return 'bg-transparent border-2 border-blue-500 text-blue-700';
|
||||
default:
|
||||
return 'bg-blue-200 text-blue-900';
|
||||
}
|
||||
}
|
||||
|
||||
const getSizeClasses = () => {
|
||||
switch(options.size) {
|
||||
case 'sm': return 'py-1 px-2 text-sm';
|
||||
case 'lg': return 'py-3 px-4 text-lg';
|
||||
default: return 'py-2 px-3';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<InfoCalloutWrapper
|
||||
className={`flex items-center rounded-lg shadow-inner ${getVariantClasses()} ${getSizeClasses()}`}
|
||||
contentEditable={isEditable}
|
||||
size={options.size}
|
||||
>
|
||||
<IconWrapper size={options.size}>
|
||||
<AlertCircle />
|
||||
</IconWrapper>
|
||||
<ContentWrapper className="flex-grow">
|
||||
<NodeViewContent contentEditable={isEditable} className="content" />
|
||||
</ContentWrapper>
|
||||
{options.dismissible && !isEditable && (
|
||||
<DismissButton onClick={() => setDismissed(true)}>
|
||||
<X size={16} />
|
||||
</DismissButton>
|
||||
)}
|
||||
</InfoCalloutWrapper>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoCalloutComponent
|
||||
|
|
|
|||
|
|
@ -1,29 +1,71 @@
|
|||
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import React from 'react'
|
||||
import { AlertTriangle, X } from 'lucide-react'
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
function WarningCalloutComponent(props: any) {
|
||||
const editorState = useEditorProvider() as any
|
||||
const isEditable = editorState.isEditable
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<CalloutWrapper
|
||||
className="flex space-x-2 items-center bg-yellow-200 rounded-lg text-yellow-900 px-3 shadow-inner"
|
||||
contentEditable={isEditable}
|
||||
>
|
||||
<AlertTriangle />{' '}
|
||||
<NodeViewContent contentEditable={isEditable} className="content" />
|
||||
</CalloutWrapper>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
interface CalloutOptions {
|
||||
dismissible?: boolean;
|
||||
variant?: 'default' | 'filled' | 'outlined';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
const CalloutWrapper = styled.div`
|
||||
const IconWrapper = styled.div<{ size?: string }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
svg {
|
||||
padding: 3px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
margin-right: 0.25rem;
|
||||
padding-left: 0.375rem;
|
||||
padding-top: ${props => props.size === 'sm' ? '0' : '0.5rem'};
|
||||
align-self: ${props => props.size === 'sm' ? 'center' : 'flex-start'};
|
||||
}
|
||||
`
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
`
|
||||
|
||||
const DismissButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
margin-left: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
|
||||
const CalloutWrapper = styled.div<{ size?: string }>`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: 1rem 0;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
flex-direction: ${props => props.size === 'sm' ? 'row' : 'column'};
|
||||
align-items: ${props => props.size === 'sm' ? 'center' : 'flex-start'};
|
||||
}
|
||||
|
||||
svg {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
@ -32,17 +74,69 @@ const CalloutWrapper = styled.div`
|
|||
border: ${(props) =>
|
||||
props.contentEditable ? '2px dashed #713f1117' : 'none'};
|
||||
border-radius: 0.5rem;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
margin: ${props => props.size === 'sm' ? '3px' : '5px 0'};
|
||||
padding: ${props => props.size === 'sm' ? '0.25rem' : '0.5rem'};
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const DragHandle = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1rem;
|
||||
height: 100%;
|
||||
cursor: move;
|
||||
z-index: 1;
|
||||
`
|
||||
function WarningCalloutComponent(props: any) {
|
||||
const editorState = useEditorProvider() as any
|
||||
const isEditable = editorState.isEditable
|
||||
const [dismissed, setDismissed] = useState(false)
|
||||
|
||||
// Extract options from props or use defaults
|
||||
const options: CalloutOptions = {
|
||||
dismissible: props.node?.attrs?.dismissible || false,
|
||||
variant: props.node?.attrs?.variant || 'default',
|
||||
size: props.node?.attrs?.size || 'md',
|
||||
}
|
||||
|
||||
if (dismissed) return null;
|
||||
|
||||
const getVariantClasses = () => {
|
||||
switch(options.variant) {
|
||||
case 'filled':
|
||||
return 'bg-yellow-500 text-white';
|
||||
case 'outlined':
|
||||
return 'bg-transparent border-2 border-yellow-500 text-yellow-700';
|
||||
default:
|
||||
return 'bg-yellow-200 text-yellow-900';
|
||||
}
|
||||
}
|
||||
|
||||
const getSizeClasses = () => {
|
||||
switch(options.size) {
|
||||
case 'sm': return 'py-1 px-2 text-sm';
|
||||
case 'lg': return 'py-3 px-4 text-lg';
|
||||
default: return 'py-2 px-3';
|
||||
}
|
||||
}
|
||||
|
||||
export default WarningCalloutComponent
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<CalloutWrapper
|
||||
className={`flex items-center rounded-lg shadow-inner ${getVariantClasses()} ${getSizeClasses()}`}
|
||||
contentEditable={isEditable}
|
||||
size={options.size}
|
||||
>
|
||||
<IconWrapper size={options.size}>
|
||||
<AlertTriangle />
|
||||
</IconWrapper>
|
||||
<ContentWrapper className="flex-grow">
|
||||
<NodeViewContent contentEditable={isEditable} className="content" />
|
||||
</ContentWrapper>
|
||||
{options.dismissible && !isEditable && (
|
||||
<DismissButton onClick={() => setDismissed(true)}>
|
||||
<X size={16} />
|
||||
</DismissButton>
|
||||
)}
|
||||
</CalloutWrapper>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarningCalloutComponent
|
||||
Loading…
Add table
Add a link
Reference in a new issue