Implement rich text for project description
This commit is contained in:
@ -141,7 +141,7 @@ export const theme = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const darkMode = ['class', '[data-theme="dark"]']
|
export const darkMode = ['class', '[data-theme="dark"]']
|
||||||
export const plugins = [require('daisyui')]
|
export const plugins = [require('@tailwindcss/typography'), require('daisyui')]
|
||||||
export const daisyui = {
|
export const daisyui = {
|
||||||
themes: [
|
themes: [
|
||||||
'light',
|
'light',
|
||||||
|
@ -19,6 +19,13 @@
|
|||||||
"@redwoodjs/router": "8.3.0",
|
"@redwoodjs/router": "8.3.0",
|
||||||
"@redwoodjs/web": "8.3.0",
|
"@redwoodjs/web": "8.3.0",
|
||||||
"@redwoodjs/web-server": "8.3.0",
|
"@redwoodjs/web-server": "8.3.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@tiptap/extension-link": "^2.8.0",
|
||||||
|
"@tiptap/extension-text-style": "^2.8.0",
|
||||||
|
"@tiptap/extension-underline": "^2.8.0",
|
||||||
|
"@tiptap/pm": "^2.8.0",
|
||||||
|
"@tiptap/react": "^2.8.0",
|
||||||
|
"@tiptap/starter-kit": "^2.8.0",
|
||||||
"@uppy/compressor": "^2.0.1",
|
"@uppy/compressor": "^2.0.1",
|
||||||
"@uppy/core": "^4.1.0",
|
"@uppy/core": "^4.1.0",
|
||||||
"@uppy/dashboard": "^4.0.2",
|
"@uppy/dashboard": "^4.0.2",
|
||||||
@ -33,12 +40,14 @@
|
|||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
|
"react-html-parser": "^2.0.2",
|
||||||
"react-typed": "^2.0.12"
|
"react-typed": "^2.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redwoodjs/vite": "8.3.0",
|
"@redwoodjs/vite": "8.3.0",
|
||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.55",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.19",
|
||||||
|
"@types/react-html-parser": "^2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"postcss": "^8.4.41",
|
"postcss": "^8.4.41",
|
||||||
|
@ -5,7 +5,6 @@ import { AuthProvider, useAuth } from 'src/auth'
|
|||||||
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
||||||
import Routes from 'src/Routes'
|
import Routes from 'src/Routes'
|
||||||
|
|
||||||
import 'src/scaffold.css'
|
|
||||||
import 'src/index.css'
|
import 'src/index.css'
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import parseHtml from 'react-html-parser'
|
||||||
import type {
|
import type {
|
||||||
DeleteProjectMutation,
|
DeleteProjectMutation,
|
||||||
DeleteProjectMutationVariables,
|
DeleteProjectMutationVariables,
|
||||||
@ -68,7 +69,11 @@ const AdminProject = ({ project }: Props) => {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<td>{project.description}</td>
|
<td>
|
||||||
|
<article className="prose">
|
||||||
|
{parseHtml(project.description)}
|
||||||
|
</article>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { mdiLinkVariant } from '@mdi/js'
|
import { mdiLinkVariant } from '@mdi/js'
|
||||||
import Icon from '@mdi/react'
|
import Icon from '@mdi/react'
|
||||||
import { format, isAfter, startOfToday } from 'date-fns'
|
import { format, isAfter, startOfToday } from 'date-fns'
|
||||||
|
import parseHtml from 'react-html-parser'
|
||||||
import type { FindProjectById } from 'types/graphql'
|
import type { FindProjectById } from 'types/graphql'
|
||||||
|
|
||||||
import { calculateLuminance } from 'src/lib/color'
|
import { calculateLuminance } from 'src/lib/color'
|
||||||
@ -41,7 +42,9 @@ const Project = ({ project }: Props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{project.description && <p>{project.description}</p>}
|
{project.description && (
|
||||||
|
<article className="prose">{parseHtml(project.description)}</article>
|
||||||
|
)}
|
||||||
{project.links.length > 0 && (
|
{project.links.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h2 className="font-bold text-3xl w-fit">Links</h2>
|
<h2 className="font-bold text-3xl w-fit">Links</h2>
|
||||||
|
@ -2,6 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
|
|
||||||
import { mdiCalendar, mdiDelete, mdiFormatTitle, mdiLinkVariant } from '@mdi/js'
|
import { mdiCalendar, mdiDelete, mdiFormatTitle, mdiLinkVariant } from '@mdi/js'
|
||||||
import Icon from '@mdi/react'
|
import Icon from '@mdi/react'
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
import Underline from '@tiptap/extension-underline'
|
||||||
|
import { useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import { Meta, UploadResult } from '@uppy/core'
|
import { Meta, UploadResult } from '@uppy/core'
|
||||||
import { format, isAfter, startOfToday } from 'date-fns'
|
import { format, isAfter, startOfToday } from 'date-fns'
|
||||||
import type {
|
import type {
|
||||||
@ -11,18 +15,12 @@ import type {
|
|||||||
} from 'types/graphql'
|
} from 'types/graphql'
|
||||||
|
|
||||||
import type { RWGqlError } from '@redwoodjs/forms'
|
import type { RWGqlError } from '@redwoodjs/forms'
|
||||||
import {
|
import { Form, FieldError, Label, TextField, Submit } from '@redwoodjs/forms'
|
||||||
Form,
|
|
||||||
FieldError,
|
|
||||||
Label,
|
|
||||||
TextField,
|
|
||||||
Submit,
|
|
||||||
TextAreaField,
|
|
||||||
} from '@redwoodjs/forms'
|
|
||||||
import { toast } from '@redwoodjs/web/toast'
|
import { toast } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
import DatePicker from 'src/components/DatePicker'
|
import DatePicker from 'src/components/DatePicker'
|
||||||
import FormTextList from 'src/components/FormTextList'
|
import FormTextList from 'src/components/FormTextList'
|
||||||
|
import RichTextEditor from 'src/components/RichTextEditor/RichTextEditor'
|
||||||
import TagsSelectorCell from 'src/components/Tag/TagsSelectorCell'
|
import TagsSelectorCell from 'src/components/Tag/TagsSelectorCell'
|
||||||
import Uploader from 'src/components/Uploader'
|
import Uploader from 'src/components/Uploader'
|
||||||
import { batchDelete } from 'src/lib/tus'
|
import { batchDelete } from 'src/lib/tus'
|
||||||
@ -39,6 +37,20 @@ interface ProjectFormProps {
|
|||||||
const ProjectForm = (props: ProjectFormProps) => {
|
const ProjectForm = (props: ProjectFormProps) => {
|
||||||
const today = startOfToday()
|
const today = startOfToday()
|
||||||
|
|
||||||
|
const descEditor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Link.configure({
|
||||||
|
openOnClick: false,
|
||||||
|
autolink: true,
|
||||||
|
linkOnPaste: true,
|
||||||
|
defaultProtocol: 'https',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: props.project?.description || '',
|
||||||
|
})
|
||||||
|
|
||||||
const [links, setLinks] = useState<string[]>(props.project?.links || [])
|
const [links, setLinks] = useState<string[]>(props.project?.links || [])
|
||||||
const [linkErrors, setLinkErrors] = useState<boolean[]>([])
|
const [linkErrors, setLinkErrors] = useState<boolean[]>([])
|
||||||
const [pickerVisible, setPickerVisible] = useState<boolean>(false)
|
const [pickerVisible, setPickerVisible] = useState<boolean>(false)
|
||||||
@ -87,7 +99,7 @@ const ProjectForm = (props: ProjectFormProps) => {
|
|||||||
props.onSave(
|
props.onSave(
|
||||||
{
|
{
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: descEditor.getHTML(),
|
||||||
date: date.toISOString(),
|
date: date.toISOString(),
|
||||||
links: links.filter((link) => link.trim().length > 0),
|
links: links.filter((link) => link.trim().length > 0),
|
||||||
images: fileIds,
|
images: fileIds,
|
||||||
@ -144,21 +156,7 @@ const ProjectForm = (props: ProjectFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<Label name="description" className="form-control w-full">
|
<RichTextEditor editor={descEditor} />
|
||||||
<TextAreaField
|
|
||||||
name="description"
|
|
||||||
defaultValue={props.project?.description}
|
|
||||||
className="textarea textarea-bordered"
|
|
||||||
errorClassName="textarea textarea-bordered textarea-error"
|
|
||||||
placeholder="Description"
|
|
||||||
/>
|
|
||||||
<div className="label">
|
|
||||||
<FieldError
|
|
||||||
name="description"
|
|
||||||
className="text-xs font-semibold text-error"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<div className="form-control w-full">
|
<div className="form-control w-full">
|
||||||
<Label
|
<Label
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { mdiDotsVertical } from '@mdi/js'
|
import { mdiDotsVertical } from '@mdi/js'
|
||||||
import Icon from '@mdi/react'
|
import Icon from '@mdi/react'
|
||||||
import { isAfter } from 'date-fns'
|
import { isAfter } from 'date-fns'
|
||||||
|
import parseHtml from 'react-html-parser'
|
||||||
import type {
|
import type {
|
||||||
DeleteProjectMutation,
|
DeleteProjectMutation,
|
||||||
DeleteProjectMutationVariables,
|
DeleteProjectMutationVariables,
|
||||||
@ -94,7 +95,11 @@ const ProjectsList = ({ projects }: FindProjects) => {
|
|||||||
return (
|
return (
|
||||||
<tr key={project.id}>
|
<tr key={project.id}>
|
||||||
<td>{truncate(project.title)}</td>
|
<td>{truncate(project.title)}</td>
|
||||||
<td className="max-w-72">{truncate(project.description)}</td>
|
<td className="max-w-72">
|
||||||
|
<article className="prose text-sm line-clamp-3">
|
||||||
|
{parseHtml(project.description)}
|
||||||
|
</article>
|
||||||
|
</td>
|
||||||
<td className="max-w-36">{timeTag(project.date)}</td>
|
<td className="max-w-36">{timeTag(project.date)}</td>
|
||||||
<td>{`${project.images.length} ${project.images.length === 1 ? 'image' : 'images'}`}</td>
|
<td>{`${project.images.length} ${project.images.length === 1 ? 'image' : 'images'}`}</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useLayoutEffect, useRef, useState } from 'react'
|
import { useLayoutEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { format, isAfter, startOfToday } from 'date-fns'
|
import { format, isAfter, startOfToday } from 'date-fns'
|
||||||
|
import parseHtml from 'react-html-parser'
|
||||||
import { FindProjects } from 'types/graphql'
|
import { FindProjects } from 'types/graphql'
|
||||||
|
|
||||||
import { Link, routes } from '@redwoodjs/router'
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
@ -56,7 +57,11 @@ const ProjectsShowcase = ({ projects }: FindProjects) => {
|
|||||||
<div className="card-title overflow-auto">
|
<div className="card-title overflow-auto">
|
||||||
<p className="whitespace-nowrap">{project.title}</p>
|
<p className="whitespace-nowrap">{project.title}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="line-clamp-5">{project.description}</div>
|
<div className="line-clamp-5">
|
||||||
|
<article className="prose text-sm">
|
||||||
|
{parseHtml(project.description)}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
<div className="card-actions justify-between">
|
<div className="card-actions justify-between">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{isAfter(new Date(project.date), startOfToday()) && (
|
{isAfter(new Date(project.date), startOfToday()) && (
|
||||||
|
165
web/src/components/RichTextEditor/RichTextEditor.tsx
Normal file
165
web/src/components/RichTextEditor/RichTextEditor.tsx
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
mdiCodeBracesBox,
|
||||||
|
mdiFormatBold,
|
||||||
|
mdiFormatClear,
|
||||||
|
mdiFormatItalic,
|
||||||
|
mdiFormatListBulleted,
|
||||||
|
mdiFormatListNumbered,
|
||||||
|
mdiFormatQuoteClose,
|
||||||
|
mdiFormatStrikethrough,
|
||||||
|
mdiFormatUnderline,
|
||||||
|
mdiLinkVariant,
|
||||||
|
mdiLinkVariantOff,
|
||||||
|
mdiRedoVariant,
|
||||||
|
mdiUndoVariant,
|
||||||
|
mdiXml,
|
||||||
|
} from '@mdi/js'
|
||||||
|
import Icon from '@mdi/react'
|
||||||
|
import { EditorContent } from '@tiptap/react'
|
||||||
|
import type { Editor } from '@tiptap/react'
|
||||||
|
|
||||||
|
interface RichTextEditorProps {
|
||||||
|
editor: Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
const RichTextEditor = ({ editor }: RichTextEditorProps) => {
|
||||||
|
const setLink = useCallback(() => {
|
||||||
|
const previousUrl = editor.getAttributes('link').href
|
||||||
|
const url = window.prompt('URL', previousUrl)
|
||||||
|
|
||||||
|
if (url === null) return
|
||||||
|
|
||||||
|
if (url === '') {
|
||||||
|
editor.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
|
||||||
|
}, [editor])
|
||||||
|
|
||||||
|
if (!editor) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex flex-wrap gap-2 justify-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('link') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={setLink}
|
||||||
|
>
|
||||||
|
<Icon path={mdiLinkVariant} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-square"
|
||||||
|
onClick={() => editor.chain().focus().unsetLink().run()}
|
||||||
|
disabled={!editor.isActive('link')}
|
||||||
|
>
|
||||||
|
<Icon path={mdiLinkVariantOff} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('bulletList') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatListBulleted} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('orderedList') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatListNumbered} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-square"
|
||||||
|
onClick={() => editor.chain().focus().unsetAllMarks().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatClear} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-square"
|
||||||
|
onClick={() => editor.chain().focus().undo().run()}
|
||||||
|
disabled={!editor.can().chain().focus().undo().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiUndoVariant} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-square"
|
||||||
|
onClick={() => editor.chain().focus().redo().run()}
|
||||||
|
disabled={!editor.can().chain().focus().redo().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiRedoVariant} className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2 justify-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('bold') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleBold().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatBold} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('italic') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatItalic} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('underline') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleUnderline().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatUnderline} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('static') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleStrike().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatStrikethrough} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('code') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleCode().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiXml} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('codeBlock') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleCodeBlock().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiCodeBracesBox} className="size-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-square ${editor.isActive('blockquote') ? 'btn-primary' : ''}`}
|
||||||
|
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFormatQuoteClose} className="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<EditorContent
|
||||||
|
editor={editor}
|
||||||
|
className="textarea textarea-bordered font-normal prose"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RichTextEditor
|
@ -19,3 +19,7 @@
|
|||||||
.image-full-no-overlay::before {
|
.image-full-no-overlay::before {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ProseMirror:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
@ -1,243 +0,0 @@
|
|||||||
.rw-scaffold {
|
|
||||||
@apply bg-white text-gray-600;
|
|
||||||
}
|
|
||||||
.rw-scaffold h1,
|
|
||||||
.rw-scaffold h2 {
|
|
||||||
@apply m-0;
|
|
||||||
}
|
|
||||||
.rw-scaffold a {
|
|
||||||
@apply bg-transparent;
|
|
||||||
}
|
|
||||||
.rw-scaffold ul {
|
|
||||||
@apply m-0 p-0;
|
|
||||||
}
|
|
||||||
.rw-scaffold input:-ms-input-placeholder {
|
|
||||||
@apply text-gray-500;
|
|
||||||
}
|
|
||||||
.rw-scaffold input::-ms-input-placeholder {
|
|
||||||
@apply text-gray-500;
|
|
||||||
}
|
|
||||||
.rw-scaffold input::placeholder {
|
|
||||||
@apply text-gray-500;
|
|
||||||
}
|
|
||||||
.rw-header {
|
|
||||||
@apply flex justify-between px-8 py-4;
|
|
||||||
}
|
|
||||||
.rw-main {
|
|
||||||
@apply mx-4 pb-4;
|
|
||||||
}
|
|
||||||
.rw-segment {
|
|
||||||
@apply w-full overflow-hidden rounded-lg border border-gray-200;
|
|
||||||
scrollbar-color: theme('colors.zinc.400') transparent;
|
|
||||||
}
|
|
||||||
.rw-segment::-webkit-scrollbar {
|
|
||||||
height: initial;
|
|
||||||
}
|
|
||||||
.rw-segment::-webkit-scrollbar-track {
|
|
||||||
@apply rounded-b-[10px] rounded-t-none border-0 border-t border-solid border-gray-200 bg-transparent p-[2px];
|
|
||||||
}
|
|
||||||
.rw-segment::-webkit-scrollbar-thumb {
|
|
||||||
@apply rounded-full border-[3px] border-solid border-transparent bg-zinc-400 bg-clip-content;
|
|
||||||
}
|
|
||||||
.rw-segment-header {
|
|
||||||
@apply bg-gray-200 px-4 py-3 text-gray-700;
|
|
||||||
}
|
|
||||||
.rw-segment-main {
|
|
||||||
@apply bg-gray-100 p-4;
|
|
||||||
}
|
|
||||||
.rw-link {
|
|
||||||
@apply text-blue-400 underline;
|
|
||||||
}
|
|
||||||
.rw-link:hover {
|
|
||||||
@apply text-blue-500;
|
|
||||||
}
|
|
||||||
.rw-forgot-link {
|
|
||||||
@apply mt-1 text-right text-xs text-gray-400 underline;
|
|
||||||
}
|
|
||||||
.rw-forgot-link:hover {
|
|
||||||
@apply text-blue-500;
|
|
||||||
}
|
|
||||||
.rw-heading {
|
|
||||||
@apply font-semibold;
|
|
||||||
}
|
|
||||||
.rw-heading.rw-heading-primary {
|
|
||||||
@apply text-xl;
|
|
||||||
}
|
|
||||||
.rw-heading.rw-heading-secondary {
|
|
||||||
@apply text-sm;
|
|
||||||
}
|
|
||||||
.rw-heading .rw-link {
|
|
||||||
@apply text-gray-600 no-underline;
|
|
||||||
}
|
|
||||||
.rw-heading .rw-link:hover {
|
|
||||||
@apply text-gray-900 underline;
|
|
||||||
}
|
|
||||||
.rw-cell-error {
|
|
||||||
@apply text-sm font-semibold;
|
|
||||||
}
|
|
||||||
.rw-form-wrapper {
|
|
||||||
@apply -mt-4 text-sm;
|
|
||||||
}
|
|
||||||
.rw-cell-error,
|
|
||||||
.rw-form-error-wrapper {
|
|
||||||
@apply my-4 rounded border border-red-100 bg-red-50 p-4 text-red-600;
|
|
||||||
}
|
|
||||||
.rw-form-error-title {
|
|
||||||
@apply m-0 font-semibold;
|
|
||||||
}
|
|
||||||
.rw-form-error-list {
|
|
||||||
@apply mt-2 list-inside list-disc;
|
|
||||||
}
|
|
||||||
.rw-button {
|
|
||||||
@apply flex cursor-pointer justify-center rounded border-0 bg-gray-200 px-4 py-1 text-xs font-semibold uppercase leading-loose tracking-wide text-gray-500 no-underline transition duration-100;
|
|
||||||
}
|
|
||||||
.rw-button:hover {
|
|
||||||
@apply bg-gray-500 text-white;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-small {
|
|
||||||
@apply rounded-sm px-2 py-1 text-xs;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-green {
|
|
||||||
@apply bg-green-500 text-white;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-green:hover {
|
|
||||||
@apply bg-green-700;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-blue {
|
|
||||||
@apply bg-blue-500 text-white;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-blue:hover {
|
|
||||||
@apply bg-blue-700;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-red {
|
|
||||||
@apply bg-red-500 text-white;
|
|
||||||
}
|
|
||||||
.rw-button.rw-button-red:hover {
|
|
||||||
@apply bg-red-700 text-white;
|
|
||||||
}
|
|
||||||
.rw-button-icon {
|
|
||||||
@apply mr-1 text-xl leading-5;
|
|
||||||
}
|
|
||||||
.rw-button-group {
|
|
||||||
@apply mx-2 my-3 flex justify-center;
|
|
||||||
}
|
|
||||||
.rw-button-group .rw-button {
|
|
||||||
@apply mx-1;
|
|
||||||
}
|
|
||||||
.rw-form-wrapper .rw-button-group {
|
|
||||||
@apply mt-8;
|
|
||||||
}
|
|
||||||
.rw-label {
|
|
||||||
@apply mt-6 block text-left font-semibold text-gray-600;
|
|
||||||
}
|
|
||||||
.rw-label.rw-label-error {
|
|
||||||
@apply text-red-600;
|
|
||||||
}
|
|
||||||
.rw-input {
|
|
||||||
@apply mt-2 block w-full rounded border border-gray-200 bg-white p-2 outline-none;
|
|
||||||
}
|
|
||||||
.rw-check-radio-items {
|
|
||||||
@apply flex justify-items-center;
|
|
||||||
}
|
|
||||||
.rw-check-radio-item-none {
|
|
||||||
@apply text-gray-600;
|
|
||||||
}
|
|
||||||
.rw-input[type='checkbox'],
|
|
||||||
.rw-input[type='radio'] {
|
|
||||||
@apply ml-0 mr-1 mt-1 inline w-4;
|
|
||||||
}
|
|
||||||
.rw-input:focus {
|
|
||||||
@apply border-gray-400;
|
|
||||||
}
|
|
||||||
.rw-input-error {
|
|
||||||
@apply border-red-600 text-red-600;
|
|
||||||
}
|
|
||||||
.rw-input-error:focus {
|
|
||||||
@apply border-red-600 outline-none;
|
|
||||||
box-shadow: 0 0 5px #c53030;
|
|
||||||
}
|
|
||||||
.rw-field-error {
|
|
||||||
@apply mt-1 block text-xs font-semibold uppercase text-red-600;
|
|
||||||
}
|
|
||||||
.rw-table-wrapper-responsive {
|
|
||||||
@apply overflow-x-auto;
|
|
||||||
}
|
|
||||||
.rw-table-wrapper-responsive .rw-table {
|
|
||||||
min-width: 48rem;
|
|
||||||
}
|
|
||||||
.rw-table {
|
|
||||||
@apply w-full text-sm;
|
|
||||||
}
|
|
||||||
.rw-table th,
|
|
||||||
.rw-table td {
|
|
||||||
@apply p-3;
|
|
||||||
}
|
|
||||||
.rw-table td {
|
|
||||||
@apply bg-white text-gray-900;
|
|
||||||
}
|
|
||||||
.rw-table tr:nth-child(odd) td,
|
|
||||||
.rw-table tr:nth-child(odd) th {
|
|
||||||
@apply bg-gray-50;
|
|
||||||
}
|
|
||||||
.rw-table thead tr {
|
|
||||||
@apply bg-gray-200 text-gray-600;
|
|
||||||
}
|
|
||||||
.rw-table th {
|
|
||||||
@apply text-left font-semibold;
|
|
||||||
}
|
|
||||||
.rw-table thead th {
|
|
||||||
@apply text-left;
|
|
||||||
}
|
|
||||||
.rw-table tbody th {
|
|
||||||
@apply text-right;
|
|
||||||
}
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.rw-table tbody th {
|
|
||||||
@apply w-1/5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.rw-table tbody tr {
|
|
||||||
@apply border-t border-gray-200;
|
|
||||||
}
|
|
||||||
.rw-table input {
|
|
||||||
@apply ml-0;
|
|
||||||
}
|
|
||||||
.rw-table-actions {
|
|
||||||
@apply flex h-4 items-center justify-end pr-1;
|
|
||||||
}
|
|
||||||
.rw-table-actions .rw-button {
|
|
||||||
@apply bg-transparent;
|
|
||||||
}
|
|
||||||
.rw-table-actions .rw-button:hover {
|
|
||||||
@apply bg-gray-500 text-white;
|
|
||||||
}
|
|
||||||
.rw-table-actions .rw-button-blue {
|
|
||||||
@apply text-blue-500;
|
|
||||||
}
|
|
||||||
.rw-table-actions .rw-button-blue:hover {
|
|
||||||
@apply bg-blue-500 text-white;
|
|
||||||
}
|
|
||||||
.rw-table-actions .rw-button-red {
|
|
||||||
@apply text-red-600;
|
|
||||||
}
|
|
||||||
.rw-table-actions .rw-button-red:hover {
|
|
||||||
@apply bg-red-600 text-white;
|
|
||||||
}
|
|
||||||
.rw-text-center {
|
|
||||||
@apply text-center;
|
|
||||||
}
|
|
||||||
.rw-login-container {
|
|
||||||
@apply mx-auto my-16 flex w-96 flex-wrap items-center justify-center;
|
|
||||||
}
|
|
||||||
.rw-login-container .rw-form-wrapper {
|
|
||||||
@apply w-full text-center;
|
|
||||||
}
|
|
||||||
.rw-login-link {
|
|
||||||
@apply mt-4 w-full text-center text-sm text-gray-600;
|
|
||||||
}
|
|
||||||
.rw-webauthn-wrapper {
|
|
||||||
@apply mx-4 mt-6 leading-6;
|
|
||||||
}
|
|
||||||
.rw-webauthn-wrapper h2 {
|
|
||||||
@apply mb-4 text-xl font-bold;
|
|
||||||
}
|
|
Reference in New Issue
Block a user