Switch to clsx for class concatenation
This commit is contained in:
parent
4614740da8
commit
91e1fb2166
@ -27,6 +27,7 @@
|
|||||||
"@vercel/speed-insights": "^1.0.2",
|
"@vercel/speed-insights": "^1.0.2",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
"camelcase-keys": "^9.1.2",
|
"camelcase-keys": "^9.1.2",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
"date-fns": "^3.0.6",
|
"date-fns": "^3.0.6",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-config-next": "14.0.4",
|
"eslint-config-next": "14.0.4",
|
||||||
|
|||||||
493
pnpm-lock.yaml
generated
493
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export default function AdminGrid ({
|
export default function AdminGrid ({
|
||||||
@ -15,7 +15,7 @@ export default function AdminGrid ({
|
|||||||
</div>}
|
</div>}
|
||||||
{/* py-[1px] fixes Safari vertical scroll bug */}
|
{/* py-[1px] fixes Safari vertical scroll bug */}
|
||||||
<div className="min-w-[14rem] overflow-x-scroll py-[1px]">
|
<div className="min-w-[14rem] overflow-x-scroll py-[1px]">
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'w-full',
|
'w-full',
|
||||||
'grid grid-cols-[auto_1fr_auto] ',
|
'grid grid-cols-[auto_1fr_auto] ',
|
||||||
'gap-2 sm:gap-3 items-center',
|
'gap-2 sm:gap-3 items-center',
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
checkPathPrefix,
|
checkPathPrefix,
|
||||||
isPathAdminConfiguration,
|
isPathAdminConfiguration,
|
||||||
} from '@/site/paths';
|
} from '@/site/paths';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { BiCog } from 'react-icons/bi';
|
import { BiCog } from 'react-icons/bi';
|
||||||
@ -25,11 +25,11 @@ export default function AdminNav({
|
|||||||
return (
|
return (
|
||||||
<SiteGrid
|
<SiteGrid
|
||||||
contentMain={
|
contentMain={
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex gap-2 md:gap-4',
|
'flex gap-2 md:gap-4',
|
||||||
'border-b border-gray-200 dark:border-gray-800 pb-3',
|
'border-b border-gray-200 dark:border-gray-800 pb-3',
|
||||||
)}>
|
)}>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex gap-2 md:gap-4',
|
'flex gap-2 md:gap-4',
|
||||||
'flex-grow overflow-x-scroll',
|
'flex-grow overflow-x-scroll',
|
||||||
)}>
|
)}>
|
||||||
@ -37,7 +37,7 @@ export default function AdminNav({
|
|||||||
<Link
|
<Link
|
||||||
key={label}
|
key={label}
|
||||||
href={href}
|
href={href}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'flex gap-0.5',
|
'flex gap-0.5',
|
||||||
checkPathPrefix(pathname, href) ? 'font-bold' : 'text-dim',
|
checkPathPrefix(pathname, href) ? 'font-bold' : 'text-dim',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { fileNameForBlobUrl } from '@/services/blob';
|
|||||||
import FormWithConfirm from '@/components/FormWithConfirm';
|
import FormWithConfirm from '@/components/FormWithConfirm';
|
||||||
import { deleteBlobPhotoAction } from '@/photo/actions';
|
import { deleteBlobPhotoAction } from '@/photo/actions';
|
||||||
import DeleteButton from './DeleteButton';
|
import DeleteButton from './DeleteButton';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { pathForAdminUploadUrl } from '@/site/paths';
|
import { pathForAdminUploadUrl } from '@/site/paths';
|
||||||
import AddButton from './AddButton';
|
import AddButton from './AddButton';
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export default function BlobUrls({
|
|||||||
alt={`Upload: ${uploadFileName}`}
|
alt={`Upload: ${uploadFileName}`}
|
||||||
src={url}
|
src={url}
|
||||||
aspectRatio={3.0 / 2.0}
|
aspectRatio={3.0 / 2.0}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'rounded-sm overflow-hidden',
|
'rounded-sm overflow-hidden',
|
||||||
'border border-gray-200 dark:border-gray-800',
|
'border border-gray-200 dark:border-gray-800',
|
||||||
)}
|
)}
|
||||||
@ -41,7 +41,7 @@ export default function BlobUrls({
|
|||||||
>
|
>
|
||||||
{uploadFileName}
|
{uploadFileName}
|
||||||
</Link>
|
</Link>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-nowrap',
|
'flex flex-nowrap',
|
||||||
'gap-2 sm:gap-3 items-center',
|
'gap-2 sm:gap-3 items-center',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { BiTrash } from 'react-icons/bi';
|
import { BiTrash } from 'react-icons/bi';
|
||||||
|
|
||||||
export default function DeleteButton () {
|
export default function DeleteButton () {
|
||||||
@ -7,7 +7,7 @@ export default function DeleteButton () {
|
|||||||
title="Delete"
|
title="Delete"
|
||||||
icon={<BiTrash size={16} className="translate-y-[-1.5px]" />}
|
icon={<BiTrash size={16} className="translate-y-[-1.5px]" />}
|
||||||
spinnerColor="text"
|
spinnerColor="text"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'text-red-500 dark:text-red-600',
|
'text-red-500 dark:text-red-600',
|
||||||
'active:!bg-red-100/50 active:dark:!bg-red-950/50',
|
'active:!bg-red-100/50 active:dark:!bg-red-950/50',
|
||||||
'!border-red-200 hover:!border-red-300',
|
'!border-red-200 hover:!border-red-300',
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Fragment } from 'react';
|
|||||||
import PhotoUpload from '@/photo/PhotoUpload';
|
import PhotoUpload from '@/photo/PhotoUpload';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import PhotoTiny from '@/photo/PhotoTiny';
|
import PhotoTiny from '@/photo/PhotoTiny';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import FormWithConfirm from '@/components/FormWithConfirm';
|
import FormWithConfirm from '@/components/FormWithConfirm';
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import { deletePhotoAction, syncPhotoExifDataAction } from '@/photo/actions';
|
import { deletePhotoAction, syncPhotoExifDataAction } from '@/photo/actions';
|
||||||
@ -56,7 +56,7 @@ export default async function AdminPhotosPage({
|
|||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<PhotoUpload shouldResize={!PRO_MODE_ENABLED} />
|
<PhotoUpload shouldResize={!PRO_MODE_ENABLED} />
|
||||||
{blobPhotoUrls.length > 0 &&
|
{blobPhotoUrls.length > 0 &&
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'border-b pb-6',
|
'border-b pb-6',
|
||||||
'border-gray-200 dark:border-gray-700',
|
'border-gray-200 dark:border-gray-700',
|
||||||
)}>
|
)}>
|
||||||
@ -70,7 +70,7 @@ export default async function AdminPhotosPage({
|
|||||||
{photos.map(photo =>
|
{photos.map(photo =>
|
||||||
<Fragment key={photo.id}>
|
<Fragment key={photo.id}>
|
||||||
<PhotoTiny
|
<PhotoTiny
|
||||||
className={cc(
|
className={clsx(
|
||||||
'rounded-sm overflow-hidden',
|
'rounded-sm overflow-hidden',
|
||||||
'border border-gray-200 dark:border-gray-800',
|
'border border-gray-200 dark:border-gray-800',
|
||||||
)}
|
)}
|
||||||
@ -82,7 +82,7 @@ export default async function AdminPhotosPage({
|
|||||||
href={pathForPhoto(photo)}
|
href={pathForPhoto(photo)}
|
||||||
className="lg:w-[50%] flex items-center gap-2"
|
className="lg:w-[50%] flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'inline-flex items-center gap-2',
|
'inline-flex items-center gap-2',
|
||||||
photo.hidden && 'text-dim',
|
photo.hidden && 'text-dim',
|
||||||
)}>
|
)}>
|
||||||
@ -94,7 +94,7 @@ export default async function AdminPhotosPage({
|
|||||||
/>}
|
/>}
|
||||||
</span>
|
</span>
|
||||||
{photo.priorityOrder !== null &&
|
{photo.priorityOrder !== null &&
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'text-xs leading-none px-1.5 py-1 rounded-sm',
|
'text-xs leading-none px-1.5 py-1 rounded-sm',
|
||||||
'dark:text-gray-300',
|
'dark:text-gray-300',
|
||||||
'bg-gray-100 dark:bg-gray-800',
|
'bg-gray-100 dark:bg-gray-800',
|
||||||
@ -102,14 +102,14 @@ export default async function AdminPhotosPage({
|
|||||||
{photo.priorityOrder}
|
{photo.priorityOrder}
|
||||||
</span>}
|
</span>}
|
||||||
</Link>
|
</Link>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'lg:w-[50%] uppercase',
|
'lg:w-[50%] uppercase',
|
||||||
'text-dim',
|
'text-dim',
|
||||||
)}>
|
)}>
|
||||||
{photo.takenAtNaive}
|
{photo.takenAtNaive}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-nowrap',
|
'flex flex-nowrap',
|
||||||
'gap-2 sm:gap-3 items-center',
|
'gap-2 sm:gap-3 items-center',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import PhotoTag from '@/tag/PhotoTag';
|
|||||||
import { formatTag } from '@/tag';
|
import { formatTag } from '@/tag';
|
||||||
import EditButton from '@/admin/EditButton';
|
import EditButton from '@/admin/EditButton';
|
||||||
import { pathForAdminTagEdit } from '@/site/paths';
|
import { pathForAdminTagEdit } from '@/site/paths';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default async function AdminTagsPage() {
|
export default async function AdminTagsPage() {
|
||||||
const tags = await getUniqueTagsHiddenCached();
|
const tags = await getUniqueTagsHiddenCached();
|
||||||
@ -29,7 +29,7 @@ export default async function AdminTagsPage() {
|
|||||||
<div className="text-dim uppercase">
|
<div className="text-dim uppercase">
|
||||||
{photoQuantityText(count, false)}
|
{photoQuantityText(count, false)}
|
||||||
</div>
|
</div>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-nowrap',
|
'flex flex-nowrap',
|
||||||
'gap-2 sm:gap-3 items-center',
|
'gap-2 sm:gap-3 items-center',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { FILM_SIMULATION_FORM_INPUT_OPTIONS } from '@/vendors/fujifilm';
|
import { FILM_SIMULATION_FORM_INPUT_OPTIONS } from '@/vendors/fujifilm';
|
||||||
import PhotoFilmSimulation from
|
import PhotoFilmSimulation from
|
||||||
'@/simulation/PhotoFilmSimulation';
|
'@/simulation/PhotoFilmSimulation';
|
||||||
@ -19,7 +19,7 @@ export default function FilmPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SiteGrid
|
<SiteGrid
|
||||||
contentMain={<div className={cc(
|
contentMain={<div className={clsx(
|
||||||
'flex items-center justify-center min-h-[30rem]',
|
'flex items-center justify-center min-h-[30rem]',
|
||||||
)}>
|
)}>
|
||||||
<div className="w-[250px] scale-[2.5]">
|
<div className="w-[250px] scale-[2.5]">
|
||||||
@ -38,7 +38,7 @@ export default function FilmPage() {
|
|||||||
<div>1/3200s</div>
|
<div>1/3200s</div>
|
||||||
<div>ISO 125</div>
|
<div>ISO 125</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'absolute top-0 left-[-2px] right-0 bottom-0',
|
'absolute top-0 left-[-2px] right-0 bottom-0',
|
||||||
'bg-gradient-to-t',
|
'bg-gradient-to-t',
|
||||||
'from-white to-[rgba(255,255,255,0.5)]',
|
'from-white to-[rgba(255,255,255,0.5)]',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Analytics } from '@vercel/analytics/react';
|
import { Analytics } from '@vercel/analytics/react';
|
||||||
import { SpeedInsights } from '@vercel/speed-insights/react';
|
import { SpeedInsights } from '@vercel/speed-insights/react';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { IBM_Plex_Mono } from 'next/font/google';
|
import { IBM_Plex_Mono } from 'next/font/google';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { BASE_URL, SITE_DESCRIPTION, SITE_TITLE } from '@/site/config';
|
import { BASE_URL, SITE_DESCRIPTION, SITE_TITLE } from '@/site/config';
|
||||||
@ -69,7 +69,7 @@ export default function RootLayout({
|
|||||||
>
|
>
|
||||||
<body className={ibmPlexMono.variable}>
|
<body className={ibmPlexMono.variable}>
|
||||||
<ThemeProviderClient>
|
<ThemeProviderClient>
|
||||||
<main className={cc(
|
<main className={clsx(
|
||||||
'px-3 pb-3',
|
'px-3 pb-3',
|
||||||
'lg:px-6 lg:pb-6',
|
'lg:px-6 lg:pb-6',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { auth } from '@/auth';
|
import { auth } from '@/auth';
|
||||||
import SignInForm from '@/auth/SignInForm';
|
import SignInForm from '@/auth/SignInForm';
|
||||||
import { PATH_ADMIN } from '@/site/paths';
|
import { PATH_ADMIN } from '@/site/paths';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
export default async function SignInPage() {
|
export default async function SignInPage() {
|
||||||
@ -12,7 +12,7 @@ export default async function SignInPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'fixed top-0 left-0 right-0 bottom-0',
|
'fixed top-0 left-0 right-0 bottom-0',
|
||||||
'flex items-center justify-center flex-col gap-8',
|
'flex items-center justify-center flex-col gap-8',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { pathForCamera } from '@/site/paths';
|
|||||||
import { IoMdCamera } from 'react-icons/io';
|
import { IoMdCamera } from 'react-icons/io';
|
||||||
import { Camera } from '.';
|
import { Camera } from '.';
|
||||||
import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink';
|
import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function PhotoCamera({
|
export default function PhotoCamera({
|
||||||
camera,
|
camera,
|
||||||
@ -30,7 +30,7 @@ export default function PhotoCamera({
|
|||||||
icon={showAppleIcon
|
icon={showAppleIcon
|
||||||
? <AiFillApple
|
? <AiFillApple
|
||||||
title="Apple"
|
title="Apple"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'text-icon',
|
'text-icon',
|
||||||
'translate-x-[-2.5px] translate-y-[2px]',
|
'translate-x-[-2.5px] translate-y-[2px]',
|
||||||
)}
|
)}
|
||||||
@ -38,7 +38,7 @@ export default function PhotoCamera({
|
|||||||
/>
|
/>
|
||||||
: <IoMdCamera
|
: <IoMdCamera
|
||||||
size={13}
|
size={13}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'text-icon',
|
'text-icon',
|
||||||
'translate-x-[-1px] translate-y-[3.5px]',
|
'translate-x-[-1px] translate-y-[3.5px]',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { ReactNode } from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { FiArrowLeft } from 'react-icons/fi';
|
import { FiArrowLeft } from 'react-icons/fi';
|
||||||
import SiteGrid from './SiteGrid';
|
import SiteGrid from './SiteGrid';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Badge from './Badge';
|
import Badge from './Badge';
|
||||||
|
|
||||||
function AdminChildPage({
|
function AdminChildPage({
|
||||||
@ -23,11 +23,11 @@ function AdminChildPage({
|
|||||||
contentMain={
|
contentMain={
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{(backPath || breadcrumb || accessory) &&
|
{(backPath || breadcrumb || accessory) &&
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-wrap items-center gap-x-2 gap-y-3',
|
'flex flex-wrap items-center gap-x-2 gap-y-3',
|
||||||
'min-h-[2.25rem]', // min-h-9 equivalent
|
'min-h-[2.25rem]', // min-h-9 equivalent
|
||||||
)}>
|
)}>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-wrap items-center gap-x-1.5 sm:gap-x-3 gap-y-1',
|
'flex flex-wrap items-center gap-x-1.5 sm:gap-x-3 gap-y-1',
|
||||||
'flex-grow',
|
'flex-grow',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function Badge({
|
export default function Badge({
|
||||||
children,
|
children,
|
||||||
@ -13,12 +13,12 @@ export default function Badge({
|
|||||||
}) {
|
}) {
|
||||||
const stylesForType = () => {
|
const stylesForType = () => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'primary': return cc(
|
case 'primary': return clsx(
|
||||||
'px-1.5 py-[0.3rem] rounded-md',
|
'px-1.5 py-[0.3rem] rounded-md',
|
||||||
'bg-gray-100/80 dark:bg-gray-900/80',
|
'bg-gray-100/80 dark:bg-gray-900/80',
|
||||||
'border border-gray-200/60 dark:border-gray-800/75'
|
'border border-gray-200/60 dark:border-gray-800/75'
|
||||||
);
|
);
|
||||||
case 'secondary': return cc(
|
case 'secondary': return clsx(
|
||||||
'px-[0.3rem] py-1 rounded-[0.25rem]',
|
'px-[0.3rem] py-1 rounded-[0.25rem]',
|
||||||
'bg-gray-300/30 dark:bg-gray-700/50',
|
'bg-gray-300/30 dark:bg-gray-700/50',
|
||||||
'text-medium',
|
'text-medium',
|
||||||
@ -29,7 +29,7 @@ export default function Badge({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'leading-none',
|
'leading-none',
|
||||||
stylesForType(),
|
stylesForType(),
|
||||||
uppercase && 'uppercase tracking-wider',
|
uppercase && 'uppercase tracking-wider',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function Checklist({
|
export default function Checklist({
|
||||||
title,
|
title,
|
||||||
@ -12,7 +12,7 @@ export default function Checklist({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex items-center gap-3',
|
'flex items-center gap-3',
|
||||||
'text-gray-600 dark:text-gray-300',
|
'text-gray-600 dark:text-gray-300',
|
||||||
'pl-[18px] mb-3',
|
'pl-[18px] mb-3',
|
||||||
@ -22,7 +22,7 @@ export default function Checklist({
|
|||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'bg-white dark:bg-black',
|
'bg-white dark:bg-black',
|
||||||
'dark:text-gray-400',
|
'dark:text-gray-400',
|
||||||
'border border-gray-200 dark:border-gray-800 rounded-md',
|
'border border-gray-200 dark:border-gray-800 rounded-md',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import StatusIcon from './StatusIcon';
|
import StatusIcon from './StatusIcon';
|
||||||
|
|
||||||
export default function ChecklistRow({
|
export default function ChecklistRow({
|
||||||
@ -16,7 +16,7 @@ export default function ChecklistRow({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex gap-2.5',
|
'flex gap-2.5',
|
||||||
'px-4 pt-2 pb-2.5',
|
'px-4 pt-2 pb-2.5',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import Badge from './Badge';
|
import Badge from './Badge';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export interface EntityLinkExternalProps {
|
export interface EntityLinkExternalProps {
|
||||||
type?: 'icon-last' | 'icon-first' | 'icon-only' | 'text-only'
|
type?: 'icon-last' | 'icon-first' | 'icon-only' | 'text-only'
|
||||||
@ -41,7 +41,7 @@ export default function EntityLink({
|
|||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
title={title}
|
title={title}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'inline-flex gap-[0.23rem]',
|
'inline-flex gap-[0.23rem]',
|
||||||
!badged && 'text-main hover:text-gray-900 dark:hover:text-gray-100',
|
!badged && 'text-main hover:text-gray-900 dark:hover:text-gray-100',
|
||||||
dim && 'text-dim',
|
dim && 'text-dim',
|
||||||
@ -59,7 +59,7 @@ export default function EntityLink({
|
|||||||
</span>}
|
</span>}
|
||||||
</>}
|
</>}
|
||||||
{icon && type !== 'text-only' &&
|
{icon && type !== 'text-only' &&
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'flex-shrink-0',
|
'flex-shrink-0',
|
||||||
'text-dim inline-flex min-w-[0.9rem]',
|
'text-dim inline-flex min-w-[0.9rem]',
|
||||||
type === 'icon-first' && 'order-first',
|
type === 'icon-first' && 'order-first',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { BiErrorAlt } from 'react-icons/bi';
|
import { BiErrorAlt } from 'react-icons/bi';
|
||||||
|
|
||||||
export default function ErrorNote({
|
export default function ErrorNote({
|
||||||
@ -7,7 +7,7 @@ export default function ErrorNote({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex items-center gap-3',
|
'flex items-center gap-3',
|
||||||
'px-3 py-2 border',
|
'px-3 py-2 border',
|
||||||
'text-red-600 dark:text-red-500/90',
|
'text-red-600 dark:text-red-500/90',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { LegacyRef } from 'react';
|
import { LegacyRef } from 'react';
|
||||||
import { useFormStatus } from 'react-dom';
|
import { useFormStatus } from 'react-dom';
|
||||||
import Spinner from './Spinner';
|
import Spinner from './Spinner';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function FieldSetWithStatus({
|
export default function FieldSetWithStatus({
|
||||||
id,
|
id,
|
||||||
@ -64,7 +64,7 @@ export default function FieldSetWithStatus({
|
|||||||
name={id}
|
name={id}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={e => onChange?.(e.target.value)}
|
onChange={e => onChange?.(e.target.value)}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'w-full',
|
'w-full',
|
||||||
// Use special class because `select` can't be readonly
|
// Use special class because `select` can't be readonly
|
||||||
readOnly || pending && 'disabled-select',
|
readOnly || pending && 'disabled-select',
|
||||||
@ -93,7 +93,7 @@ export default function FieldSetWithStatus({
|
|||||||
type={type}
|
type={type}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
readOnly={readOnly || pending}
|
readOnly={readOnly || pending}
|
||||||
className={cc(type === 'text' && 'w-full')}
|
className={clsx(type === 'text' && 'w-full')}
|
||||||
autoCapitalize={!capitalize ? 'off' : undefined}
|
autoCapitalize={!capitalize ? 'off' : undefined}
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import AnimateItems from './AnimateItems';
|
import AnimateItems from './AnimateItems';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export default function HeaderList({
|
|||||||
items={(title || icon
|
items={(title || icon
|
||||||
? [<div
|
? [<div
|
||||||
key="header"
|
key="header"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'text-gray-900',
|
'text-gray-900',
|
||||||
'dark:text-gray-100',
|
'dark:text-gray-100',
|
||||||
'flex items-center mb-0.5 gap-1',
|
'flex items-center mb-0.5 gap-1',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Spinner, { SpinnerColor } from './Spinner';
|
import Spinner, { SpinnerColor } from './Spinner';
|
||||||
|
|
||||||
export default function IconButton({
|
export default function IconButton({
|
||||||
@ -19,7 +19,7 @@ export default function IconButton({
|
|||||||
spinnerSize?: number
|
spinnerSize?: number
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
className,
|
className,
|
||||||
'relative inline-flex items-center',
|
'relative inline-flex items-center',
|
||||||
'w-[1rem] h-[1.1rem]',
|
'w-[1rem] h-[1.1rem]',
|
||||||
@ -27,7 +27,7 @@ export default function IconButton({
|
|||||||
{!isLoading
|
{!isLoading
|
||||||
? <button
|
? <button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'inline-flex items-center justify-center',
|
'inline-flex items-center justify-center',
|
||||||
'p-0 border-none shadow-none',
|
'p-0 border-none shadow-none',
|
||||||
'active:bg-transparent bg-transparent dark:bg-transparent',
|
'active:bg-transparent bg-transparent dark:bg-transparent',
|
||||||
@ -38,7 +38,7 @@ export default function IconButton({
|
|||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
: <span className={cc(
|
: <span className={clsx(
|
||||||
'inline-flex items-center justify-center',
|
'inline-flex items-center justify-center',
|
||||||
'h-full w-full',
|
'h-full w-full',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useEffect, useState, useTransition } from 'react';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { SpinnerColor } from './Spinner';
|
import { SpinnerColor } from './Spinner';
|
||||||
|
|
||||||
export default function IconPathButton({
|
export default function IconPathButton({
|
||||||
@ -57,7 +57,7 @@ export default function IconPathButton({
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
isLoading={shouldShowLoader}
|
isLoading={shouldShowLoader}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'translate-y-[-0.5px]',
|
'translate-y-[-0.5px]',
|
||||||
'active:translate-y-[1px]',
|
'active:translate-y-[1px]',
|
||||||
'text-medium',
|
'text-medium',
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { blobToImage } from '@/utility/blob';
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { CopyExif } from '@/lib/CopyExif';
|
import { CopyExif } from '@/lib/CopyExif';
|
||||||
import exifr from 'exifr';
|
import exifr from 'exifr';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Spinner from './Spinner';
|
import Spinner from './Spinner';
|
||||||
import { ACCEPTED_PHOTO_FILE_TYPES } from '@/photo';
|
import { ACCEPTED_PHOTO_FILE_TYPES } from '@/photo';
|
||||||
import { FiUploadCloud } from 'react-icons/fi';
|
import { FiUploadCloud } from 'react-icons/fi';
|
||||||
@ -47,13 +47,13 @@ export default function ImageInput({
|
|||||||
<div className="flex items-center gap-2 sm:gap-4">
|
<div className="flex items-center gap-2 sm:gap-4">
|
||||||
<label
|
<label
|
||||||
htmlFor={INPUT_ID}
|
htmlFor={INPUT_ID}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'shrink-0 select-none text-main',
|
'shrink-0 select-none text-main',
|
||||||
loading && 'pointer-events-none cursor-not-allowed',
|
loading && 'pointer-events-none cursor-not-allowed',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={cc(
|
className={clsx(
|
||||||
'button primary normal-case',
|
'button primary normal-case',
|
||||||
loading && 'disabled'
|
loading && 'disabled'
|
||||||
)}
|
)}
|
||||||
@ -212,7 +212,7 @@ export default function ImageInput({
|
|||||||
</div>
|
</div>
|
||||||
<canvas
|
<canvas
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'bg-gray-50 dark:bg-gray-900/50 rounded-md',
|
'bg-gray-50 dark:bg-gray-900/50 rounded-md',
|
||||||
'border border-gray-200 dark:border-gray-800',
|
'border border-gray-200 dark:border-gray-800',
|
||||||
'w-[400px]',
|
'w-[400px]',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export default function InfoBlock({
|
export default function InfoBlock({
|
||||||
@ -21,7 +21,7 @@ export default function InfoBlock({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-col items-center justify-center',
|
'flex flex-col items-center justify-center',
|
||||||
'rounded-lg border',
|
'rounded-lg border',
|
||||||
'bg-gray-50 border-gray-200',
|
'bg-gray-50 border-gray-200',
|
||||||
@ -29,7 +29,7 @@ export default function InfoBlock({
|
|||||||
getPaddingClasses(),
|
getPaddingClasses(),
|
||||||
className,
|
className,
|
||||||
)}>
|
)}>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex flex-col justify-center w-full',
|
'flex flex-col justify-center w-full',
|
||||||
centered && 'items-center',
|
centered && 'items-center',
|
||||||
'space-y-4',
|
'space-y-4',
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import useClickInsideOutside from '@/utility/useClickInsideOutside';
|
import useClickInsideOutside from '@/utility/useClickInsideOutside';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import AnimateItems from './AnimateItems';
|
import AnimateItems from './AnimateItems';
|
||||||
@ -40,7 +40,7 @@ export default function Modal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className={cc(
|
className={clsx(
|
||||||
'fixed inset-0 z-50 flex items-center justify-center',
|
'fixed inset-0 z-50 flex items-center justify-center',
|
||||||
'bg-black',
|
'bg-black',
|
||||||
)}
|
)}
|
||||||
@ -54,7 +54,7 @@ export default function Modal({
|
|||||||
duration={0.3}
|
duration={0.3}
|
||||||
items={[<div
|
items={[<div
|
||||||
key="modalContent"
|
key="modalContent"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'p-3 rounded-lg',
|
'p-3 rounded-lg',
|
||||||
'bg-white dark:bg-black',
|
'bg-white dark:bg-black',
|
||||||
'dark:border dark:border-gray-800',
|
'dark:border dark:border-gray-800',
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { BiError } from 'react-icons/bi';
|
import { BiError } from 'react-icons/bi';
|
||||||
import Spinner from '@/components/Spinner';
|
import Spinner from '@/components/Spinner';
|
||||||
@ -49,7 +49,7 @@ export default function OGTile({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={path}
|
href={path}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'group',
|
'group',
|
||||||
'block w-full rounded-md overflow-hidden',
|
'block w-full rounded-md overflow-hidden',
|
||||||
'border shadow-sm',
|
'border shadow-sm',
|
||||||
@ -62,14 +62,14 @@ export default function OGTile({
|
|||||||
style={{ aspectRatio }}
|
style={{ aspectRatio }}
|
||||||
>
|
>
|
||||||
{loadingState === 'loading' &&
|
{loadingState === 'loading' &&
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'absolute top-0 left-0 right-0 bottom-0 z-10',
|
'absolute top-0 left-0 right-0 bottom-0 z-10',
|
||||||
'flex items-center justify-center',
|
'flex items-center justify-center',
|
||||||
)}>
|
)}>
|
||||||
<Spinner size={40} />
|
<Spinner size={40} />
|
||||||
</div>}
|
</div>}
|
||||||
{loadingState === 'failed' &&
|
{loadingState === 'failed' &&
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'absolute top-0 left-0 right-0 bottom-0 z-[11]',
|
'absolute top-0 left-0 right-0 bottom-0 z-[11]',
|
||||||
'flex items-center justify-center',
|
'flex items-center justify-center',
|
||||||
'text-red-400',
|
'text-red-400',
|
||||||
@ -79,7 +79,7 @@ export default function OGTile({
|
|||||||
{(loadingState === 'loading' || loadingState === 'loaded') &&
|
{(loadingState === 'loading' || loadingState === 'loaded') &&
|
||||||
<img
|
<img
|
||||||
alt={title}
|
alt={title}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'absolute top-0 left-0 right-0 bottom-0 z-0',
|
'absolute top-0 left-0 right-0 bottom-0 z-0',
|
||||||
'w-full',
|
'w-full',
|
||||||
loadingState === 'loading' && 'opacity-0',
|
loadingState === 'loading' && 'opacity-0',
|
||||||
@ -109,7 +109,7 @@ export default function OGTile({
|
|||||||
}}
|
}}
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'md:text-lg',
|
'md:text-lg',
|
||||||
'flex flex-col gap-1 p-3',
|
'flex flex-col gap-1 p-3',
|
||||||
'font-sans leading-none',
|
'font-sans leading-none',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { BiLogoGithub } from 'react-icons/bi';
|
import { BiLogoGithub } from 'react-icons/bi';
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export default function RepoLink() {
|
|||||||
<Link
|
<Link
|
||||||
href="http://github.com/sambecker/exif-photo-blog"
|
href="http://github.com/sambecker/exif-photo-blog"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'flex items-center gap-1',
|
'flex items-center gap-1',
|
||||||
'text-black dark:text-white',
|
'text-black dark:text-white',
|
||||||
'hover:underline',
|
'hover:underline',
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import { TbPhotoShare } from 'react-icons/tb';
|
import { TbPhotoShare } from 'react-icons/tb';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { BiCopy } from 'react-icons/bi';
|
import { BiCopy } from 'react-icons/bi';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { shortenUrl } from '@/utility/url';
|
import { shortenUrl } from '@/utility/url';
|
||||||
@ -22,7 +22,7 @@ export default function ShareModal({
|
|||||||
return (
|
return (
|
||||||
<Modal onClosePath={pathClose}>
|
<Modal onClosePath={pathClose}>
|
||||||
<div className="space-y-3 md:space-y-4 w-full">
|
<div className="space-y-3 md:space-y-4 w-full">
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex items-center gap-x-3',
|
'flex items-center gap-x-3',
|
||||||
'text-xl md:text-3xl leading-snug',
|
'text-xl md:text-3xl leading-snug',
|
||||||
)}>
|
)}>
|
||||||
@ -32,7 +32,7 @@ export default function ShareModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'rounded-md',
|
'rounded-md',
|
||||||
'w-full overflow-hidden',
|
'w-full overflow-hidden',
|
||||||
'flex items-center justify-stretch',
|
'flex items-center justify-stretch',
|
||||||
@ -42,7 +42,7 @@ export default function ShareModal({
|
|||||||
{shortenUrl(pathShare)}
|
{shortenUrl(pathShare)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cc(
|
className={clsx(
|
||||||
'p-3 border-l',
|
'p-3 border-l',
|
||||||
'border-gray-200 bg-gray-100 active:bg-gray-200',
|
'border-gray-200 bg-gray-100 active:bg-gray-200',
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function SiteGrid({
|
export default function SiteGrid({
|
||||||
className,
|
className,
|
||||||
@ -14,7 +14,7 @@ export default function SiteGrid({
|
|||||||
sideHiddenOnMobile?: boolean
|
sideHiddenOnMobile?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
className,
|
className,
|
||||||
'grid',
|
'grid',
|
||||||
'grid-cols-1 md:grid-cols-12',
|
'grid-cols-1 md:grid-cols-12',
|
||||||
@ -22,14 +22,14 @@ export default function SiteGrid({
|
|||||||
'gap-y-4',
|
'gap-y-4',
|
||||||
'max-w-7xl',
|
'max-w-7xl',
|
||||||
)}>
|
)}>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'col-span-1 md:col-span-9',
|
'col-span-1 md:col-span-9',
|
||||||
sideFirstOnMobile && 'order-2 md:order-none',
|
sideFirstOnMobile && 'order-2 md:order-none',
|
||||||
)}>
|
)}>
|
||||||
{contentMain}
|
{contentMain}
|
||||||
</div>
|
</div>
|
||||||
{contentSide &&
|
{contentSide &&
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'col-span-1 md:col-span-3',
|
'col-span-1 md:col-span-3',
|
||||||
sideFirstOnMobile && 'order-1 md:order-none',
|
sideFirstOnMobile && 'order-1 md:order-none',
|
||||||
sideHiddenOnMobile && 'hidden md:block',
|
sideHiddenOnMobile && 'hidden md:block',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
const SIZE_DEFAULT = 12;
|
const SIZE_DEFAULT = 12;
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ export default function Spinner({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cc(
|
className={clsx(
|
||||||
className,
|
className,
|
||||||
color === 'light-gray' &&
|
color === 'light-gray' &&
|
||||||
'text-gray-300 dark:text-gray-600',
|
'text-gray-300 dark:text-gray-600',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { HTMLProps, useEffect, useRef } from 'react';
|
import { HTMLProps, useEffect, useRef } from 'react';
|
||||||
import { useFormStatus } from 'react-dom';
|
import { useFormStatus } from 'react-dom';
|
||||||
import Spinner, { SpinnerColor } from './Spinner';
|
import Spinner, { SpinnerColor } from './Spinner';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { toastSuccess } from '@/toast';
|
import { toastSuccess } from '@/toast';
|
||||||
|
|
||||||
interface Props extends HTMLProps<HTMLButtonElement> {
|
interface Props extends HTMLProps<HTMLButtonElement> {
|
||||||
@ -43,7 +43,7 @@ export default function SubmitButtonWithStatus({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cc(
|
className={clsx(
|
||||||
className,
|
className,
|
||||||
'inline-flex items-center gap-2',
|
'inline-flex items-center gap-2',
|
||||||
styleAsLink && 'link',
|
styleAsLink && 'link',
|
||||||
@ -51,7 +51,7 @@ export default function SubmitButtonWithStatus({
|
|||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
>
|
>
|
||||||
{(icon || pending) &&
|
{(icon || pending) &&
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'h-4',
|
'h-4',
|
||||||
'min-w-[1rem]',
|
'min-w-[1rem]',
|
||||||
'inline-flex justify-center sm:justify-normal',
|
'inline-flex justify-center sm:justify-normal',
|
||||||
@ -62,7 +62,7 @@ export default function SubmitButtonWithStatus({
|
|||||||
? <Spinner size={14} color={spinnerColor} />
|
? <Spinner size={14} color={spinnerColor} />
|
||||||
: icon}
|
: icon}
|
||||||
</span>}
|
</span>}
|
||||||
{children && <span className={cc(
|
{children && <span className={clsx(
|
||||||
icon !== undefined && 'hidden sm:inline-block',
|
icon !== undefined && 'hidden sm:inline-block',
|
||||||
)}>
|
)}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function Switcher({
|
export default function Switcher({
|
||||||
children,
|
children,
|
||||||
@ -7,7 +7,7 @@ export default function Switcher({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex divide-x',
|
'flex divide-x',
|
||||||
'divide-gray-300 dark:divide-gray-800',
|
'divide-gray-300 dark:divide-gray-800',
|
||||||
'border rounded-[0.25rem]',
|
'border rounded-[0.25rem]',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function SwitcherItem({
|
export default function SwitcherItem({
|
||||||
icon,
|
icon,
|
||||||
@ -16,7 +16,7 @@ export default function SwitcherItem({
|
|||||||
active?: boolean
|
active?: boolean
|
||||||
noPadding?: boolean
|
noPadding?: boolean
|
||||||
}) {
|
}) {
|
||||||
const className = cc(
|
const className = clsx(
|
||||||
classNameProp,
|
classNameProp,
|
||||||
'py-0.5 px-1.5',
|
'py-0.5 px-1.5',
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Photo, PhotoDateRange } from '.';
|
|||||||
import PhotoLarge from './PhotoLarge';
|
import PhotoLarge from './PhotoLarge';
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import PhotoGrid from './PhotoGrid';
|
import PhotoGrid from './PhotoGrid';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import PhotoLinks from './PhotoLinks';
|
import PhotoLinks from './PhotoLinks';
|
||||||
import TagHeader from '@/tag/TagHeader';
|
import TagHeader from '@/tag/TagHeader';
|
||||||
import { Camera } from '@/camera';
|
import { Camera } from '@/camera';
|
||||||
@ -95,7 +95,7 @@ export default function PhotoDetailPage({
|
|||||||
tag={tag}
|
tag={tag}
|
||||||
animateOnFirstLoadOnly
|
animateOnFirstLoadOnly
|
||||||
/>}
|
/>}
|
||||||
contentSide={<div className={cc(
|
contentSide={<div className={clsx(
|
||||||
'grid grid-cols-2',
|
'grid grid-cols-2',
|
||||||
'gap-0.5 sm:gap-1',
|
'gap-0.5 sm:gap-1',
|
||||||
'md:flex md:gap-4',
|
'md:flex md:gap-4',
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import NextImage from 'next/image';
|
|||||||
import { createPhotoAction, updatePhotoAction } from './actions';
|
import { createPhotoAction, updatePhotoAction } from './actions';
|
||||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import CanvasBlurCapture from '@/components/CanvasBlurCapture';
|
import CanvasBlurCapture from '@/components/CanvasBlurCapture';
|
||||||
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/site/paths';
|
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/site/paths';
|
||||||
import {
|
import {
|
||||||
@ -106,7 +106,7 @@ export default function PhotoForm({
|
|||||||
<NextImage
|
<NextImage
|
||||||
alt="Upload"
|
alt="Upload"
|
||||||
src={url}
|
src={url}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'border rounded-md overflow-hidden',
|
'border rounded-md overflow-hidden',
|
||||||
'border-gray-200 dark:border-gray-700'
|
'border-gray-200 dark:border-gray-700'
|
||||||
)}
|
)}
|
||||||
@ -124,7 +124,7 @@ export default function PhotoForm({
|
|||||||
<img
|
<img
|
||||||
alt="blur"
|
alt="blur"
|
||||||
src={formData.blurData}
|
src={formData.blurData}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'border rounded-md overflow-hidden',
|
'border rounded-md overflow-hidden',
|
||||||
'border-gray-200 dark:border-gray-700'
|
'border-gray-200 dark:border-gray-700'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Photo } from '.';
|
import { Photo } from '.';
|
||||||
import PhotoSmall from './PhotoSmall';
|
import PhotoSmall from './PhotoSmall';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import AnimateItems from '@/components/AnimateItems';
|
import AnimateItems from '@/components/AnimateItems';
|
||||||
import { Camera } from '@/camera';
|
import { Camera } from '@/camera';
|
||||||
import MorePhotos from '@/photo/MorePhotos';
|
import MorePhotos from '@/photo/MorePhotos';
|
||||||
@ -37,7 +37,7 @@ export default function PhotoGrid({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<AnimateItems
|
<AnimateItems
|
||||||
className={cc(
|
className={clsx(
|
||||||
'grid gap-0.5 sm:gap-1',
|
'grid gap-0.5 sm:gap-1',
|
||||||
small
|
small
|
||||||
? 'grid-cols-3 xs:grid-cols-6'
|
? 'grid-cols-3 xs:grid-cols-6'
|
||||||
@ -56,7 +56,7 @@ export default function PhotoGrid({
|
|||||||
<div
|
<div
|
||||||
key={photo.id}
|
key={photo.id}
|
||||||
className={GRID_ASPECT_RATIO !== 0
|
className={GRID_ASPECT_RATIO !== 0
|
||||||
? cc(
|
? clsx(
|
||||||
'aspect-square',
|
'aspect-square',
|
||||||
'overflow-hidden',
|
'overflow-hidden',
|
||||||
'[&>*]:flex [&>*]:w-full [&>*]:h-full',
|
'[&>*]:flex [&>*]:w-full [&>*]:h-full',
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Photo, photoHasCameraData, photoHasExifData, titleForPhoto } from '.';
|
import { Photo, photoHasCameraData, photoHasExifData, titleForPhoto } from '.';
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import ImageLarge from '@/components/ImageLarge';
|
import ImageLarge from '@/components/ImageLarge';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { pathForPhoto, pathForPhotoShare } from '@/site/paths';
|
import { pathForPhoto, pathForPhotoShare } from '@/site/paths';
|
||||||
import PhotoTags from '@/tag/PhotoTags';
|
import PhotoTags from '@/tag/PhotoTags';
|
||||||
@ -38,7 +38,7 @@ export default function PhotoLarge({
|
|||||||
const camera = cameraFromPhoto(photo);
|
const camera = cameraFromPhoto(photo);
|
||||||
|
|
||||||
const renderMiniGrid = (children: JSX.Element, rightPadding = true) =>
|
const renderMiniGrid = (children: JSX.Element, rightPadding = true) =>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex gap-y-4',
|
'flex gap-y-4',
|
||||||
'flex-col sm:flex-row md:flex-col',
|
'flex-col sm:flex-row md:flex-col',
|
||||||
'[&>*]:sm:flex-grow',
|
'[&>*]:sm:flex-grow',
|
||||||
@ -60,7 +60,7 @@ export default function PhotoLarge({
|
|||||||
priority={priority}
|
priority={priority}
|
||||||
/>}
|
/>}
|
||||||
contentSide={
|
contentSide={
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'leading-snug',
|
'leading-snug',
|
||||||
'sticky top-4 self-start',
|
'sticky top-4 self-start',
|
||||||
'grid grid-cols-2 md:grid-cols-1',
|
'grid grid-cols-2 md:grid-cols-1',
|
||||||
@ -115,11 +115,11 @@ export default function PhotoLarge({
|
|||||||
<li>{photo.isoFormatted}</li>
|
<li>{photo.isoFormatted}</li>
|
||||||
<li>{photo.exposureCompensationFormatted ?? '—'}</li>
|
<li>{photo.exposureCompensationFormatted ?? '—'}</li>
|
||||||
</ul>}
|
</ul>}
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'flex gap-y-4',
|
'flex gap-y-4',
|
||||||
'flex-col sm:flex-row md:flex-col',
|
'flex-col sm:flex-row md:flex-col',
|
||||||
)}>
|
)}>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'grow uppercase',
|
'grow uppercase',
|
||||||
'text-medium',
|
'text-medium',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { Photo } from '.';
|
import { Photo } from '.';
|
||||||
import PhotoGrid from './PhotoGrid';
|
import PhotoGrid from './PhotoGrid';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -23,7 +23,7 @@ export default function PhotoLightbox({
|
|||||||
const showOverageTile = countNotShown > 0;
|
const showOverageTile = countNotShown > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'border dark:border-gray-800 p-1.5 lg:p-2 rounded-md',
|
'border dark:border-gray-800 p-1.5 lg:p-2 rounded-md',
|
||||||
'bg-gray-50 dark:bg-gray-950',
|
'bg-gray-50 dark:bg-gray-950',
|
||||||
)}>
|
)}>
|
||||||
@ -33,7 +33,7 @@ export default function PhotoLightbox({
|
|||||||
additionalTile={showOverageTile
|
additionalTile={showOverageTile
|
||||||
? <Link
|
? <Link
|
||||||
href={moreLink}
|
href={moreLink}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'flex flex-col items-center justify-center',
|
'flex flex-col items-center justify-center',
|
||||||
'gap-0.5 lg:gap-1',
|
'gap-0.5 lg:gap-1',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { Photo, PhotoDateRange, dateRangeForPhotos } from '.';
|
import { Photo, PhotoDateRange, dateRangeForPhotos } from '.';
|
||||||
import ShareButton from '@/components/ShareButton';
|
import ShareButton from '@/components/ShareButton';
|
||||||
import AnimateItems from '@/components/AnimateItems';
|
import AnimateItems from '@/components/AnimateItems';
|
||||||
@ -37,19 +37,19 @@ export default function PhotoSetHeader({
|
|||||||
animateOnFirstLoadOnly
|
animateOnFirstLoadOnly
|
||||||
items={[<div
|
items={[<div
|
||||||
key="PhotosHeader"
|
key="PhotosHeader"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'grid gap-0.5 sm:gap-1 items-start',
|
'grid gap-0.5 sm:gap-1 items-start',
|
||||||
HIGH_DENSITY_GRID
|
HIGH_DENSITY_GRID
|
||||||
? 'xs:grid-cols-2 sm:grid-cols-4 lg:grid-cols-5'
|
? 'xs:grid-cols-2 sm:grid-cols-4 lg:grid-cols-5'
|
||||||
: 'xs:grid-cols-2 sm:grid-cols-4 md:grid-cols-3 lg:grid-cols-4',
|
: 'xs:grid-cols-2 sm:grid-cols-4 md:grid-cols-3 lg:grid-cols-4',
|
||||||
)}>
|
)}>
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'inline-flex',
|
'inline-flex',
|
||||||
HIGH_DENSITY_GRID && 'sm:col-span-2',
|
HIGH_DENSITY_GRID && 'sm:col-span-2',
|
||||||
)}>
|
)}>
|
||||||
{entity}
|
{entity}
|
||||||
</span>
|
</span>
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'inline-flex gap-2 items-center self-start',
|
'inline-flex gap-2 items-center self-start',
|
||||||
'uppercase text-dim',
|
'uppercase text-dim',
|
||||||
HIGH_DENSITY_GRID
|
HIGH_DENSITY_GRID
|
||||||
@ -63,7 +63,7 @@ export default function PhotoSetHeader({
|
|||||||
{selectedPhotoIndex === undefined &&
|
{selectedPhotoIndex === undefined &&
|
||||||
<ShareButton path={sharePath} dim />}
|
<ShareButton path={sharePath} dim />}
|
||||||
</span>
|
</span>
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'hidden sm:inline-block',
|
'hidden sm:inline-block',
|
||||||
'text-right uppercase',
|
'text-right uppercase',
|
||||||
'text-dim',
|
'text-dim',
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Photo, titleForPhoto } from '.';
|
import { Photo, titleForPhoto } from '.';
|
||||||
import ImageSmall from '@/components/ImageSmall';
|
import ImageSmall from '@/components/ImageSmall';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { pathForPhoto } from '@/site/paths';
|
import { pathForPhoto } from '@/site/paths';
|
||||||
import { Camera } from '@/camera';
|
import { Camera } from '@/camera';
|
||||||
import { FilmSimulation } from '@/simulation';
|
import { FilmSimulation } from '@/simulation';
|
||||||
@ -22,7 +22,7 @@ export default function PhotoSmall({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={pathForPhoto(photo, tag, camera, simulation)}
|
href={pathForPhoto(photo, tag, camera, simulation)}
|
||||||
className={cc(
|
className={clsx(
|
||||||
'active:brightness-75',
|
'active:brightness-75',
|
||||||
selected && 'brightness-50',
|
selected && 'brightness-50',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Photo, titleForPhoto } from '.';
|
import { Photo, titleForPhoto } from '.';
|
||||||
import ImageTiny from '@/components/ImageTiny';
|
import ImageTiny from '@/components/ImageTiny';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { pathForPhoto } from '@/site/paths';
|
import { pathForPhoto } from '@/site/paths';
|
||||||
|
|
||||||
export default function PhotoTiny({
|
export default function PhotoTiny({
|
||||||
@ -18,7 +18,7 @@ export default function PhotoTiny({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={pathForPhoto(photo, tag)}
|
href={pathForPhoto(photo, tag)}
|
||||||
className={cc(
|
className={clsx(
|
||||||
className,
|
className,
|
||||||
'active:brightness-75',
|
'active:brightness-75',
|
||||||
selected && 'brightness-50',
|
selected && 'brightness-50',
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { PATH_ADMIN_UPLOADS, pathForAdminUploadUrl } from '@/site/paths';
|
import { PATH_ADMIN_UPLOADS, pathForAdminUploadUrl } from '@/site/paths';
|
||||||
import ImageInput from '../components/ImageInput';
|
import ImageInput from '../components/ImageInput';
|
||||||
import { MAX_IMAGE_SIZE } from '@/services/next-image';
|
import { MAX_IMAGE_SIZE } from '@/services/next-image';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
|
|
||||||
export default function PhotoUpload({
|
export default function PhotoUpload({
|
||||||
shouldResize,
|
shouldResize,
|
||||||
@ -25,7 +25,7 @@ export default function PhotoUpload({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'space-y-4',
|
'space-y-4',
|
||||||
isUploading && 'cursor-not-allowed',
|
isUploading && 'cursor-not-allowed',
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import InfoBlock from '@/components/InfoBlock';
|
|||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import { IS_SITE_READY } from '@/site/config';
|
import { IS_SITE_READY } from '@/site/config';
|
||||||
import SiteChecklist from '@/site/SiteChecklist';
|
import SiteChecklist from '@/site/SiteChecklist';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { HiOutlinePhotograph } from 'react-icons/hi';
|
import { HiOutlinePhotograph } from 'react-icons/hi';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ export default function PhotosEmptyState() {
|
|||||||
className="text-medium"
|
className="text-medium"
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
<div className={cc(
|
<div className={clsx(
|
||||||
'font-bold text-2xl',
|
'font-bold text-2xl',
|
||||||
'text-gray-700 dark:text-gray-200',
|
'text-gray-700 dark:text-gray-200',
|
||||||
)}>
|
)}>
|
||||||
@ -40,7 +40,7 @@ export default function PhotosEmptyState() {
|
|||||||
2. Change the name of this blog and other configuration
|
2. Change the name of this blog and other configuration
|
||||||
by editing environment variables referenced in
|
by editing environment variables referenced in
|
||||||
{' '}
|
{' '}
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'px-1.5',
|
'px-1.5',
|
||||||
'bg-gray-100',
|
'bg-gray-100',
|
||||||
'border border-gray-200 dark:border-gray-700',
|
'border border-gray-200 dark:border-gray-700',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import ThemeSwitcher from '@/site/ThemeSwitcher';
|
import ThemeSwitcher from '@/site/ThemeSwitcher';
|
||||||
@ -10,7 +10,7 @@ import { isPathSignIn } from '@/site/paths';
|
|||||||
import { signOutAction } from '@/auth/action';
|
import { signOutAction } from '@/auth/action';
|
||||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||||
|
|
||||||
const LINK_STYLE = cc(
|
const LINK_STYLE = clsx(
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
'hover:text-gray-300',
|
'hover:text-gray-300',
|
||||||
'hover:dark:text-gray-600',
|
'hover:dark:text-gray-600',
|
||||||
@ -23,7 +23,7 @@ export default function FooterAuth() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SiteGrid
|
<SiteGrid
|
||||||
contentMain={<div className={cc(
|
contentMain={<div className={clsx(
|
||||||
'flex items-center',
|
'flex items-center',
|
||||||
'my-8',
|
'my-8',
|
||||||
'text-dim',
|
'text-dim',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import SiteGrid from '../components/SiteGrid';
|
import SiteGrid from '../components/SiteGrid';
|
||||||
import ThemeSwitcher from '@/site/ThemeSwitcher';
|
import ThemeSwitcher from '@/site/ThemeSwitcher';
|
||||||
import { signOut } from 'next-auth/react';
|
import { signOut } from 'next-auth/react';
|
||||||
@ -15,7 +15,7 @@ export default function FooterStatic({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SiteGrid
|
<SiteGrid
|
||||||
contentMain={<div className={cc(
|
contentMain={<div className={clsx(
|
||||||
'my-8',
|
'my-8',
|
||||||
'flex items-center',
|
'flex items-center',
|
||||||
'text-dim',
|
'text-dim',
|
||||||
@ -31,7 +31,7 @@ export default function FooterStatic({
|
|||||||
<RepoLink />}
|
<RepoLink />}
|
||||||
{showSignOut &&
|
{showSignOut &&
|
||||||
<div
|
<div
|
||||||
className={cc(
|
className={clsx(
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
'hover:text-gray-600 dark:hover:text-gray-400',
|
'hover:text-gray-600 dark:hover:text-gray-400',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import SiteGrid from '../components/SiteGrid';
|
import SiteGrid from '../components/SiteGrid';
|
||||||
@ -55,7 +55,7 @@ export default function Nav({ showTextLinks }: { showTextLinks?: boolean }) {
|
|||||||
items={showNav
|
items={showNav
|
||||||
? [<div
|
? [<div
|
||||||
key="nav"
|
key="nav"
|
||||||
className={cc(
|
className={clsx(
|
||||||
'flex items-center',
|
'flex items-center',
|
||||||
'w-full min-h-[4rem]',
|
'w-full min-h-[4rem]',
|
||||||
'leading-none',
|
'leading-none',
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { ComponentProps, ReactNode, useTransition } from 'react';
|
import { ComponentProps, ReactNode, useTransition } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import ChecklistRow from '../components/ChecklistRow';
|
import ChecklistRow from '../components/ChecklistRow';
|
||||||
import { FiExternalLink } from 'react-icons/fi';
|
import { FiExternalLink } from 'react-icons/fi';
|
||||||
import {
|
import {
|
||||||
@ -60,7 +60,7 @@ export default function SiteChecklistClient({
|
|||||||
<a {...{
|
<a {...{
|
||||||
href,
|
href,
|
||||||
...external && { target: '_blank', rel: 'noopener noreferrer' },
|
...external && { target: '_blank', rel: 'noopener noreferrer' },
|
||||||
className: cc(
|
className: clsx(
|
||||||
'underline hover:no-underline',
|
'underline hover:no-underline',
|
||||||
),
|
),
|
||||||
}}>
|
}}>
|
||||||
@ -79,7 +79,7 @@ export default function SiteChecklistClient({
|
|||||||
const renderCopyButton = (label: string, text: string, subtle?: boolean) =>
|
const renderCopyButton = (label: string, text: string, subtle?: boolean) =>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<BiCopy size={15} />}
|
icon={<BiCopy size={15} />}
|
||||||
className={cc(subtle && 'text-gray-300 dark:text-gray-700')}
|
className={clsx(subtle && 'text-gray-300 dark:text-gray-700')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
toastSuccess(`${label} copied to clipboard`);
|
toastSuccess(`${label} copied to clipboard`);
|
||||||
@ -92,7 +92,7 @@ export default function SiteChecklistClient({
|
|||||||
className="overflow-x-scroll overflow-y-hidden"
|
className="overflow-x-scroll overflow-y-hidden"
|
||||||
>
|
>
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
<span className={cc(
|
<span className={clsx(
|
||||||
'text-medium',
|
'text-medium',
|
||||||
'rounded-sm',
|
'rounded-sm',
|
||||||
'bg-gray-100 dark:bg-gray-800',
|
'bg-gray-100 dark:bg-gray-800',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cc } from '@/utility/css';
|
import { clsx } from 'clsx';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export default function ToasterWithThemes() {
|
|||||||
theme={theme as 'system' | 'light' | 'dark'}
|
theme={theme as 'system' | 'light' | 'dark'}
|
||||||
toastOptions={{
|
toastOptions={{
|
||||||
classNames: {
|
classNames: {
|
||||||
toast: cc(
|
toast: clsx(
|
||||||
'font-mono font-normal',
|
'font-mono font-normal',
|
||||||
'!border-gray-200 dark:!border-gray-800',
|
'!border-gray-200 dark:!border-gray-800',
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
export const cc = (
|
|
||||||
...classes: (string | boolean | undefined)[]
|
|
||||||
): string =>
|
|
||||||
classes
|
|
||||||
.filter(s => typeof s === 'string' && s.length)
|
|
||||||
.join(' ');
|
|
||||||
Loading…
Reference in New Issue
Block a user