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