Polishing touches and tweaks
This commit is contained in:
@ -142,4 +142,16 @@ 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('daisyui')]
|
||||||
export const daisyui = { themes: ['light', 'dark'] }
|
export const daisyui = {
|
||||||
|
themes: [
|
||||||
|
'light',
|
||||||
|
{
|
||||||
|
dark: {
|
||||||
|
...require('daisyui/src/theming/themes')['dark'],
|
||||||
|
'base-100': '#212121',
|
||||||
|
'base-200': '#1d1d1d',
|
||||||
|
'base-300': '#191919',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ const ColorPicker = ({ color, setColor }: ColorPickerProps) => {
|
|||||||
<HexColorInput color={color} className="w-16" />
|
<HexColorInput color={color} className="w-16" />
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="btn btn-square btn-sm"
|
className="btn btn-square btn-sm"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
@ -35,6 +36,7 @@ const ColorPicker = ({ color, setColor }: ColorPickerProps) => {
|
|||||||
<Icon path={mdiContentCopy} className="size-4" />
|
<Icon path={mdiContentCopy} className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="btn btn-square btn-sm "
|
className="btn btn-square btn-sm "
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef, useEffect } from 'react'
|
import { useState, useRef, useLayoutEffect } from 'react'
|
||||||
|
|
||||||
import SocialLinksCell from 'src/components/Social/SocialLinksCell'
|
import SocialLinksCell from 'src/components/Social/SocialLinksCell'
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ const ContactCard = ({ portraitUrl }: ContactCardProps) => {
|
|||||||
|
|
||||||
const observedDiv = useRef(null)
|
const observedDiv = useRef(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!observedDiv.current) return
|
if (!observedDiv.current) return
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
@ -4,6 +4,7 @@ import Icon from '@mdi/react'
|
|||||||
|
|
||||||
interface FormTextListProps {
|
interface FormTextListProps {
|
||||||
name: string
|
name: string
|
||||||
|
hint?: string
|
||||||
itemPlaceholder: string
|
itemPlaceholder: string
|
||||||
icon?: string
|
icon?: string
|
||||||
list: string[]
|
list: string[]
|
||||||
@ -13,6 +14,7 @@ interface FormTextListProps {
|
|||||||
|
|
||||||
const FormTextList = ({
|
const FormTextList = ({
|
||||||
name,
|
name,
|
||||||
|
hint,
|
||||||
itemPlaceholder,
|
itemPlaceholder,
|
||||||
icon,
|
icon,
|
||||||
list,
|
list,
|
||||||
@ -23,15 +25,20 @@ const FormTextList = ({
|
|||||||
<div className="flex flex-col space-y-2 bg-base-100 rounded-xl">
|
<div className="flex flex-col space-y-2 bg-base-100 rounded-xl">
|
||||||
<div className="flex space-x-2 justify-between">
|
<div className="flex space-x-2 justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<p className="font-semibold">{name}</p>
|
<p className="font-semibold text-center">{name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
{hint && (
|
||||||
|
<p className="opacity-70 text-xs font-light text-center">{hint}</p>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="btn btn-square btn-sm"
|
||||||
|
type="button"
|
||||||
|
onClick={() => setList([...list, ''])}
|
||||||
|
>
|
||||||
|
<Icon path={mdiPlus} className="size-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
className="btn btn-square btn-sm"
|
|
||||||
type="button"
|
|
||||||
onClick={() => setList([...list, ''])}
|
|
||||||
>
|
|
||||||
<Icon path={mdiPlus} className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{list.map((item, i) => (
|
{list.map((item, i) => (
|
||||||
<label
|
<label
|
||||||
@ -53,11 +60,11 @@ const FormTextList = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="btn btn-square btn-sm flex-none"
|
className="btn btn-square btn-error btn-sm flex-none"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setList(list.filter((_, j) => j !== i))}
|
onClick={() => setList(list.filter((_, j) => j !== i))}
|
||||||
>
|
>
|
||||||
<Icon path={mdiDelete} className="size-4 text-error" />
|
<Icon path={mdiDelete} className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useRef, useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Meta, UploadResult } from '@uppy/core'
|
import { Meta, UploadResult } from '@uppy/core'
|
||||||
import type {
|
import type {
|
||||||
@ -56,13 +56,7 @@ const CREATE_PORTRAIT_MUTATION: TypedDocumentNode<
|
|||||||
`
|
`
|
||||||
|
|
||||||
const PortraitForm = ({ portrait }: PortraitFormProps) => {
|
const PortraitForm = ({ portrait }: PortraitFormProps) => {
|
||||||
const [fileId, _setFileId] = useState<string>(portrait?.fileId)
|
const [fileId, setFileId] = useState<string>(portrait?.fileId)
|
||||||
const fileIdRef = useRef<string>(fileId)
|
|
||||||
|
|
||||||
const setFileId = (fileId: string) => {
|
|
||||||
_setFileId(fileId)
|
|
||||||
fileIdRef.current = fileId
|
|
||||||
}
|
|
||||||
|
|
||||||
const unloadAbortController = new AbortController()
|
const unloadAbortController = new AbortController()
|
||||||
|
|
||||||
@ -96,7 +90,7 @@ const PortraitForm = ({ portrait }: PortraitFormProps) => {
|
|||||||
setFileId(result.successful[0]?.uploadURL)
|
setFileId(result.successful[0]?.uploadURL)
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'beforeunload',
|
'beforeunload',
|
||||||
(e) => handleBeforeUnload(e, [fileIdRef.current]),
|
(e) => handleBeforeUnload(e, [fileId]),
|
||||||
{
|
{
|
||||||
once: true,
|
once: true,
|
||||||
signal: unloadAbortController.signal,
|
signal: unloadAbortController.signal,
|
||||||
|
@ -46,18 +46,23 @@ const Project = ({ project }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<h2 className="font-bold text-3xl w-fit">Links</h2>
|
<h2 className="font-bold text-3xl w-fit">Links</h2>
|
||||||
<div className="flex flex-col gap-2 w-fit">
|
<div className="flex flex-col gap-2 w-fit">
|
||||||
{project.links.map((link, i) => (
|
<ul className="list-none">
|
||||||
<a
|
{project.links.map((link, i) => (
|
||||||
key={i}
|
<li key={i}>
|
||||||
href={link}
|
<div className="flex gap-2 items-center justify-start">
|
||||||
target="_blank"
|
<Icon path={mdiLinkVariant} className="size-4" />
|
||||||
rel="noreferrer"
|
<a
|
||||||
className="btn btn-wide"
|
href={link}
|
||||||
>
|
target="_blank"
|
||||||
<Icon path={mdiLinkVariant} className="size-5" />
|
rel="noreferrer"
|
||||||
{link}
|
className="list-item link link-hover"
|
||||||
</a>
|
>
|
||||||
))}
|
{link}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -198,6 +198,7 @@ const ProjectForm = (props: ProjectFormProps) => {
|
|||||||
<div className={`${!pickerVisible && 'pt-2'}`}>
|
<div className={`${!pickerVisible && 'pt-2'}`}>
|
||||||
<FormTextList
|
<FormTextList
|
||||||
name="Links"
|
name="Links"
|
||||||
|
hint="Short links are recommended"
|
||||||
itemPlaceholder="URL"
|
itemPlaceholder="URL"
|
||||||
icon={mdiLinkVariant}
|
icon={mdiLinkVariant}
|
||||||
list={links}
|
list={links}
|
||||||
@ -246,16 +247,13 @@ const ProjectForm = (props: ProjectFormProps) => {
|
|||||||
<div className="card-actions rounded-md justify-end">
|
<div className="card-actions rounded-md justify-end">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-square btn-sm shadow-xl"
|
className="btn btn-square btn-sm btn-error shadow-xl"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setToDelete([...toDelete, fileId])
|
setToDelete([...toDelete, fileId])
|
||||||
setFileIds(fileIds.filter((id) => id !== fileId))
|
setFileIds(fileIds.filter((id) => id !== fileId))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon path={mdiDelete} className="size-4" />
|
||||||
path={mdiDelete}
|
|
||||||
className="size-4 text-error"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useRef, useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Meta, UploadResult } from '@uppy/core'
|
import { Meta, UploadResult } from '@uppy/core'
|
||||||
import type {
|
import type {
|
||||||
@ -55,13 +55,7 @@ const CREATE_RESUME_MUTATION: TypedDocumentNode<
|
|||||||
`
|
`
|
||||||
|
|
||||||
const ResumeForm = ({ resume }: ResumeFormProps) => {
|
const ResumeForm = ({ resume }: ResumeFormProps) => {
|
||||||
const [fileId, _setFileId] = useState<string>(resume?.fileId)
|
const [fileId, setFileId] = useState<string>(resume?.fileId)
|
||||||
const fileIdRef = useRef<string>(fileId)
|
|
||||||
|
|
||||||
const setFileId = (fileId: string) => {
|
|
||||||
_setFileId(fileId)
|
|
||||||
fileIdRef.current = fileId
|
|
||||||
}
|
|
||||||
|
|
||||||
const unloadAbortController = new AbortController()
|
const unloadAbortController = new AbortController()
|
||||||
|
|
||||||
@ -95,7 +89,7 @@ const ResumeForm = ({ resume }: ResumeFormProps) => {
|
|||||||
setFileId(result.successful[0]?.uploadURL)
|
setFileId(result.successful[0]?.uploadURL)
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'beforeunload',
|
'beforeunload',
|
||||||
(e) => handleBeforeUnload(e, [fileIdRef.current]),
|
(e) => handleBeforeUnload(e, [fileId]),
|
||||||
{
|
{
|
||||||
once: true,
|
once: true,
|
||||||
signal: unloadAbortController.signal,
|
signal: unloadAbortController.signal,
|
||||||
|
@ -8,7 +8,7 @@ const DARK_THEME = 'dark'
|
|||||||
|
|
||||||
const ThemeToggle = () => {
|
const ThemeToggle = () => {
|
||||||
const [theme, setTheme] = useState(
|
const [theme, setTheme] = useState(
|
||||||
localStorage.getItem('theme') ? localStorage.getItem('theme') : LIGHT_THEME
|
localStorage.getItem('theme') ?? LIGHT_THEME
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleToggle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleToggle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -37,13 +37,8 @@ const ThemeToggle = () => {
|
|||||||
checked={theme === DARK_THEME}
|
checked={theme === DARK_THEME}
|
||||||
onChange={handleToggle}
|
onChange={handleToggle}
|
||||||
/>
|
/>
|
||||||
|
<Icon path={mdiWeatherSunny} className="swap-off size-8 text-warning" />
|
||||||
<Icon
|
<Icon path={mdiWeatherNight} className="swap-on size-8 text-primary" />
|
||||||
path={mdiWeatherSunny}
|
|
||||||
className="swap-off size-8 text-yellow-500"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon path={mdiWeatherNight} className="swap-on size-8 text-blue-500" />
|
|
||||||
</label>
|
</label>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,12 @@ export const Titles = ({ titles, className }: TitlesProps) => {
|
|||||||
backDelay={1000}
|
backDelay={1000}
|
||||||
startWhenVisible
|
startWhenVisible
|
||||||
loop
|
loop
|
||||||
|
onStringTyped={(pos, self) => {
|
||||||
|
if (pos === 0) {
|
||||||
|
self.stop()
|
||||||
|
setTimeout(() => self.start(), 2500)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -56,6 +56,9 @@ const TitlesForm = ({ titles }: TitlesFormProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={onSubmit} className="max-w-80 space-y-2">
|
<Form onSubmit={onSubmit} className="max-w-80 space-y-2">
|
||||||
|
<p className="text-center opacity-70">
|
||||||
|
The first one gets displayed for longer
|
||||||
|
</p>
|
||||||
{Array.from({ length: MAX_TITLES }).map((_, i) => (
|
{Array.from({ length: MAX_TITLES }).map((_, i) => (
|
||||||
<Label key={i} name={`title${i}`} className="form-control w-full">
|
<Label key={i} name={`title${i}`} className="form-control w-full">
|
||||||
<Label
|
<Label
|
||||||
|
@ -8,9 +8,8 @@ export const deleteFile = async (fileId: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleBeforeUnload = (_e: BeforeUnloadEvent, files: string[]) => {
|
export const handleBeforeUnload = (_e: BeforeUnloadEvent, files: string[]) =>
|
||||||
batchDelete(files)
|
batchDelete(files)
|
||||||
}
|
|
||||||
|
|
||||||
export const batchDelete = (files: string[]) => {
|
export const batchDelete = (files: string[]) => {
|
||||||
for (const file of files) deleteFile(file)
|
for (const file of files) deleteFile(file)
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import { mdiCompass, mdiContacts } from '@mdi/js'
|
||||||
|
import Icon from '@mdi/react'
|
||||||
|
|
||||||
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
import { Metadata } from '@redwoodjs/web'
|
import { Metadata } from '@redwoodjs/web'
|
||||||
|
|
||||||
import TitlesCell from 'src/components/Title/TitlesCell'
|
import TitlesCell from 'src/components/Title/TitlesCell'
|
||||||
@ -7,11 +11,27 @@ const HomePage = () => {
|
|||||||
<>
|
<>
|
||||||
<Metadata title="Home" />
|
<Metadata title="Home" />
|
||||||
|
|
||||||
<div className="hero min-h-64">
|
<div className="hero min-h-[calc(100vh-6rem)]">
|
||||||
<div className="hero-content">
|
<div className="hero-content flex flex-col gap-8">
|
||||||
<div className="max-w-xl text-center">
|
<div className="text-center">
|
||||||
<TitlesCell className="text-primary" />
|
<TitlesCell className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Link
|
||||||
|
to={routes.projects()}
|
||||||
|
className="btn btn-primary btn-outline sm:btn-wide btn-lg"
|
||||||
|
>
|
||||||
|
<Icon path={mdiCompass} className="size-6" />
|
||||||
|
Explore
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={routes.contact()}
|
||||||
|
className="btn btn-primary btn-outline sm:btn-wide btn-lg"
|
||||||
|
>
|
||||||
|
<Icon path={mdiContacts} className="size-6" />
|
||||||
|
Contact
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
Reference in New Issue
Block a user