Skip to content

Commit

Permalink
feat: added redability hover card
Browse files Browse the repository at this point in the history
  • Loading branch information
fluid-design-io committed Jun 24, 2024
1 parent 0830d7d commit f7bcea1
Showing 1 changed file with 253 additions and 88 deletions.
341 changes: 253 additions & 88 deletions apps/web/components/palette/base-color-palettes.tsx
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

0 comments on commit f7bcea1

Please sign in to comment.