Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Created December 18, 2025 01:39
Show Gist options
  • Select an option

  • Save ryanflorence/0a7edf867b7b756e0e3775c3e54e13f0 to your computer and use it in GitHub Desktop.

Select an option

Save ryanflorence/0a7edf867b7b756e0e3775c3e54e13f0 to your computer and use it in GitHub Desktop.
import { type Handle } from '@remix-run/component'
import { escape } from '@remix-run/interaction/keys'
import type { Card as CardType } from '../types.ts'
import { App } from './App.tsx'
export function Card(this: Handle) {
let isEditing = false
let titleInput: HTMLInputElement
let cardDiv: HTMLDivElement
let { updateCard, deleteCard } = this.context.get(App)
return ({ card }: { card: CardType }) => {
return (
<div
tabIndex={-1}
connect={(node) => (cardDiv = node)}
css={{
backgroundColor: '#fff',
borderRadius: '8px',
padding: '12px',
marginBottom: '8px',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
cursor: 'pointer',
transition: 'box-shadow 0.2s',
'&:hover': {
boxShadow: '0 2px 6px rgba(0, 0, 0, 0.15)',
},
}}
on={{
click: () => {
if (!isEditing) {
isEditing = true
this.update(() => {
titleInput?.select()
})
}
},
}}
>
{isEditing ? (
<form
on={{
submit: (event) => {
event.preventDefault()
let formData = new FormData(event.currentTarget)
updateCard({ ...card, ...Object.fromEntries(formData) })
isEditing = false
this.update(() => {
cardDiv.focus()
})
},
}}
css={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
}}
>
<input
type="text"
name="title"
defaultValue={card.title}
connect={(node) => (titleInput = node)}
css={{
border: '1px solid #ddd',
borderRadius: '4px',
padding: '6px 8px',
fontSize: '14px',
fontWeight: '600',
'&:focus': {
outline: 'none',
borderColor: '#4a90e2',
},
}}
on={{
[escape]: () => {
isEditing = false
this.update(() => {
cardDiv?.focus()
})
},
}}
/>
<textarea
name="description"
defaultValue={card.description}
css={{
border: '1px solid #ddd',
borderRadius: '4px',
padding: '6px 8px',
fontSize: '13px',
fontFamily: 'inherit',
resize: 'vertical',
minHeight: '60px',
'&:focus': {
outline: 'none',
borderColor: '#4a90e2',
},
}}
on={{
[escape]: () => {
isEditing = false
this.update(() => {
cardDiv?.focus()
})
},
}}
/>
<div
css={{
display: 'flex',
gap: '8px',
justifyContent: 'flex-end',
}}
>
<button
type="submit"
css={{
padding: '6px 12px',
fontSize: '12px',
border: 'none',
borderRadius: '4px',
backgroundColor: '#4a90e2',
color: 'white',
cursor: 'pointer',
'&:hover': {
backgroundColor: '#357abd',
},
}}
>
Save
</button>
<button
type="button"
css={{
padding: '6px 12px',
fontSize: '12px',
border: '1px solid #ddd',
borderRadius: '4px',
backgroundColor: 'white',
cursor: 'pointer',
'&:hover': {
backgroundColor: '#f5f5f5',
},
}}
on={{
click: (event) => {
event.stopPropagation()
isEditing = false
this.update(() => {
cardDiv?.focus()
})
},
}}
>
Cancel
</button>
<button
type="button"
css={{
padding: '6px 12px',
fontSize: '12px',
border: 'none',
borderRadius: '4px',
backgroundColor: '#e74c3c',
color: 'white',
cursor: 'pointer',
'&:hover': {
backgroundColor: '#c0392b',
},
}}
on={{
click: (event) => {
event.stopPropagation()
deleteCard(card.id)
},
}}
>
Delete
</button>
</div>
</form>
) : (
<div>
<div
css={{
fontSize: '14px',
fontWeight: '600',
marginBottom: '4px',
color: '#333',
}}
>
{card.title}
</div>
{card.description && (
<div
css={{
fontSize: '12px',
color: '#666',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{card.description}
</div>
)}
</div>
)}
</div>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment