Feature: Add TipTap editor package

This commit is contained in:
Igor Martyniuk
2026-07-31 12:41:47 +00:00
parent 827dc22729
commit 1d51ece993
22 changed files with 1600 additions and 184 deletions
+1
View File
@@ -12,6 +12,7 @@
"preview": "vite preview"
},
"dependencies": {
"@crux-kit/editor-tiptap": "*",
"@crux-kit/hooks": "*",
"@crux-kit/intl-date": "*",
"@crux-kit/intl-name": "*",
+4
View File
@@ -3,6 +3,10 @@
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/typography";
/* @crux-kit/editor-tiptap — Tailwind class scanning + editor content styles */
@source '../../../../../packages/design-system/editor-tiptap/src';
@import '@crux-kit/editor-tiptap/styles.css';
/* ReUI Configs */
@import './config.reui.css';
@@ -1,12 +1,12 @@
import { useForm } from 'react-hook-form';
import SandboxShowcase from '#devtools/view/components/SandboxShowcase';
import { FormElement, FormGrid, GridElement, InputMCEEditor } from '#shared/forms/composed';
import { FormElement, FormGrid, GridElement, InputMCEEditor, InputTiptapEditor } from '#shared/forms/composed';
import { Form as FormProvider } from '#shared/ui/form';
import template from '../lib/templates/richTextField';
import FormActions from './FormActions';
export default function RichTextFieldSection() {
const form = useForm({ defaultValues: { content: '' } });
const form = useForm({ defaultValues: { content: '', contentLight: '' } });
return (
<SandboxShowcase
@@ -21,11 +21,19 @@ export default function RichTextFieldSection() {
<GridElement colSpan={12}>
<InputMCEEditor
name="content"
label="Rich text editor"
label="Rich text editor (TinyMCE)"
placeholder="Write formatted content…"
description="Full rich-text editing with the shared TinyMCE configuration."
/>
</GridElement>
<GridElement colSpan={12}>
<InputTiptapEditor
name="contentLight"
label="Rich text editor (TipTap · light preset)"
placeholder="Write formatted content…"
description="Free, headless editor from @crux-kit/editor-tiptap. HTML in / HTML out."
/>
</GridElement>
<GridElement colSpan={12}>
<FormActions form={form} />
</GridElement>
@@ -0,0 +1,59 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { RichTextEditor, type EditorPreset } from '@crux-kit/editor-tiptap';
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '#shared/ui/form';
/**
* Free rich-text field backed by @crux-kit/editor-tiptap (TipTap). Same
* value/onChange (HTML string) seam as InputMCEEditor, so it is a drop-in
* replacement.
*/
export const InputTiptapEditor: React.FC<{
name: string;
label?: string;
description?: React.ReactNode;
placeholder?: string;
required?: boolean;
disabled?: boolean;
preset?: EditorPreset;
}> = ({
name,
description,
label,
placeholder,
required = false,
disabled = false,
preset = 'light'
}) => {
const form = useFormContext();
return (
<FormField
control={form.control}
name={name}
disabled={disabled}
render={({ field }) => {
return (
<FormItem>
{label && (
<FormLabel>
{label}
{required && <span className="ml-1 text-red-500">*</span>}
</FormLabel>
)}
<FormControl>
<RichTextEditor
value={field.value ?? ''}
onChange={field.onChange}
disabled={field.disabled}
placeholder={placeholder}
preset={preset}
/>
</FormControl>
<FormMessage>{description}</FormMessage>
</FormItem>
);
}}
/>
);
};
@@ -30,3 +30,4 @@ export * from './fields/InputDate';
/* Text editor fields */
export * from './fields/InputMCEEditor';
export * from './fields/InputTiptapEditor';
@@ -1,60 +0,0 @@
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '#shared/ui/Accordion';
import { Card, CardContent, CardHeader, CardTitle } from '#shared/ui/Card';
export interface FaqItem {
title: string;
text: string;
}
export type FaqItems = Array<FaqItem>;
export function Faq() {
const items: FaqItems = [
{
title: 'How is pricing determined for each plan ?',
text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision. Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision. Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision"
},
{
title: 'What payment methods are accepted for subscriptions ?',
text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision"
},
{
title: 'Are there any hidden fees in the pricing ?',
text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision"
},
{
title: 'Is there a discount for annual subscriptions ?',
text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision"
},
{
title: 'Do you offer refunds on subscription cancellations ?',
text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision"
},
{
title: 'Can I add extra features to my current plan ?',
text: "Metronic embraces flexible licensing options that empower you to choose the perfect fit for your project's needs and budget. Understanding the factors influencing each plan's pricing helps you make an informed decision"
}
];
const generateItems = () => {
return (
<Accordion type="single" collapsible>
{items.map((item, index) => (
<AccordionItem key={index} value={`item-${index}`}>
<AccordionTrigger>{item.title}</AccordionTrigger>
<AccordionContent>{item.text}</AccordionContent>
</AccordionItem>
))}
</Accordion>
);
};
return (
<Card>
<CardHeader>
<CardTitle>FAQ</CardTitle>
</CardHeader>
<CardContent className="py-3">{generateItems()}</CardContent>
</Card>
);
}
+6
View File
@@ -30,6 +30,12 @@ export default defineConfig(({ mode }) => {
new URL('../../packages/design-system/keenicon/src', import.meta.url)
)
},
{
find: '@crux-kit/editor-tiptap',
replacement: fileURLToPath(
new URL('../../packages/design-system/editor-tiptap/src', import.meta.url)
)
},
{
find: '@crux-kit/hooks',
replacement: fileURLToPath(new URL('../../packages/hooks/src', import.meta.url))