Skip to content

Commit

Permalink
feat: ✨ added Contrast Ratio plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
fluid-design-io committed Oct 31, 2023
1 parent e7ed6ea commit 18c7137
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 29 deletions.
8 changes: 6 additions & 2 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,21 @@ export default async function RootLayout({
}: {
children: React.ReactNode;
}) {
const { baseColors, colorPalettes, colorMode } = await getServerColors();
const { baseColors, colorPalettes, colorMode, showReadability } =
await getServerColors();
useColorStore.setState({
baseColors,
colorPalettes,
colorMode,
showReadability,
});
return (
<html lang="en" suppressHydrationWarning>
<body className={cn(inter.variable, comfortaa.variable)}>
<div id="skip-nav" />
<ColorStoreInitializer {...{ baseColors, colorPalettes, colorMode }} />
<ColorStoreInitializer
{...{ baseColors, colorPalettes, colorMode, showReadability }}
/>
<StyleSheetInitializer />
<PerformanceChecker />
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
Expand Down
34 changes: 23 additions & 11 deletions apps/web/components/palette/base-color-palettes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import React from "react";

import PaletteButton from "./base-palette-button";
import ColorString from "./color-string";
import ReadabilityString from "./readability-string";

function BaseColorPalettes() {
const { colorPalettes, colorMode } = useColorStore.getState();
const { colorPalettes, colorMode, showReadability } =
useColorStore.getState();
const animation = (i, type) => {
let baseDelay = 0.12;
switch (type) {
Expand Down Expand Up @@ -47,7 +49,7 @@ function BaseColorPalettes() {
const step = i;
return (
<div
className="flex flex-col bg-background transition-colors"
className="relative flex flex-col bg-background transition-colors"
key={`base-color-palette-${type}-${step}`}
>
<PaletteButton
Expand All @@ -70,24 +72,34 @@ function BaseColorPalettes() {
<div className="font-comfortaa font-bold text-foreground/80">
{colorStepMap[i]}
</div>
<div className="relative">
<div className="relative w-full">
<div
className={cn(
"line-clamp-1 text-muted-foreground/80 transition-colors duration-1000 @md/section-secondary:w-[-webkit-fill-available]",
"hover:w-max hover:overflow-visible hover:bg-background hover:transition-none",
"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",
)}
>
<ColorString
{...{
step,
type,
animation: animation(step, type),
}}
/>
{showReadability ? (
<ReadabilityString
{...{
step,
type,
animation: animation(step, type),
}}
/>
) : (
<ColorString
{...{
step,
type,
animation: animation(step, type),
}}
/>
)}
</div>
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions apps/web/components/palette/base-palette-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { BaseColorTypes, RawColor } from "@/types/app";
import { cn } from "@ui/lib/utils";
import { AnimatePresence, motion } from "framer-motion";
import { motion } from "framer-motion";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { Copy } from "lucide-react";
Expand Down Expand Up @@ -97,7 +97,9 @@ const PaletteButton = ({
transitionDelay: `${animation}s`,
transitionDuration: `${animation * 1.2}s`,
}}
initial={false}
initial={{
opacity: 1,
}}
animate={
performance === Performance.high
? {
Expand Down
58 changes: 58 additions & 0 deletions apps/web/components/palette/readability-string.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";

import { useColorStore } from "@/store/store";
import { BaseColorTypes } from "@/types/app";
import { cn } from "@ui/lib/utils";

function ReadabilityString({
type,
step,
}: {
type: BaseColorTypes;
step: number;
}) {
const { colorPalettes } = useColorStore();
const readability = colorPalettes[type][step]?.readability;
// Function to determine border style
const getBorderStyle = (readabilityValue: number) => {
if (readabilityValue >= 7) {
return "border-border ring-1 ring-inset ring-offset-2 ring-primary ring-offset-border";
} else if (readabilityValue >= 4.5) {
return "border border-accent-foreground";
} else if (readabilityValue >= 3) {
console.log(`readabilityValue: ${readabilityValue}`);
return "border border-dashed";
} else {
return "";
}
};

const foregroundBorderStyle = readability
? getBorderStyle(readability.foreground.readability)
: "";

const backgroundBorderStyle = readability
? getBorderStyle(readability.background.readability)
: "";
return (
<div
className="animate-text flex w-full justify-between"
title={`readability`}
>
<span
className={cn(foregroundBorderStyle, "rounded px-1")}
title={`Against Foreground Readability: ${readability?.foreground?.readability}`}
>
{readability?.foreground?.readability}
</span>
<span
className={cn(backgroundBorderStyle, "!-ml-0.5 rounded px-1")}
title={`Against Background Readability: ${readability?.background?.readability}`}
>
{readability?.background?.readability}
</span>
</div>
);
}

export default ReadabilityString;
39 changes: 39 additions & 0 deletions apps/web/components/svg/eye-cvd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react";
import { SVGProps } from "react";
const EyeCVD = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<g clipPath="url(#a)">
<path
fill="currentColor"
fillRule="evenodd"
d="M12 16.684c-7 0-10-7-10-7S3.987 5.047 8.5 3.32c.32-.123.654-.23 1-.321l1.23 2.89a4.002 4.002 0 1 0 3.127 7.337l1.26 2.957c-.358.12-.73.22-1.117.3a9.93 9.93 0 0 1-2 .2Zm1.068-5.31.79 1.853A4 4 0 0 0 12 5.684c-.444 0-.87.072-1.27.205l.79 1.853a2 2 0 1 0 1.548 3.633Zm0 0L11.52 7.743a2 2 0 0 1 1.548 3.633Z"
clipRule="evenodd"
/>
<path
stroke="currentColor"
strokeLinecap="square"
strokeLinejoin="round"
strokeWidth={2}
d="M10 3.854a8.81 8.81 0 0 1 3.111-.554C19.333 3.3 22 9.387 22 9.387s-2.155 4.92-7.111 5.913"
/>
<path
stroke="currentColor"
strokeDasharray="4 4"
strokeLinecap="round"
strokeWidth={2}
d="M3 20h19"
/>
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h24v24H0z" />
</clipPath>
</defs>
</svg>
);
export default EyeCVD;
34 changes: 34 additions & 0 deletions apps/web/components/toolbar/cvd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import primaryToolbarMenu from "../ui/primary-toolbar-menu";
import { useColorStore } from "@/store/store";
import ToolbarMenuItem from "./toolbar-menu-item";
import { cn } from "@ui/lib/utils";
import { handleToggleReadability } from "@/app/actions";

function CVD() {
const menuItem = primaryToolbarMenu["Color Vision Deficiency"];
const { showReadability } = useColorStore();
return (
<form
onSubmit={(e) => {
e.preventDefault();
handleToggleReadability(`${!showReadability}`);
useColorStore.setState({ showReadability: !showReadability });
}}
>
<button
type="submit"
aria-label="Toggle Readability"
className={cn(
showReadability &&
"-mx-1.5 rounded-sm bg-primary/20 px-1.5 lg:mx-0 lg:px-0",
)}
>
<ToolbarMenuItem {...menuItem} />
</button>
</form>
);
}

export default CVD;

CVD.displayName = "CVD";
4 changes: 4 additions & 0 deletions apps/web/components/toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import ToolbarUploadImage from "./upload-image";
import ToolbarDownloadBasePalette from "./download-base-palette";
import ToolbarShareableLink from "./shareable-link";
import Readability from "./readability";
import CVD from "./cvd";

const ToolbarPrimative = ({ children }) => children;

Expand All @@ -10,4 +12,6 @@ export const Toolbar = Object.assign(ToolbarPrimative, {
UploadImage: ToolbarUploadImage,
DownloadBasePalette: ToolbarDownloadBasePalette,
ShareableLink: ToolbarShareableLink,
Readability: Readability,
CVD: CVD,
});
47 changes: 47 additions & 0 deletions apps/web/components/toolbar/readability.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import primaryToolbarMenu from "../ui/primary-toolbar-menu";
import { useColorStore } from "@/store/store";
import ToolbarMenuItem from "./toolbar-menu-item";
import { cn } from "@ui/lib/utils";
import { handleToggleReadability } from "@/app/actions";
import { useState } from "react";

import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@ui/components/ui/popover";

import dynamic from "next/dynamic";

const ReadabilityPlugin = dynamic(() => import("./plugin/readability.plugin"), {
ssr: false,
});

function Readability() {
const menuItem = primaryToolbarMenu.Readability;
const [open, setOpen] = useState(false);
const { showReadability } = useColorStore();
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger>
<button
type="button"
aria-label="Show Readability Plugin"
className={cn(
showReadability &&
"-mx-1.5 rounded-sm bg-primary/20 px-1.5 lg:mx-0 lg:px-0",
)}
>
<ToolbarMenuItem {...menuItem} />
</button>
</PopoverTrigger>
<PopoverContent className="w-[18rem] sm:w-[24rem]" align="end">
{open && <ReadabilityPlugin key={`shareable-${open}`} />}
</PopoverContent>
</Popover>
);
}

export default Readability;

Readability.displayName = "Readability";
3 changes: 0 additions & 3 deletions apps/web/components/ui/color-mode-dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import {
} from "@ui/components/ui/dropdown-menu";
import { useColorStore } from "@/store/store";
import { ColorMode } from "@/types/app";
import { ChevronDownIcon } from "lucide-react";
import { useEffect, useState } from "react";
function ColorModeDropdownMenu() {
const [mounted, setMounted] = useState(false);
const { colorMode, setColorMode } = useColorStore();
const handleChangeColorMode = async (mode: ColorMode) => {
setColorMode(mode);
Expand Down
2 changes: 2 additions & 0 deletions apps/web/components/ui/desktop-primary-toolbar-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ function DesktopPreviewToolbarButtons() {
)}
>
<Toolbar>
<Toolbar.Readability />
{/* <Toolbar.CVD /> coming soon */}
<Toolbar.UploadImage />
<Toolbar.DownloadBasePalette />
<Toolbar.ShareableLink />
Expand Down
15 changes: 14 additions & 1 deletion apps/web/components/ui/primary-toolbar-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Download, ImagePlus, Link } from "lucide-react";
import { Download, Eye, ImagePlus, Link } from "lucide-react";
import EyeCVD from "../svg/eye-cvd";

export enum ToolbarMenus {
UPLOAD_IMAGE = "Upload Image",
DOWNLOAD = "Download",
SHARE = "Share",
READABILITY = "Readability",
CVD = "Color Vision Deficiency",
}

export type ToolbarMenu = {
Expand All @@ -17,6 +20,16 @@ export type PrimaryToolbarMenu = {
};

const primaryToolbarMenu: PrimaryToolbarMenu = {
[ToolbarMenus.READABILITY]: {
title: "Readability",
description: "Check the contrast ratio of your palette",
icon: Eye,
},
[ToolbarMenus.CVD]: {
title: "Color Vision Deficiency",
description: "Simulate color vision deficiency",
icon: EyeCVD,
},
[ToolbarMenus.UPLOAD_IMAGE]: {
title: "Upload Image",
description: "Generate a color palette from an image",
Expand Down
Loading

0 comments on commit 18c7137

Please sign in to comment.