diff --git a/src/app/admin/baseline/page.tsx b/src/app/admin/baseline/page.tsx new file mode 100644 index 00000000..0dbec5f6 --- /dev/null +++ b/src/app/admin/baseline/page.tsx @@ -0,0 +1,193 @@ +'use client'; + +import FieldSetWithStatus from '@/components/FieldSetWithStatus'; +import EntityLink from '@/components/primitives/EntityLink'; +import LabeledIcon from '@/components/primitives/LabeledIcon'; +import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; +import { clsx } from 'clsx/lite'; +import { useState } from 'react'; +import { FaCamera, FaHandSparkles, FaUserAltSlash } from 'react-icons/fa'; +import { IoMdCamera } from 'react-icons/io'; +import { IoImageSharp } from 'react-icons/io5'; + +const DEBUG_LINES = new Array(22).fill(null); + +export default function ComponentsPage() { + const [_debugGrid, setDebugGrid] = useState('true'); + const [_debugComponents, setDebugComponents] = useState('false'); + + const debugGrid = _debugGrid === 'true'; + const debugComponents = _debugComponents === 'true'; + + return ( + <> +

+
+ Baseline Grid: + + 13px / 18px + 14px / 20px + +
+
*]:inline-flex [&>*]:gap-1 [&_input]:-translate-y-0.5', + )}> + + +
+

+
+
+
+ } + debug={debugComponents} + > + Camera
Line two +
+
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } + label="Image" + debug={debugComponents} + /> +
+
+ } + label="Image" + badged + debug={debugComponents} + /> +
+
+ } + debug={debugComponents} + > + Canon Mark III + +
+
+ } + label="Astia/Soft" + type="icon-last" + iconWide + badged + debug={debugComponents} + /> +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } + label="Astia/Soft" + type="icon-last" + badged + debug={debugComponents} + /> +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+
*]:bg-gray-800', + '[&>*]:flex', + )}> + {DEBUG_LINES.map((_, i) => +
+ Line {(i + 1).toString().padStart(2, '0')} +
+ )} +
+
+ + ); +} diff --git a/src/camera/PhotoCamera.tsx b/src/camera/PhotoCamera.tsx index 1f9eb84c..e6e8511a 100644 --- a/src/camera/PhotoCamera.tsx +++ b/src/camera/PhotoCamera.tsx @@ -2,7 +2,9 @@ import { AiFillApple } from 'react-icons/ai'; import { pathForCamera } from '@/site/paths'; import { IoMdCamera } from 'react-icons/io'; import { Camera, formatCameraText } from '.'; -import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink'; +import EntityLink, { + EntityLinkExternalProps, +} from '@/components/primitives/EntityLink'; export default function PhotoCamera({ camera, @@ -31,7 +33,7 @@ export default function PhotoCamera({ /> : } type={showAppleIcon && isCameraApple ? 'icon-first' : type} badged={badged} diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 8a942928..e3fa1b6d 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -27,8 +27,9 @@ export default function Badge({ ); case 'small': return clsx( - 'px-[0.3rem] py-1 rounded-[0.25rem]', - 'text-[0.7rem] font-medium', + 'px-[4px] py-[2.5px]', + 'md:px-[4.5px] md:py-[3px]', + 'text-[0.7rem] font-medium rounded-[0.25rem]', highContrast ? 'text-invert bg-invert' : 'text-medium bg-gray-300/30 dark:bg-gray-700/50', @@ -43,7 +44,7 @@ export default function Badge({ }; return ( <> - - {labelSmall ?? label} - - - {label} - - ; - - const classForContrast = () => { - switch (contrast) { - case 'low': - return 'text-dim'; - case 'high': - return 'text-main'; - default: - return 'text-medium'; - } - }; - - return ( - - - {type !== 'icon-only' && <> - {badged - ? - - {renderLabel()} - - - : - {renderLabel()} - } - } - {icon && type !== 'text-only' && - - {icon} - } - - {hoverEntity !== undefined && - - {hoverEntity} - } - - ); -} diff --git a/src/components/primitives/EntityLink.tsx b/src/components/primitives/EntityLink.tsx new file mode 100644 index 00000000..9ccd22fc --- /dev/null +++ b/src/components/primitives/EntityLink.tsx @@ -0,0 +1,89 @@ +import { ReactNode } from 'react'; +import LabeledIcon, { LabeledIconType } from './LabeledIcon'; +import Badge from '../Badge'; +import { clsx } from 'clsx/lite'; + +export interface EntityLinkExternalProps { + type?: LabeledIconType + badged?: boolean + contrast?: 'low' | 'medium' | 'high' +} + +export default function EntityLink({ + icon, + label, + labelSmall, + iconWide, + type, + badged, + contrast = 'medium', + href, + prefetch, + title, + hoverEntity, + debug, +}: { + icon: ReactNode + label: ReactNode + labelSmall?: ReactNode + iconWide?: boolean + href?: string + prefetch?: boolean + title?: string + hoverEntity?: ReactNode + debug?: boolean +} & EntityLinkExternalProps) { + const classForContrast = () => { + switch (contrast) { + case 'low': + return 'text-dim'; + case 'high': + return 'text-main'; + default: + return 'text-medium'; + } + }; + + const renderLabel = () => <> + + {labelSmall ?? label} + + + {label} + + ; + + return ( + + + {badged + ? + {renderLabel()} + + : renderLabel()} + + {hoverEntity !== undefined && + + {hoverEntity} + } + + ); +} diff --git a/src/components/primitives/Icon.tsx b/src/components/primitives/Icon.tsx new file mode 100644 index 00000000..57adbe06 --- /dev/null +++ b/src/components/primitives/Icon.tsx @@ -0,0 +1,35 @@ +import { ReactNode } from 'react'; +import { clsx } from 'clsx/lite'; +import Spinner from '../Spinner'; + +export default function Icon({ + children, + className, + iconClassName, + wide, + loading, + debug, +}: { + children: ReactNode + className?: string + iconClassName?: string + wide?: boolean + loading?: boolean + debug?: boolean, +}) { + return ( + + {loading + ? + : + {children} + } + + ); +} diff --git a/src/components/primitives/LabeledIcon.tsx b/src/components/primitives/LabeledIcon.tsx new file mode 100644 index 00000000..ca249680 --- /dev/null +++ b/src/components/primitives/LabeledIcon.tsx @@ -0,0 +1,60 @@ +import { ComponentProps, ReactNode } from 'react'; +import Icon from './Icon'; +import { clsx } from 'clsx/lite'; +import Link from 'next/link'; + +export type LabeledIconType = + 'icon-first' | + 'icon-last' | + 'icon-only' | + 'text-only'; + +export default function LabeledIcon({ + icon, + type = 'icon-first', + className: classNameProp, + children, + iconWide, + href, + prefetch, + debug, +}: { + icon?: ReactNode, + type?: LabeledIconType, + className?: string, + children: ReactNode, + iconWide?:boolean, + debug?: boolean, +} & Partial>) { + const className = clsx( + 'inline-flex gap-x-1 md:gap-x-1.5', + classNameProp, + debug && 'border border-green-500 m-[-1px]', + ); + + const renderContent = () => <> + {icon && type !== 'text-only' && + + {icon} + } + {children && type !== 'icon-only' && + + {children} + } + ; + + return href + ? + {renderContent()} + + :
+ {renderContent()} +
; +} diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index c54b0088..376315fe 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -65,11 +65,11 @@ export default function PhotoLarge({ contentSide={
{/* Meta */}
@@ -88,7 +88,7 @@ export default function PhotoLarge({
-
+
{photo.caption &&
{photo.caption} @@ -106,7 +106,7 @@ export default function PhotoLarge({
{/* EXIF Data */} -
+
{showExifContent && <>
    @@ -134,8 +134,8 @@ export default function PhotoLarge({ />} }
    -
    +
    {isPathAdmin(pathname) ? <> {userEmail === undefined && diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index d140f0ef..b17daa95 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -138,7 +138,7 @@ export default function SiteChecklistClient({
    ; return ( -
    +
    } diff --git a/src/site/globals.css b/src/site/globals.css index 9803be2a..fefddffb 100644 --- a/src/site/globals.css +++ b/src/site/globals.css @@ -116,7 +116,7 @@ hover:text-gray-600 hover:dark:text-gray-400 } - /* Common Utilities: Text */ + /* Utilities: Text */ .text-main { @apply text-gray-900 dark:text-gray-100 @@ -145,7 +145,7 @@ @apply text-red-500 dark:text-red-400 } - /* Common Utilities: Background */ + /* Utilities: Background */ .bg-main { @apply bg-white dark:bg-black @@ -159,4 +159,22 @@ @apply bg-gray-900 dark:bg-gray-100 } + /* Utilities: Baseline Grid */ + .space-y-baseline { + @apply + space-y-[1.125rem] md:space-y-[1.25rem] + } + .gap-y-baseline { + @apply + gap-y-[1.125rem] md:gap-y-[1.25rem] + } + .gap-baseline { + @apply + gap-[1.125rem] md:gap-[1.25rem] + } + .debug-baseline-grid { + @apply + bg-[repeating-linear-gradient(to_bottom,#222,#222_1px,transparent_1px,transparent_1.125rem)] + md:bg-[repeating-linear-gradient(to_bottom,#222,#222_1px,transparent_1px,transparent_1.25rem)] + } } diff --git a/src/tag/FavsTag.tsx b/src/tag/FavsTag.tsx index 5496a276..65fc5d8e 100644 --- a/src/tag/FavsTag.tsx +++ b/src/tag/FavsTag.tsx @@ -1,8 +1,10 @@ import { FaStar } from 'react-icons/fa'; -import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink'; import { TAG_FAVS } from '.'; import { pathForTag } from '@/site/paths'; import { clsx } from 'clsx/lite'; +import EntityLink, { + EntityLinkExternalProps, +} from '@/components/primitives/EntityLink'; export default function FavsTag({ type, @@ -30,7 +32,7 @@ export default function FavsTag({ size={12} className={clsx( 'text-amber-500', - 'translate-x-[-1px] translate-y-[3.5px]', + 'translate-x-[-1px] translate-y-[-0.5px]', )} />} type={type} diff --git a/src/tag/PhotoTag.tsx b/src/tag/PhotoTag.tsx index 56fab6a6..0c1e4546 100644 --- a/src/tag/PhotoTag.tsx +++ b/src/tag/PhotoTag.tsx @@ -1,7 +1,9 @@ import { pathForTag } from '@/site/paths'; import { FaTag } from 'react-icons/fa'; import { formatTag } from '.'; -import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink'; +import EntityLink, { + EntityLinkExternalProps, +} from '@/components/primitives/EntityLink'; export default function PhotoTag({ tag, @@ -19,7 +21,7 @@ export default function PhotoTag({ href={pathForTag(tag)} icon={} type={type} badged={badged} diff --git a/src/tag/PhotoTags.tsx b/src/tag/PhotoTags.tsx index ef48121e..6eec2af3 100644 --- a/src/tag/PhotoTags.tsx +++ b/src/tag/PhotoTags.tsx @@ -1,7 +1,7 @@ import PhotoTag from '@/tag/PhotoTag'; import { isTagFavs } from '.'; import FavsTag from './FavsTag'; -import { EntityLinkExternalProps } from '@/components/EntityLink'; +import { EntityLinkExternalProps } from '@/components/primitives/EntityLink'; export default function PhotoTags({ tags, @@ -10,13 +10,13 @@ export default function PhotoTags({ tags: string[] } & EntityLinkExternalProps) { return ( -
    +
    {tags.map(tag => -
    + <> {isTagFavs(tag) - ? - : } -
    )} + ? + : } + )}
    ); } diff --git a/tailwind.config.js b/tailwind.config.js index 9816930c..b76c7276 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,13 +12,13 @@ module.exports = { ...defaultTheme.screens, }, fontSize: { - 'xs': '0.75rem', - 'sm': ['0.825rem', '1.15rem'], - 'base': ['0.875rem', '1.275rem'], - 'lg': ['0.925rem', '1.05rem'], - 'xl': '1rem', - '2xl': '1.1rem', - '3xl': ['1.3rem', '1.7rem'], + 'xs': ['0.75rem', '1rem'], // 12px on 16px + 'sm': ['0.8125rem', '1.125rem'], // 13px on 18px [Default: mobile] + 'base': ['0.875rem', '1.25rem'], // 14px on 20px [Default: desktop] + 'lg': ['1rem', '1.25rem'], // 16px on 20px + 'xl': ['1.125rem', '1.25rem'], // 18px on 20px + '2xl': ['1.25rem', '1.25rem'], // 20px on 20px + '3xl': ['1.5rem', '1.5rem'], // 24px on 24px }, extend: { fontFamily: {