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 { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||||
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||||
import { AlertCircle } from 'lucide-react'
|
import { AlertCircle, X } from 'lucide-react'
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
function InfoCalloutComponent(props: any) {
|
interface CalloutOptions {
|
||||||
const editorState = useEditorProvider() as any
|
dismissible?: boolean;
|
||||||
const isEditable = editorState.isEditable
|
variant?: 'default' | 'filled' | 'outlined';
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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 {
|
.content {
|
||||||
|
|
@ -32,7 +74,69 @@ const InfoCalloutWrapper = styled.div`
|
||||||
border: ${(props) =>
|
border: ${(props) =>
|
||||||
props.contentEditable ? '2px dashed #1f3a8a12' : 'none'};
|
props.contentEditable ? '2px dashed #1f3a8a12' : 'none'};
|
||||||
border-radius: 0.5rem;
|
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
|
export default InfoCalloutComponent
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,71 @@
|
||||||
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'
|
||||||
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||||
import { AlertTriangle } from 'lucide-react'
|
import { AlertTriangle, X } from 'lucide-react'
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
function WarningCalloutComponent(props: any) {
|
interface CalloutOptions {
|
||||||
const editorState = useEditorProvider() as any
|
dismissible?: boolean;
|
||||||
const isEditable = editorState.isEditable
|
variant?: 'default' | 'filled' | 'outlined';
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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 {
|
.content {
|
||||||
|
|
@ -32,17 +74,69 @@ const CalloutWrapper = styled.div`
|
||||||
border: ${(props) =>
|
border: ${(props) =>
|
||||||
props.contentEditable ? '2px dashed #713f1117' : 'none'};
|
props.contentEditable ? '2px dashed #713f1117' : 'none'};
|
||||||
border-radius: 0.5rem;
|
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`
|
function WarningCalloutComponent(props: any) {
|
||||||
position: absolute;
|
const editorState = useEditorProvider() as any
|
||||||
top: 0;
|
const isEditable = editorState.isEditable
|
||||||
left: 0;
|
const [dismissed, setDismissed] = useState(false)
|
||||||
width: 1rem;
|
|
||||||
height: 100%;
|
// Extract options from props or use defaults
|
||||||
cursor: move;
|
const options: CalloutOptions = {
|
||||||
z-index: 1;
|
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