pancy fants

This commit is contained in:
john 2025-05-06 12:53:32 +02:00
parent a4fd3a3556
commit b6633d6f25
15 changed files with 339 additions and 116 deletions

View file

@ -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
View 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>
)
}

View file

@ -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>
)

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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}`}
/>
)
}