-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0830d7d
commit f7bcea1
Showing
1 changed file
with
253 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,116 +1,281 @@ | ||
import { colorStepMap } from "@/lib/colorStepMap"; | ||
import { useColorStore } from "@/store/store"; | ||
import { BaseColorTypes } from "@/types/app"; | ||
import { cn } from "@ui/lib/utils"; | ||
import React from "react"; | ||
'use client' | ||
|
||
import PaletteButton from "./base-palette-button"; | ||
import ColorString from "./color-string"; | ||
import ReadabilityString from "./readability-string"; | ||
import { useColorStore } from '@/context/color-store-provider' | ||
|
||
import { useToolStore } from '@/store/toolStore' | ||
import { BaseColorTypes } from '@/types/app' | ||
import { cn } from '@ui/lib/utils' | ||
|
||
import { ColorStep, colorStepMap } from '@/lib/colorStepMap' | ||
import { Separator } from '@ui/components/ui/separator' | ||
import PaletteButton from './base-palette-button' | ||
import ColorString from './color-string' | ||
import ReadabilityString from './readability-string' | ||
|
||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@ui/components/ui/hover-card' | ||
import { AArrowUp, X } from 'lucide-react' | ||
import { useState } from 'react' | ||
|
||
const ReadabilityBadge = ({ score }: { score?: number }) => { | ||
if (!score) return null | ||
const eligibleText = `WCAG compliance level ${score >= 7 ? 'AAA' : 'AA'} with a score of ${score}` | ||
const eligibleConditionalText = `Eligible for WCAG compliance when text size is over 18px bold or 24px regular, with a score of ${score}` | ||
const ineligibleText = `Ineligible for WCAG compliance with a score of ${score}` | ||
const badgeClassName = cn( | ||
' z-[9] flex w-6 items-center justify-center rounded-full border py-2 text-sm font-semibold text-foreground shadow-lg', | ||
{ 'bg-white dark:bg-zinc-900': score >= 4.5 }, | ||
{ 'bg-amber-200 dark:bg-amber-900': score < 4.5 && score >= 3.1 }, | ||
{ 'bg-rose-200 dark:bg-rose-950': score < 3.1 } | ||
) | ||
return ( | ||
<> | ||
<div | ||
className={cn('pointer-events-none absolute inset-y-1.5 -left-3', badgeClassName)} | ||
title={score >= 4.5 ? eligibleText : score >= 3.1 ? eligibleConditionalText : ineligibleText} | ||
> | ||
{score >= 4.5 ? ( | ||
<div className="-translate-y-0.5 tracking-[-0.25rem] text-muted-foreground [text-orientation:upright] [writing-mode:vertical-rl]"> | ||
<span aria-hidden="true">{score >= 7 ? 'AAA' : 'AA'}</span> | ||
<span className="sr-only">{eligibleText}</span> | ||
</div> | ||
) : score >= 3.1 ? ( | ||
<> | ||
<AArrowUp className="size-4 text-amber-700 dark:text-amber-200" /> | ||
<span className="sr-only">{eligibleConditionalText}</span> | ||
</> | ||
) : ( | ||
<> | ||
<X className="size-3.5 text-rose-700 dark:text-rose-200" strokeWidth={3} /> | ||
<span className="sr-only">{ineligibleText}</span> | ||
</> | ||
)} | ||
</div> | ||
<div className={cn('absolute inset-y-1.5 -right-3', badgeClassName)}> | ||
<div className="-translate-y-0.5 font-mono tabular-nums text-muted-foreground [writing-mode:vertical-rl]"> | ||
<span | ||
className={cn( | ||
'text-xs', | ||
{ 'text-foreground': score >= 4.5 }, | ||
{ 'text-amber-700 dark:text-amber-200': score < 4.5 && score >= 3.1 }, | ||
{ 'text-rose-700 dark:text-rose-200': score < 3.1 } | ||
)} | ||
> | ||
{score.toFixed(1)} | ||
</span> | ||
</div> | ||
</div> | ||
</> | ||
) | ||
} | ||
|
||
const PreviewText = ({ | ||
backgroundColor, | ||
color, | ||
cursorX = 51, | ||
index = 0, | ||
}: { | ||
backgroundColor?: string | ||
color: string | ||
cursorX?: number | ||
index?: number | ||
}) => { | ||
const shift = 2 // 2% shift | ||
const clipPathWidth = Math.min(95, Math.max(5, cursorX)) | ||
return ( | ||
<div | ||
className="absolute inset-0 flex select-none items-center justify-center" | ||
style={{ | ||
backgroundColor, | ||
clipPath: backgroundColor | ||
? `polygon(0 0, ${clipPathWidth - shift + index * shift * 2}% 0, ${clipPathWidth + shift + index * shift * 2}% 100%, 0 100%)` | ||
: 'none', | ||
}} | ||
> | ||
<p className="text-shadow px-5 text-center" style={{ color }}> | ||
The five boxing wizards jump quickly. | ||
</p> | ||
</div> | ||
) | ||
} | ||
|
||
function BaseColorPalettes() { | ||
const { colorPalettes, colorMode, showReadability } = | ||
useColorStore.getState(); | ||
const animation = (i, type) => { | ||
let baseDelay = 0.12; | ||
const { colorPalettes } = useColorStore((s) => s.colors) | ||
const { colorMode, showReadability } = useToolStore() | ||
const [cursorX, setCusorX] = useState(51) | ||
const [cursorY, setCusorY] = useState(50) | ||
const animation = (i: number, type: BaseColorTypes) => { | ||
let baseDelay = 0.12 | ||
switch (type) { | ||
case "primary": | ||
baseDelay += 0; | ||
break; | ||
case "secondary": | ||
baseDelay += 0.06; | ||
break; | ||
case "accent": | ||
baseDelay += 0.12; | ||
break; | ||
case "gray": | ||
baseDelay += 0.18; | ||
break; | ||
case 'primary': | ||
baseDelay += 0 | ||
break | ||
case 'secondary': | ||
baseDelay += 0.06 | ||
break | ||
case 'accent': | ||
baseDelay += 0.12 | ||
break | ||
case 'gray': | ||
baseDelay += 0.18 | ||
break | ||
default: | ||
baseDelay += 0; | ||
break; | ||
baseDelay += 0 | ||
break | ||
} | ||
let springDelay = Math.round((Math.pow(1.2, i) - 0.8) * 100) / 100; | ||
return baseDelay + springDelay * 0.06; | ||
}; | ||
let springDelay = Math.round((Math.pow(1.2, i) - 0.8) * 100) / 100 | ||
return baseDelay + springDelay * 0.06 | ||
} | ||
const colorPaletteNames: BaseColorTypes[] = ['primary', 'secondary', 'accent', 'gray'] | ||
const handleDragClipPath = (e: React.MouseEvent) => { | ||
const { clientX, clientY } = e | ||
const { left, right, top, bottom } = e.currentTarget.getBoundingClientRect() | ||
const width = right - left | ||
const cursorX = ((clientX - left) / width) * 100 | ||
const cursorY = ((clientY - top) / (bottom - top)) * 100 | ||
setCusorX(cursorX) | ||
setCusorY(cursorY) | ||
} | ||
return ( | ||
<div className="grid gap-4 @xs/section-secondary:grid-cols-2 @md/section-secondary:grid-cols-4 @2xl/section-secondary:grid-cols-1"> | ||
{Object.keys(colorPalettes).map((type: BaseColorTypes) => { | ||
{colorPaletteNames.map((type: BaseColorTypes) => { | ||
return ( | ||
<div | ||
id={`${type}-color-palettes`} | ||
className={cn( | ||
"grid grid-flow-row grid-cols-1 gap-1.5", | ||
"@2xl/section-secondary:grid-cols-11", | ||
'grid grid-flow-row grid-cols-1 gap-1.5', | ||
'@2xl/section-secondary:grid-cols-11', | ||
'scroll-mt-8' | ||
)} | ||
id={`${type}-color-palettes`} | ||
key={`base-color-palette-${type}`} | ||
> | ||
{colorPalettes[type].map((color, i) => { | ||
const step = i; | ||
const step: ColorStep = i as any | ||
return ( | ||
<div | ||
className="relative flex flex-col bg-background transition-colors" | ||
key={`base-color-palette-${type}-${step}`} | ||
> | ||
<PaletteButton | ||
{...{ | ||
animation: animation(step, type), | ||
type, | ||
step: step, | ||
color: color, | ||
colorMode, | ||
}} | ||
/> | ||
<div | ||
className={cn( | ||
"mt-1.5 flex flex-col items-start justify-start text-xs tabular-nums", | ||
"@xs/section-secondary:flex-row @xs/section-secondary:items-center @xs/section-secondary:justify-between", | ||
"@md/section-secondary:flex-col @md/section-secondary:items-start @md/section-secondary:justify-start", | ||
"@md/section-secondary:min-h-[2.0625rem]", | ||
)} | ||
> | ||
<div className="font-comfortaa font-bold text-foreground/80"> | ||
{colorStepMap[i]} | ||
</div> | ||
<div className="relative w-full"> | ||
<HoverCard key={`base-color-palette-${type}-${step}`}> | ||
<HoverCardTrigger asChild> | ||
<div className="relative flex flex-col bg-background transition-colors"> | ||
<PaletteButton | ||
{...{ | ||
animation: animation(step, type), | ||
color: color, | ||
colorMode, | ||
step: step, | ||
type, | ||
}} | ||
/> | ||
|
||
<div | ||
className={cn( | ||
"line-clamp-1 text-muted-foreground/80 transition-colors duration-1000 @md/section-secondary:w-[-webkit-fill-available]", | ||
"hover:[calc(max(max-content,100%))] hover:overflow-visible hover:bg-background hover:transition-none", | ||
"hover:-mx-1 hover:-my-0.5 hover:rounded hover:px-1 hover:py-0.5", | ||
"hover:text-muted-foreground hover:ring-1 hover:ring-inset hover:ring-border", | ||
"contrast-more:font-medium contrast-more:text-foreground/80 contrast-more:hover:text-foreground", | ||
"@md/section-secondary:hover:absolute @md/section-secondary:hover:left-0 @md/section-secondary:hover:top-0 @md/section-secondary:hover:z-10 @md/section-secondary:hover:shadow-sm", | ||
'mt-1.5 flex flex-col items-start justify-start text-xs tabular-nums', | ||
'@xs/section-secondary:flex-row @xs/section-secondary:items-center @xs/section-secondary:justify-between', | ||
'@md/section-secondary:flex-col @md/section-secondary:items-start @md/section-secondary:justify-start', | ||
'@md/section-secondary:min-h-[2.0625rem]' | ||
)} | ||
> | ||
{showReadability ? ( | ||
<ReadabilityString | ||
{...{ | ||
step, | ||
type, | ||
animation: animation(step, type), | ||
}} | ||
/> | ||
) : ( | ||
<ColorString | ||
{...{ | ||
step, | ||
type, | ||
animation: animation(step, type), | ||
}} | ||
/> | ||
)} | ||
<div className="font-comfortaa font-bold text-foreground/80">{colorStepMap[step]}</div> | ||
<div className="relative w-full"> | ||
<div | ||
className={cn( | ||
'line-clamp-1 text-end text-muted-foreground/80 transition-colors duration-1000', | ||
'@md/section-secondary:w-[-webkit-fill-available] @md/section-secondary:text-start', | ||
'hocus:[calc(max(max-content,100%))] hocus:overflow-visible hocus:bg-background hocus:transition-none', | ||
'hocus:-mx-1 hocus:-my-0.5 hocus:rounded hocus:px-1 hocus:py-0.5', | ||
'hocus:text-muted-foreground hocus:ring-1 hocus:ring-inset hocus:ring-border hocus:outline-none', | ||
'contrast-more:hocus:text-foreground contrast-more:font-medium contrast-more:text-foreground/80', | ||
'@md/section-secondary:hocus:absolute @md/section-secondary:hocus:left-0 @md/section-secondary:hocus:top-0 @md/section-secondary:hocus:z-10 @md/section-secondary:hocus:line-clamp-none @md/section-secondary:hocus:shadow-sm' | ||
)} | ||
> | ||
{showReadability ? ( | ||
<ReadabilityString | ||
{...{ | ||
animation: animation(step, type), | ||
step, | ||
type, | ||
}} | ||
/> | ||
) : ( | ||
<ColorString | ||
{...{ | ||
animation: animation(step, type), | ||
step, | ||
type, | ||
}} | ||
/> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</HoverCardTrigger> | ||
<HoverCardContent | ||
style={{ | ||
backgroundColor: colorPalettes[type][0]?.color, | ||
}} | ||
className="group/hover-card relative mx-4 flex min-h-[10rem] w-[min(22rem,100svw)] flex-col items-stretch justify-stretch p-0 shadow-2xl" | ||
> | ||
<div | ||
className="absolute inset-x-0 inset-y-0 z-[12] w-full cursor-none" | ||
onMouseMove={handleDragClipPath} | ||
onMouseLeave={() => { | ||
setTimeout(() => { | ||
setCusorX(51) | ||
setCusorY(50) | ||
}, 500) | ||
}} | ||
> | ||
<div | ||
className="absolute z-[13] size-2 -translate-x-3 -translate-y-1 rounded-full opacity-0 ring-2 ring-foreground ring-offset-1 ring-offset-background transition-opacity group-hover/hover-card:opacity-100" | ||
style={{ | ||
backgroundColor: colorPalettes[type][step]?.color, | ||
left: `${cursorX + 8 * (cursorY / 100)}%`, | ||
top: `${cursorY}%`, | ||
}} | ||
/> | ||
</div> | ||
<div className="relative flex flex-1"> | ||
<ReadabilityBadge score={color?.readability?.foreground.readability} /> | ||
<div className="relative w-full flex-1 items-center justify-center overflow-hidden rounded-t-md"> | ||
<PreviewText color={colorPalettes[type][step]?.color} /> | ||
|
||
<PreviewText | ||
color={colorPalettes[type][0]?.color} | ||
backgroundColor={colorPalettes[type][step]?.color} | ||
cursorX={cursorX} | ||
/> | ||
</div> | ||
</div> | ||
<Separator | ||
style={{ | ||
backgroundColor: colorPalettes[type][5]?.color, | ||
opacity: 0.5, | ||
}} | ||
/> | ||
<div | ||
className="relative flex flex-1" | ||
style={{ | ||
backgroundColor: colorPalettes[type][10]?.color, | ||
}} | ||
> | ||
<ReadabilityBadge score={color?.readability?.background.readability} /> | ||
<div className="relative w-full flex-1 items-center justify-center overflow-hidden rounded-b-md"> | ||
<PreviewText color={colorPalettes[type][step]?.color} /> | ||
<PreviewText | ||
color={colorPalettes[type][10]?.color} | ||
backgroundColor={colorPalettes[type][step]?.color} | ||
cursorX={cursorX} | ||
index={1} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
</HoverCardContent> | ||
</HoverCard> | ||
) | ||
})} | ||
</div> | ||
); | ||
) | ||
})} | ||
</div> | ||
); | ||
) | ||
} | ||
|
||
export default BaseColorPalettes; | ||
export default BaseColorPalettes |