ruler-components 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +458 -0
- package/dist/index.d.ts +458 -0
- package/dist/index.js +3618 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3467 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -2
- package/AGENTS.md +0 -9
- package/CLAUDE.md +0 -1
- package/app/docs/[slug]/page.tsx +0 -147
- package/app/docs/layout.tsx +0 -26
- package/app/docs/page.tsx +0 -148
- package/app/favicon.ico +0 -0
- package/app/globals.css +0 -168
- package/app/icon.png +0 -0
- package/app/layout.tsx +0 -90
- package/app/opengraph-image.tsx +0 -10
- package/app/page.tsx +0 -34
- package/app/twitter-image.tsx +0 -10
- package/components/docs/accordion-doc-page.tsx +0 -352
- package/components/docs/alert-doc-page.tsx +0 -293
- package/components/docs/allinput-doc-page.tsx +0 -183
- package/components/docs/avatar-doc-page.tsx +0 -424
- package/components/docs/badge-doc-page.tsx +0 -363
- package/components/docs/button-doc-page.tsx +0 -406
- package/components/docs/card-doc-page.tsx +0 -364
- package/components/docs/checkbox-doc-page.tsx +0 -277
- package/components/docs/colors-doc-page.tsx +0 -430
- package/components/docs/dialog-doc-page.tsx +0 -364
- package/components/docs/docs-footer.tsx +0 -11
- package/components/docs/docs-mobile-menu-bar.tsx +0 -22
- package/components/docs/docs-mobile-nav-context.tsx +0 -27
- package/components/docs/docs-page.tsx +0 -263
- package/components/docs/docs-search.tsx +0 -303
- package/components/docs/docs-sidebar.tsx +0 -165
- package/components/docs/docs-topbar.tsx +0 -32
- package/components/docs/dropdown-doc-page.tsx +0 -522
- package/components/docs/getting-started-doc-page.tsx +0 -205
- package/components/docs/label-doc-page.tsx +0 -173
- package/components/docs/loaders-doc-page.tsx +0 -244
- package/components/docs/menu-doc-page.tsx +0 -373
- package/components/docs/modal-doc-page.tsx +0 -436
- package/components/docs/next-steps.tsx +0 -53
- package/components/docs/pagination-doc-page.tsx +0 -316
- package/components/docs/progress-doc-page.tsx +0 -387
- package/components/docs/radio-doc-page.tsx +0 -270
- package/components/docs/skeleton-doc-page.tsx +0 -230
- package/components/docs/stepper-doc-page.tsx +0 -309
- package/components/docs/switch-doc-page.tsx +0 -264
- package/components/docs/table-doc-page.tsx +0 -689
- package/components/docs/tabs-doc-page.tsx +0 -359
- package/components/docs/theming-doc-page.tsx +0 -243
- package/components/docs/tooltip-doc-page.tsx +0 -357
- package/components/docs/usage-section.tsx +0 -21
- package/components/icons/discord.tsx +0 -9
- package/components/icons/github.tsx +0 -9
- package/components/icons/x.tsx +0 -9
- package/components/installation-section.tsx +0 -34
- package/components/landing/feature-highlights.tsx +0 -105
- package/components/landing/footer-strip.tsx +0 -45
- package/components/landing/hero-card.tsx +0 -14
- package/components/landing/hero-spotlight.tsx +0 -126
- package/components/landing/hero.tsx +0 -64
- package/components/landing/navbar.tsx +0 -44
- package/components/landing/quick-start-section.tsx +0 -59
- package/components/landing/site-footer.tsx +0 -55
- package/components/theme-provider.tsx +0 -60
- package/components/ui/accordion.tsx +0 -174
- package/components/ui/alert.tsx +0 -103
- package/components/ui/allinput.tsx +0 -210
- package/components/ui/avatar.tsx +0 -179
- package/components/ui/badge.tsx +0 -52
- package/components/ui/button.tsx +0 -75
- package/components/ui/card.tsx +0 -105
- package/components/ui/checkbox.tsx +0 -130
- package/components/ui/code-block.tsx +0 -60
- package/components/ui/data-table.tsx +0 -235
- package/components/ui/dialog.tsx +0 -173
- package/components/ui/dropdown.tsx +0 -78
- package/components/ui/feature-card.tsx +0 -38
- package/components/ui/input.tsx +0 -44
- package/components/ui/label.tsx +0 -110
- package/components/ui/loaders.tsx +0 -139
- package/components/ui/menu.tsx +0 -118
- package/components/ui/modal.tsx +0 -399
- package/components/ui/package-manager-tabs.tsx +0 -40
- package/components/ui/pagination.tsx +0 -200
- package/components/ui/progress.tsx +0 -134
- package/components/ui/radio.tsx +0 -262
- package/components/ui/skeleton.tsx +0 -102
- package/components/ui/steppers.tsx +0 -309
- package/components/ui/switch.tsx +0 -153
- package/components/ui/table.tsx +0 -58
- package/components/ui/tabs.tsx +0 -166
- package/components/ui/theme-toggle.tsx +0 -60
- package/components/ui/tooltip.tsx +0 -238
- package/data/color-palette.ts +0 -35
- package/data/docs-nav.ts +0 -84
- package/eslint.config.mjs +0 -18
- package/index.ts +0 -8
- package/lib/cn.ts +0 -4
- package/lib/color.ts +0 -85
- package/lib/og-image.tsx +0 -138
- package/lib/theme.ts +0 -33
- package/next.config.ts +0 -7
- package/postcss.config.mjs +0 -7
- package/public/dark.png +0 -0
- package/public/dark1.png +0 -0
- package/public/dark2.png +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/light.png +0 -0
- package/public/light1.png +0 -0
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/video/hero-demo.mp4 +0 -0
- package/public/window.svg +0 -1
- package/tsconfig.json +0 -34
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { createPortal } from "react-dom";
|
|
5
|
-
import { motion } from "framer-motion";
|
|
6
|
-
import { Check, Copy, Palette } from "lucide-react";
|
|
7
|
-
import { DocsPage, DocsPageHeader, DocsSection } from "@/components/docs/docs-page";
|
|
8
|
-
import { Tooltip } from "@/components/ui/tooltip";
|
|
9
|
-
import { COLOR_PALETTE } from "@/data/color-palette";
|
|
10
|
-
import {
|
|
11
|
-
contrastLabel,
|
|
12
|
-
formatColor,
|
|
13
|
-
getContrastRatio,
|
|
14
|
-
readableTextColor,
|
|
15
|
-
type ColorFormat,
|
|
16
|
-
} from "@/lib/color";
|
|
17
|
-
import { cn } from "@/lib/cn";
|
|
18
|
-
|
|
19
|
-
const FORMATS: { label: string; value: ColorFormat }[] = [
|
|
20
|
-
{ label: "HEX", value: "hex" },
|
|
21
|
-
{ label: "RGB", value: "rgb" },
|
|
22
|
-
{ label: "HSL", value: "hsl" },
|
|
23
|
-
{ label: "CSS", value: "css" },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
export function ColorsDocPage() {
|
|
27
|
-
const [selected, setSelected] = useState(
|
|
28
|
-
COLOR_PALETTE.find((h) => h.name === "Blue")?.name ?? COLOR_PALETTE[0].name
|
|
29
|
-
);
|
|
30
|
-
const [activeStep, setActiveStep] = useState(500);
|
|
31
|
-
const [format, setFormat] = useState<ColorFormat>("hex");
|
|
32
|
-
const [copied, setCopied] = useState<string | null>(null);
|
|
33
|
-
const [mounted, setMounted] = useState(false);
|
|
34
|
-
|
|
35
|
-
useEffect(() => setMounted(true), []);
|
|
36
|
-
|
|
37
|
-
const activeHue = COLOR_PALETTE.find((h) => h.name === selected) ?? COLOR_PALETTE[0];
|
|
38
|
-
const activeShade =
|
|
39
|
-
activeHue.shades.find((s) => s.step === activeStep) ?? activeHue.shades[5];
|
|
40
|
-
|
|
41
|
-
const activeValue = formatColor(activeShade.hex, format);
|
|
42
|
-
const textColor = readableTextColor(activeShade.hex);
|
|
43
|
-
const isDarkOverlay = textColor === "#000000";
|
|
44
|
-
const overlaySoft = isDarkOverlay ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.16)";
|
|
45
|
-
const overlayStrong = isDarkOverlay ? "rgba(0,0,0,0.14)" : "rgba(255,255,255,0.28)";
|
|
46
|
-
|
|
47
|
-
function copy(value: string) {
|
|
48
|
-
navigator.clipboard.writeText(value).catch(() => {});
|
|
49
|
-
setCopied(value);
|
|
50
|
-
setTimeout(() => setCopied((prev) => (prev === value ? null : prev)), 1200);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function pickHue(name: string) {
|
|
54
|
-
setSelected(name);
|
|
55
|
-
setActiveStep(500);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<DocsPage wide className="relative z-10 min-w-0">
|
|
60
|
-
{mounted &&
|
|
61
|
-
createPortal(
|
|
62
|
-
<div
|
|
63
|
-
aria-hidden
|
|
64
|
-
className="pointer-events-none fixed left-0 right-0 top-0 z-0 h-80 max-w-full lg:left-64"
|
|
65
|
-
style={{
|
|
66
|
-
background: `radial-gradient(ellipse 70% 90% at 80% 0%, ${activeShade.hex}42, transparent 70%)`,
|
|
67
|
-
}}
|
|
68
|
-
/>,
|
|
69
|
-
document.getElementById("docs-shell") ?? document.body
|
|
70
|
-
)}
|
|
71
|
-
|
|
72
|
-
<DocsPageHeader
|
|
73
|
-
icon={Palette}
|
|
74
|
-
title="Colors"
|
|
75
|
-
description="The full color palette available across ruler.ui — every hue and shade used throughout components, ready to copy in HEX, RGB, HSL, or CSS."
|
|
76
|
-
/>
|
|
77
|
-
|
|
78
|
-
<div className="mt-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-zinc-500 dark:text-zinc-400">
|
|
79
|
-
<span>
|
|
80
|
-
<strong className="font-semibold text-zinc-900 dark:text-white">
|
|
81
|
-
{COLOR_PALETTE.length}
|
|
82
|
-
</strong>{" "}
|
|
83
|
-
hues
|
|
84
|
-
</span>
|
|
85
|
-
<span className="h-1 w-1 rounded-full bg-zinc-300 dark:bg-zinc-700" />
|
|
86
|
-
<span>
|
|
87
|
-
<strong className="font-semibold text-zinc-900 dark:text-white">
|
|
88
|
-
{COLOR_PALETTE.length * 11}
|
|
89
|
-
</strong>{" "}
|
|
90
|
-
shades
|
|
91
|
-
</span>
|
|
92
|
-
<span className="h-1 w-1 rounded-full bg-zinc-300 dark:bg-zinc-700" />
|
|
93
|
-
<span>WCAG contrast built in</span>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
<DocsSection first title="Explore" description="Every hue as a full-tone paint strip. Click one to spotlight it below.">
|
|
97
|
-
<div className="-mx-1 flex min-w-0 gap-3 overflow-x-auto px-1 py-2 [-ms-overflow-style:none] [scrollbar-width:none] sm:gap-5 [&::-webkit-scrollbar]:hidden">
|
|
98
|
-
{COLOR_PALETTE.map((hue) => {
|
|
99
|
-
const isSelected = hue.name === selected;
|
|
100
|
-
const gradient = `linear-gradient(to bottom, ${hue.shades.map((s) => s.hex).join(", ")})`;
|
|
101
|
-
return (
|
|
102
|
-
<button
|
|
103
|
-
key={hue.name}
|
|
104
|
-
type="button"
|
|
105
|
-
onClick={() => pickHue(hue.name)}
|
|
106
|
-
style={{ backgroundImage: gradient }}
|
|
107
|
-
className={cn(
|
|
108
|
-
"relative h-52 w-32 shrink-0 origin-bottom cursor-pointer overflow-hidden rounded-2xl shadow-[0_6px_16px_-10px_rgba(0,0,0,0.2)] ring-2 ring-offset-2 ring-offset-white transition-[transform,box-shadow] duration-300 ease-out will-change-transform hover:-translate-y-1 hover:scale-[1.03] focus:outline-none focus-visible:ring-zinc-900 dark:ring-offset-zinc-950 dark:focus-visible:ring-white",
|
|
109
|
-
isSelected ? "scale-[1.02] ring-zinc-900 dark:ring-white" : "scale-100 ring-transparent"
|
|
110
|
-
)}
|
|
111
|
-
>
|
|
112
|
-
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-black/30 px-3 py-2.5 backdrop-blur-md">
|
|
113
|
-
<span className="text-sm font-semibold text-white">{hue.name}</span>
|
|
114
|
-
{isSelected && (
|
|
115
|
-
<span className="flex h-4 w-4 items-center justify-center rounded-full bg-white text-zinc-900">
|
|
116
|
-
<Check className="h-2.5 w-2.5" strokeWidth={3} />
|
|
117
|
-
</span>
|
|
118
|
-
)}
|
|
119
|
-
</div>
|
|
120
|
-
</button>
|
|
121
|
-
);
|
|
122
|
-
})}
|
|
123
|
-
</div>
|
|
124
|
-
</DocsSection>
|
|
125
|
-
|
|
126
|
-
<motion.div
|
|
127
|
-
key={activeHue.name}
|
|
128
|
-
initial={{ opacity: 0, y: 6 }}
|
|
129
|
-
animate={{ opacity: 1, y: 0 }}
|
|
130
|
-
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
131
|
-
>
|
|
132
|
-
<DocsSection
|
|
133
|
-
title={`${activeHue.name} ${activeShade.step}`}
|
|
134
|
-
description="The selected shade, spotlighted — drag through the ramp to compare weights."
|
|
135
|
-
>
|
|
136
|
-
<div
|
|
137
|
-
className="relative flex min-h-[18rem] flex-col justify-between overflow-hidden rounded-[1.5rem] p-5 shadow-[0_24px_60px_-18px_rgba(0,0,0,0.3)] transition-colors duration-500 sm:min-h-[22rem] sm:rounded-[2rem] sm:p-10"
|
|
138
|
-
style={{ backgroundColor: activeShade.hex }}
|
|
139
|
-
>
|
|
140
|
-
<span
|
|
141
|
-
aria-hidden
|
|
142
|
-
className="pointer-events-none absolute -right-4 -top-8 select-none font-mono text-[8.5rem] font-bold leading-none sm:text-[10.5rem]"
|
|
143
|
-
style={{ color: textColor, opacity: 0.09 }}
|
|
144
|
-
>
|
|
145
|
-
{activeShade.hex.replace("#", "").toUpperCase()}
|
|
146
|
-
</span>
|
|
147
|
-
|
|
148
|
-
<div className="relative flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between" style={{ color: textColor }}>
|
|
149
|
-
<div className="min-w-0">
|
|
150
|
-
<p className="text-xs font-semibold uppercase tracking-[0.2em] opacity-60">
|
|
151
|
-
Palette / {activeHue.name}
|
|
152
|
-
</p>
|
|
153
|
-
<h3 className="mt-2 text-3xl font-bold tracking-tight sm:mt-3 sm:text-6xl">
|
|
154
|
-
{activeHue.name} {activeShade.step}
|
|
155
|
-
</h3>
|
|
156
|
-
</div>
|
|
157
|
-
<div
|
|
158
|
-
className="flex w-fit max-w-full shrink-0 items-center gap-0.5 self-start rounded-full p-1 backdrop-blur-md sm:gap-1"
|
|
159
|
-
style={{ backgroundColor: overlaySoft }}
|
|
160
|
-
>
|
|
161
|
-
{FORMATS.map((f) => (
|
|
162
|
-
<button
|
|
163
|
-
key={f.value}
|
|
164
|
-
type="button"
|
|
165
|
-
onClick={() => setFormat(f.value)}
|
|
166
|
-
className="relative rounded-full px-3 py-1.5 text-xs font-medium transition-colors"
|
|
167
|
-
style={{ color: textColor }}
|
|
168
|
-
>
|
|
169
|
-
{format === f.value && (
|
|
170
|
-
<motion.span
|
|
171
|
-
layoutId="hero-format-pill"
|
|
172
|
-
className="absolute inset-0 rounded-full backdrop-blur-md"
|
|
173
|
-
style={{ backgroundColor: overlayStrong }}
|
|
174
|
-
transition={{ type: "spring", stiffness: 500, damping: 35 }}
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
<span className="relative">{f.label}</span>
|
|
178
|
-
</button>
|
|
179
|
-
))}
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<div className="relative mt-8">
|
|
184
|
-
<div className="flex items-end justify-between gap-6" style={{ color: textColor }}>
|
|
185
|
-
<div>
|
|
186
|
-
<p className="break-all font-mono text-xl font-semibold sm:text-3xl">{activeValue}</p>
|
|
187
|
-
<p className="mt-1 text-xs font-medium opacity-70">
|
|
188
|
-
{contrastLabel(getContrastRatio(activeShade.hex, "#ffffff"))} on white ·{" "}
|
|
189
|
-
{contrastLabel(getContrastRatio(activeShade.hex, "#000000"))} on black
|
|
190
|
-
</p>
|
|
191
|
-
</div>
|
|
192
|
-
<Tooltip content={copied === activeValue ? "Copied!" : "Copy value"}>
|
|
193
|
-
<button
|
|
194
|
-
type="button"
|
|
195
|
-
onClick={() => copy(activeValue)}
|
|
196
|
-
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full backdrop-blur-md transition-transform hover:scale-105"
|
|
197
|
-
style={{ backgroundColor: overlaySoft, color: textColor }}
|
|
198
|
-
>
|
|
199
|
-
{copied === activeValue ? (
|
|
200
|
-
<Check className="h-4 w-4" />
|
|
201
|
-
) : (
|
|
202
|
-
<Copy className="h-4 w-4" />
|
|
203
|
-
)}
|
|
204
|
-
</button>
|
|
205
|
-
</Tooltip>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div className="mt-6 flex h-10 gap-1">
|
|
209
|
-
{activeHue.shades.map((shade) => {
|
|
210
|
-
const isActiveShade = shade.step === activeStep;
|
|
211
|
-
return (
|
|
212
|
-
<div key={shade.step} className="relative h-full flex-1">
|
|
213
|
-
<Tooltip
|
|
214
|
-
content={
|
|
215
|
-
<span className="flex items-center gap-1.5">
|
|
216
|
-
<span className="font-semibold">{shade.step}</span>
|
|
217
|
-
<span className="opacity-60">{shade.hex.toUpperCase()}</span>
|
|
218
|
-
</span>
|
|
219
|
-
}
|
|
220
|
-
>
|
|
221
|
-
<button
|
|
222
|
-
type="button"
|
|
223
|
-
onClick={() => setActiveStep(shade.step)}
|
|
224
|
-
className={cn(
|
|
225
|
-
"absolute inset-0 rounded-md transition-all duration-150 hover:-translate-y-1 focus:outline-none",
|
|
226
|
-
isActiveShade && "-translate-y-1.5"
|
|
227
|
-
)}
|
|
228
|
-
style={{
|
|
229
|
-
backgroundColor: shade.hex,
|
|
230
|
-
boxShadow: isActiveShade
|
|
231
|
-
? `0 8px 18px -6px ${shade.hex}, 0 0 0 2px ${overlayStrong}`
|
|
232
|
-
: undefined,
|
|
233
|
-
}}
|
|
234
|
-
/>
|
|
235
|
-
</Tooltip>
|
|
236
|
-
{isActiveShade && (
|
|
237
|
-
<motion.span
|
|
238
|
-
layoutId="ramp-active-dot"
|
|
239
|
-
className="absolute -bottom-3 left-1/2 h-1 w-1 -translate-x-1/2 rounded-full"
|
|
240
|
-
style={{ backgroundColor: textColor }}
|
|
241
|
-
/>
|
|
242
|
-
)}
|
|
243
|
-
</div>
|
|
244
|
-
);
|
|
245
|
-
})}
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</DocsSection>
|
|
250
|
-
|
|
251
|
-
<DocsSection
|
|
252
|
-
title="Readable on any surface"
|
|
253
|
-
description="How this shade reads as text, side by side against pure white and pure black."
|
|
254
|
-
>
|
|
255
|
-
<div className="grid gap-4 sm:grid-cols-2">
|
|
256
|
-
<ContrastPreviewCard hex={activeShade.hex} surface="light" />
|
|
257
|
-
<ContrastPreviewCard hex={activeShade.hex} surface="dark" />
|
|
258
|
-
</div>
|
|
259
|
-
</DocsSection>
|
|
260
|
-
|
|
261
|
-
<DocsSection
|
|
262
|
-
title={`Every shade in ${activeHue.name}`}
|
|
263
|
-
description="Formatted, ready to copy, in whichever format you're working with."
|
|
264
|
-
>
|
|
265
|
-
<div className="overflow-x-auto rounded-2xl border border-zinc-200 shadow-[0_2px_8px_rgba(0,0,0,0.04)] dark:border-zinc-800 dark:shadow-[0_2px_8px_rgba(0,0,0,0.2)]">
|
|
266
|
-
<div className="min-w-[32rem]">
|
|
267
|
-
<div className="flex items-center gap-4 border-b border-zinc-200 bg-zinc-50/70 px-4 py-2.5 text-xs font-medium text-zinc-500 dark:border-zinc-800 dark:bg-zinc-900/40 dark:text-zinc-400">
|
|
268
|
-
<span className="w-9" />
|
|
269
|
-
<span className="flex-1">Name</span>
|
|
270
|
-
<span className="w-36 shrink-0 sm:w-52">Value</span>
|
|
271
|
-
<span className="w-20 text-right sm:w-24">Contrast</span>
|
|
272
|
-
</div>
|
|
273
|
-
<div className="divide-y divide-zinc-200 dark:divide-zinc-800">
|
|
274
|
-
{activeHue.shades.map((shade) => {
|
|
275
|
-
const value = formatColor(shade.hex, format);
|
|
276
|
-
const isCopied = copied === value;
|
|
277
|
-
const isRowActive = shade.step === activeStep;
|
|
278
|
-
return (
|
|
279
|
-
<div
|
|
280
|
-
key={shade.step}
|
|
281
|
-
onClick={() => setActiveStep(shade.step)}
|
|
282
|
-
className={cn(
|
|
283
|
-
"group relative flex cursor-pointer items-center gap-4 pl-4 pr-4 py-3 transition-colors",
|
|
284
|
-
isRowActive ? "bg-zinc-50 dark:bg-zinc-900/60" : "hover:bg-zinc-50/70 dark:hover:bg-zinc-900/40"
|
|
285
|
-
)}
|
|
286
|
-
>
|
|
287
|
-
<span
|
|
288
|
-
aria-hidden
|
|
289
|
-
className={cn(
|
|
290
|
-
"absolute left-0 top-1/2 h-6 w-1 -translate-y-1/2 rounded-r-full transition-opacity duration-200",
|
|
291
|
-
isRowActive ? "opacity-100" : "opacity-0"
|
|
292
|
-
)}
|
|
293
|
-
style={{ backgroundColor: shade.hex }}
|
|
294
|
-
/>
|
|
295
|
-
<span
|
|
296
|
-
className="h-9 w-9 shrink-0 rounded-xl border border-black/5 shadow-sm transition-transform duration-200 group-hover:scale-105 dark:border-white/10"
|
|
297
|
-
style={{ backgroundColor: shade.hex }}
|
|
298
|
-
/>
|
|
299
|
-
<span className="flex-1 text-sm font-medium text-zinc-900 dark:text-white">
|
|
300
|
-
{activeHue.name}{" "}
|
|
301
|
-
<span className="text-zinc-400 dark:text-zinc-500">{shade.step}</span>
|
|
302
|
-
</span>
|
|
303
|
-
<div className="w-36 shrink-0 sm:w-52">
|
|
304
|
-
<Tooltip content={isCopied ? "Copied!" : "Copy to clipboard"}>
|
|
305
|
-
<button
|
|
306
|
-
type="button"
|
|
307
|
-
onClick={(e) => {
|
|
308
|
-
e.stopPropagation();
|
|
309
|
-
copy(value);
|
|
310
|
-
}}
|
|
311
|
-
className="group/copy flex w-full items-center gap-2 whitespace-nowrap rounded-md font-mono text-xs text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
|
|
312
|
-
>
|
|
313
|
-
<span className="min-w-0 flex-1 truncate text-left">{value}</span>
|
|
314
|
-
{isCopied ? (
|
|
315
|
-
<Check className="h-3 w-3 shrink-0 text-green-500" />
|
|
316
|
-
) : (
|
|
317
|
-
<Copy className="h-3 w-3 shrink-0 opacity-0 transition-opacity group-hover/copy:opacity-100" />
|
|
318
|
-
)}
|
|
319
|
-
</button>
|
|
320
|
-
</Tooltip>
|
|
321
|
-
</div>
|
|
322
|
-
<div
|
|
323
|
-
className="flex w-20 items-center justify-end gap-1.5 sm:w-24"
|
|
324
|
-
onClick={(e) => e.stopPropagation()}
|
|
325
|
-
>
|
|
326
|
-
<ContrastBadge hex={shade.hex} against="#ffffff" />
|
|
327
|
-
<ContrastBadge hex={shade.hex} against="#000000" />
|
|
328
|
-
</div>
|
|
329
|
-
</div>
|
|
330
|
-
);
|
|
331
|
-
})}
|
|
332
|
-
</div>
|
|
333
|
-
</div>
|
|
334
|
-
</div>
|
|
335
|
-
</DocsSection>
|
|
336
|
-
</motion.div>
|
|
337
|
-
</DocsPage>
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function ContrastPreviewCard({ hex, surface }: { hex: string; surface: "light" | "dark" }) {
|
|
342
|
-
const isLight = surface === "light";
|
|
343
|
-
const bg = isLight ? "#ffffff" : "#09090b";
|
|
344
|
-
const ratio = getContrastRatio(hex, bg);
|
|
345
|
-
const label = contrastLabel(ratio);
|
|
346
|
-
const pct = Math.min(100, (ratio / 21) * 100);
|
|
347
|
-
|
|
348
|
-
const labelColor = isLight
|
|
349
|
-
? label === "Fail"
|
|
350
|
-
? "text-red-600"
|
|
351
|
-
: label === "AA"
|
|
352
|
-
? "text-yellow-600"
|
|
353
|
-
: "text-green-600"
|
|
354
|
-
: label === "Fail"
|
|
355
|
-
? "text-red-400"
|
|
356
|
-
: label === "AA"
|
|
357
|
-
? "text-yellow-400"
|
|
358
|
-
: "text-green-400";
|
|
359
|
-
|
|
360
|
-
return (
|
|
361
|
-
<div
|
|
362
|
-
className={cn(
|
|
363
|
-
"rounded-2xl border p-6",
|
|
364
|
-
isLight ? "border-zinc-200 bg-white" : "border-zinc-800 bg-zinc-950"
|
|
365
|
-
)}
|
|
366
|
-
>
|
|
367
|
-
<p
|
|
368
|
-
className={cn(
|
|
369
|
-
"text-xs font-medium uppercase tracking-wide",
|
|
370
|
-
isLight ? "text-zinc-400" : "text-zinc-500"
|
|
371
|
-
)}
|
|
372
|
-
>
|
|
373
|
-
On {isLight ? "light" : "dark"} surface
|
|
374
|
-
</p>
|
|
375
|
-
<p className="mt-3 font-mono text-3xl font-bold" style={{ color: hex }}>
|
|
376
|
-
Aa Bb Cc
|
|
377
|
-
</p>
|
|
378
|
-
<p className="mt-1 text-lg font-semibold" style={{ color: hex }}>
|
|
379
|
-
The quick brown fox jumps.
|
|
380
|
-
</p>
|
|
381
|
-
|
|
382
|
-
<div className="mt-5">
|
|
383
|
-
<div
|
|
384
|
-
className={cn(
|
|
385
|
-
"relative h-1.5 w-full overflow-hidden rounded-full",
|
|
386
|
-
isLight ? "bg-zinc-100" : "bg-zinc-800"
|
|
387
|
-
)}
|
|
388
|
-
>
|
|
389
|
-
<div
|
|
390
|
-
className={cn(
|
|
391
|
-
"h-full rounded-full transition-all duration-300",
|
|
392
|
-
label === "Fail" ? "bg-red-500" : label === "AA" ? "bg-yellow-500" : "bg-green-500"
|
|
393
|
-
)}
|
|
394
|
-
style={{ width: `${pct}%` }}
|
|
395
|
-
/>
|
|
396
|
-
</div>
|
|
397
|
-
<div
|
|
398
|
-
className={cn(
|
|
399
|
-
"mt-1.5 flex items-center justify-between text-xs",
|
|
400
|
-
isLight ? "text-zinc-500" : "text-zinc-400"
|
|
401
|
-
)}
|
|
402
|
-
>
|
|
403
|
-
<span className="font-mono">{ratio.toFixed(2)}:1</span>
|
|
404
|
-
<span className={cn("font-semibold", labelColor)}>{label}</span>
|
|
405
|
-
</div>
|
|
406
|
-
</div>
|
|
407
|
-
</div>
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function ContrastBadge({ hex, against }: { hex: string; against: "#ffffff" | "#000000" }) {
|
|
412
|
-
const ratio = getContrastRatio(hex, against);
|
|
413
|
-
const label = contrastLabel(ratio);
|
|
414
|
-
const passes = label !== "Fail";
|
|
415
|
-
const surface = against === "#ffffff" ? "white" : "black";
|
|
416
|
-
|
|
417
|
-
return (
|
|
418
|
-
<Tooltip content={`${ratio.toFixed(2)}:1 · ${label} on ${surface}`}>
|
|
419
|
-
<span
|
|
420
|
-
className={cn(
|
|
421
|
-
"flex h-6 w-9 items-center justify-center rounded-md text-[11px] font-semibold ring-1 ring-inset",
|
|
422
|
-
passes ? "ring-green-500/30" : "ring-red-500/30"
|
|
423
|
-
)}
|
|
424
|
-
style={{ backgroundColor: against, color: hex }}
|
|
425
|
-
>
|
|
426
|
-
Aa
|
|
427
|
-
</span>
|
|
428
|
-
</Tooltip>
|
|
429
|
-
);
|
|
430
|
-
}
|