pancy fants
This commit is contained in:
parent
a4fd3a3556
commit
b6633d6f25
15 changed files with 339 additions and 116 deletions
|
@ -18,8 +18,8 @@ export default function FancyTextEditor({
|
|||
value: _value,
|
||||
onInput,
|
||||
onKeyDown,
|
||||
className,
|
||||
placeholder,
|
||||
className: extraClasses = '',
|
||||
placeholder = '',
|
||||
}: TextInputProps) {
|
||||
const divRef = useRef<HTMLDivElement>(null)
|
||||
const [hasFocus, setHasFocus] = useState(false)
|
||||
|
@ -39,7 +39,7 @@ export default function FancyTextEditor({
|
|||
}
|
||||
|
||||
if (!value && !hasFocus) {
|
||||
div.innerText = placeholder ?? ''
|
||||
div.innerText = placeholder
|
||||
} else if (div.innerText !== value) {
|
||||
div.innerText = value
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export default function FancyTextEditor({
|
|||
const blurListener = () => {
|
||||
setHasFocus(false)
|
||||
if (!value) {
|
||||
div.innerText = placeholder ?? ''
|
||||
div.innerText = placeholder
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ export default function FancyTextEditor({
|
|||
return (
|
||||
<div
|
||||
ref={divRef}
|
||||
className={`w-full p-3 resize-none border border-gray-200 rounded-md focus:outline-none focus:border-gray-300 ${textColour} min-h-30 ${className ?? ''}`}
|
||||
className={`text-input w-full min-h-30 ${textColour} ${extraClasses}`}
|
||||
contentEditable
|
||||
onKeyDown={handleKeyDown}
|
||||
suppressContentEditableWarning
|
||||
|
|
11
src/components/NavBar.tsx
Normal file
11
src/components/NavBar.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Link } from 'react-router'
|
||||
|
||||
export default function NavBar() {
|
||||
return (
|
||||
<nav className={`w-full flex flex-row-reverse px-4 md:px-8 py-0.5`}>
|
||||
<Link className={`text-gray-800`} to="/signup">
|
||||
create account
|
||||
</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import { useState, ChangeEvent } from 'react'
|
||||
import { useState } from 'react'
|
||||
import FancyTextEditor, { TextInputKeyDownEvent } from './FancyTextEditor.tsx'
|
||||
import PrimaryButton from './PrimaryButton.tsx'
|
||||
import SecondaryButton from './SecondaryButton.tsx'
|
||||
import { openFileDialog } from '../utils/openFileDialog.ts'
|
||||
|
||||
interface NewPostWidgetProps {
|
||||
onSubmit: (content: string, media: File[]) => void
|
||||
|
@ -20,20 +23,19 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
|
|||
setContent(value)
|
||||
}
|
||||
|
||||
const handleMediaChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const inputEl = e.target as HTMLInputElement
|
||||
if (inputEl.files == null || inputEl.files.length === 0) {
|
||||
async function onAddMediaClicked() {
|
||||
const files = await openFileDialog('image/*', true)
|
||||
if (files == null || files.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const newFiles = Array.from(inputEl.files).map((file) => ({
|
||||
const newFiles = Array.from(files).map((file) => ({
|
||||
id: crypto.randomUUID(),
|
||||
file,
|
||||
objectUrl: URL.createObjectURL(file),
|
||||
}))
|
||||
|
||||
setAttachments((attachments) => [...attachments, ...newFiles])
|
||||
inputEl.value = ''
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
|
@ -90,24 +92,13 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
|
|||
)}
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<label className="cursor-pointer text-gray-500 hover:text-gray-700">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleMediaChange}
|
||||
className="hidden"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<span className="flex items-center">+ Add media</span>
|
||||
</label>
|
||||
|
||||
<button
|
||||
className="px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
|
||||
<SecondaryButton onClick={onAddMediaClicked}>+ add media</SecondaryButton>
|
||||
<PrimaryButton
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting || (content.trim() === '' && attachments.length === 0)}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
post
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
27
src/components/PrimaryButton.tsx
Normal file
27
src/components/PrimaryButton.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface PrimaryButtonProps {
|
||||
disabled?: boolean
|
||||
type?: 'submit' | 'button'
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function PrimaryButton({
|
||||
disabled = false,
|
||||
type = 'button',
|
||||
onClick = () => {},
|
||||
className: extraClasses = '',
|
||||
children,
|
||||
}: PropsWithChildren<PrimaryButtonProps>) {
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={`primary-button ${extraClasses}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
18
src/components/PrimaryLinkButton.tsx
Normal file
18
src/components/PrimaryLinkButton.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface PrimaryLinkButtonProps {
|
||||
href: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function PrimaryLinkButton({
|
||||
href,
|
||||
className: extraClasses = '',
|
||||
children,
|
||||
}: PropsWithChildren<PrimaryLinkButtonProps>) {
|
||||
return (
|
||||
<a href={href} className={`primary-button text-center ${extraClasses}`}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
31
src/components/SecondaryButton.tsx
Normal file
31
src/components/SecondaryButton.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { PropsWithChildren } from 'react'
|
||||
interface PrimaryButtonProps {
|
||||
disabled?: boolean
|
||||
type?: 'submit' | 'button'
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function SecondaryButton({
|
||||
disabled = false,
|
||||
type = 'button',
|
||||
onClick = () => {},
|
||||
className: extraClasses = '',
|
||||
children,
|
||||
}: PropsWithChildren<PrimaryButtonProps>) {
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={`
|
||||
px-4 p-2 rounded-md
|
||||
text-primary-500 hover:text-primary-700
|
||||
cursor-pointer disabled:cursor-default
|
||||
${extraClasses}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
31
src/components/TextInput.tsx
Normal file
31
src/components/TextInput.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
interface TextInputProps {
|
||||
id?: string
|
||||
type?: 'text' | 'email' | 'password'
|
||||
value: string
|
||||
onInput: (value: string) => void
|
||||
className?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
export default function TextInput({
|
||||
id,
|
||||
value,
|
||||
onInput,
|
||||
className: extraClasses = '',
|
||||
placeholder = '',
|
||||
type = 'text',
|
||||
required = false,
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<input
|
||||
id={id}
|
||||
value={value}
|
||||
type={type}
|
||||
required={required}
|
||||
onChange={(e) => onInput(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className={`text-input w-full ${extraClasses}`}
|
||||
/>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue