diff --git a/components/docs/interactive-examples/dot-background.tsx b/components/docs/interactive-examples/dot-background.tsx new file mode 100644 index 0000000..8d328d7 --- /dev/null +++ b/components/docs/interactive-examples/dot-background.tsx @@ -0,0 +1,121 @@ +import { DotBackground } from "@/registry/radix-nova/dot-background" + +const colorVariants = [ + { + variant: "default", + label: "Default", + description: "Uses the default surface and border tokens.", + }, + { + variant: "muted", + label: "Muted", + description: "A softer surface for secondary sections.", + }, + { + variant: "card", + label: "Card", + description: "Blends into card-based layouts.", + }, + { + variant: "accent", + label: "Accent", + description: "Adds stronger contrast for callouts.", + }, + { + variant: "secondary", + label: "Secondary", + description: "Works well inside layered UIs.", + }, +] as const + +const spacingVariants = [ + { + spacing: "dense", + label: "Dense", + description: "Tighter pattern for more texture.", + }, + { + spacing: "default", + label: "Default", + description: "Balanced density for general use.", + }, + { + spacing: "relaxed", + label: "Relaxed", + description: "Airier spacing for larger panels.", + }, + { + spacing: "loose", + label: "Loose", + description: "Sparse dots for subtle decoration.", + }, +] as const + +export function DotBackgroundInteractiveExample() { + return ( +
+
+ {colorVariants.map(({ variant, label, description }) => ( + +
+
+
{label}
+

{description}

+
+
{`variant="${variant}"`}
+
+
+ ))} +
+ +
+ {spacingVariants.map(({ spacing, label, description }) => ( + +
+
+
{label}
+

{description}

+
+
{`spacing="${spacing}"`}
+
+
+ ))} +
+ + +
+
+
Custom
+

+ Override surface color, dot color, spacing, and dot size with + `vars`. +

+
+
+ {`variant="custom" spacing="custom"`} +
+
+
+
+ ) +} diff --git a/components/docs/interactive-examples/index.ts b/components/docs/interactive-examples/index.ts index a1e873e..96f76f3 100644 --- a/components/docs/interactive-examples/index.ts +++ b/components/docs/interactive-examples/index.ts @@ -2,5 +2,6 @@ export { AlertDialogInteractiveExample } from "./alert-dialog" export { ButtonInteractiveExample } from "./button" export { ConfirmDialogInteractiveExample } from "./confirm-dialog" export { DialogInteractiveExample } from "./dialog" +export { DotBackgroundInteractiveExample } from "./dot-background" export { FilepickerDropzoneInteractiveExample } from "./filepicker-dropzone" export { LoadingButtonInteractiveExample } from "./loading-button" diff --git a/content/docs/components/dot-background.mdx b/content/docs/components/dot-background.mdx new file mode 100644 index 0000000..86eabfa --- /dev/null +++ b/content/docs/components/dot-background.mdx @@ -0,0 +1,103 @@ +--- +title: Dot Background +description: A dotted surface wrapper with semantic color and spacing variants plus CSS variable overrides. +--- + +import { DotBackgroundInteractiveExample } from "@/components/docs/interactive-examples"; + +## Install + +```bash +npx shadcn@latest add @c-ui/dot-background +``` + +Direct URL install: + +```bash +npx shadcn@latest add https://coneno.github.io/c-ui/r/radix-nova/dot-background.json +``` + +## Live Example + +
+ +
+ +## Usage + +```tsx +import { DotBackground } from "@/components/ui/dot-background" + +export function HeroSurface() { + return ( + +
+

Patterned container

+

+ Add subtle texture without leaving your design tokens. +

+
+
+ ) +} +``` + +## Color Variants + +```tsx + + + + + +``` + +## Spacing Variants + +```tsx + + + + +``` + +## Custom Overrides + +Use `variant="custom"` and `spacing="custom"` when you want the surface to be driven entirely by `vars`. + +```tsx + +
+

Custom surface

+

+ Tune the density and dot size with numbers or raw CSS lengths. +

+
+
+``` + +## Configuration + +| Prop | Type | Default | Notes | +| --- | --- | --- | --- | +| `variant` | `"default" \| "muted" \| "card" \| "accent" \| "secondary" \| "custom"` | `"default"` | Semantic background palette | +| `spacing` | `"dense" \| "default" \| "relaxed" \| "loose" \| "custom"` | `"default"` | Controls the dot grid spacing | +| `vars.backgroundColor` | `string` | `undefined` | Overrides the surface color | +| `vars.dotColor` | `string` | `undefined` | Overrides the dot color | +| `vars.spacing` | `number \| string` | `undefined` | Custom dot spacing; numbers become `px` | +| `vars.dotSize` | `number \| string` | `undefined` | Custom dot size; numbers become `px` | +| `asChild` | `boolean` | `false` | Render the pattern styles onto a child element via Radix `Slot` | +| `className` | `string` | `undefined` | Extends layout and border styling | +| `style` | `React.CSSProperties` | `undefined` | Supports the `--dot-background-*` CSS custom properties directly | + +All native `
` props are forwarded. diff --git a/registry.json b/registry.json index edf4ea6..0735a02 100644 --- a/registry.json +++ b/registry.json @@ -75,6 +75,20 @@ ], "style": "radix-nova" }, + { + "name": "dot-background", + "type": "registry:component", + "title": "Dot Background", + "description": "A dotted surface wrapper with semantic color and spacing variants.", + "files": [ + { + "path": "registry/radix-nova/dot-background.tsx", + "type": "registry:component", + "target": "components/ui/dot-background.tsx" + } + ], + "style": "radix-nova" + }, { "name": "loading-button", "type": "registry:component", diff --git a/registry/radix-nova/dot-background.tsx b/registry/radix-nova/dot-background.tsx new file mode 100644 index 0000000..c99060b --- /dev/null +++ b/registry/radix-nova/dot-background.tsx @@ -0,0 +1,161 @@ +import * as React from "react" +import { cva } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const dotBackgroundColorVariants = { + default: { + backgroundColor: "var(--background)", + dotColor: "var(--border)", + }, + muted: { + backgroundColor: "var(--muted)", + dotColor: "color-mix(in oklab, var(--muted-foreground) 24%, transparent)", + }, + card: { + backgroundColor: "var(--card)", + dotColor: "color-mix(in oklab, var(--border) 90%, transparent)", + }, + accent: { + backgroundColor: "var(--accent)", + dotColor: "color-mix(in oklab, var(--accent-foreground) 18%, transparent)", + }, + secondary: { + backgroundColor: "var(--secondary)", + dotColor: + "color-mix(in oklab, var(--secondary-foreground) 16%, transparent)", + }, + custom: { + backgroundColor: "var(--background)", + dotColor: "var(--border)", + }, +} as const satisfies Record< + string, + { backgroundColor: string; dotColor: string } +> + +const dotBackgroundSpacingVariants = { + dense: { spacing: "0.875rem" }, + default: { spacing: "1.25rem" }, + relaxed: { spacing: "1.75rem" }, + loose: { spacing: "2.25rem" }, + custom: { spacing: "1.25rem" }, +} as const satisfies Record + +type DotBackgroundVariant = keyof typeof dotBackgroundColorVariants +type DotBackgroundSpacingVariant = keyof typeof dotBackgroundSpacingVariants + +const defaultDotBackgroundVariant: DotBackgroundVariant = "default" +const defaultDotBackgroundSpacing: DotBackgroundSpacingVariant = "default" +const defaultDotBackgroundDotSize = "1px" + +function createVariantEntries>(variants: T) { + return Object.fromEntries( + Object.keys(variants).map((key) => [key, ""]) + ) as Record +} + +const dotBackgroundVariants = cva("relative isolate", { + variants: { + variant: createVariantEntries(dotBackgroundColorVariants), + spacing: createVariantEntries(dotBackgroundSpacingVariants), + }, + defaultVariants: { + variant: defaultDotBackgroundVariant, + spacing: defaultDotBackgroundSpacing, + }, +}) + +type DotBackgroundStyle = React.CSSProperties & { + "--dot-background-color"?: string + "--dot-background-dot-color"?: string + "--dot-background-spacing"?: string + "--dot-background-dot-size"?: string +} + +type DotBackgroundResolvedStyle = DotBackgroundStyle & { + "--dot-pattern-bg"?: string + "--dot-pattern-color"?: string + "--dot-pattern-spacing"?: string + "--dot-pattern-dot-size"?: string +} + +type DotBackgroundVars = { + backgroundColor?: string + dotColor?: string + spacing?: number | string + dotSize?: number | string +} + +type DotBackgroundProps = Omit, "style"> & { + asChild?: boolean + variant?: DotBackgroundVariant + spacing?: DotBackgroundSpacingVariant + vars?: DotBackgroundVars + style?: DotBackgroundStyle +} + +function resolveCssLength(value: number | string | undefined) { + if (typeof value === "number") { + return `${value}px` + } + + return value +} + +function DotBackground({ + asChild = false, + variant = defaultDotBackgroundVariant, + spacing = defaultDotBackgroundSpacing, + vars, + className, + style, + ...props +}: DotBackgroundProps) { + const Comp = asChild ? Slot.Root : "div" + const colorVariant = dotBackgroundColorVariants[variant] + const spacingVariant = dotBackgroundSpacingVariants[spacing] + + const resolvedStyle: DotBackgroundResolvedStyle = { + "--dot-background-color": vars?.backgroundColor, + "--dot-background-dot-color": vars?.dotColor, + "--dot-background-spacing": resolveCssLength(vars?.spacing), + "--dot-background-dot-size": resolveCssLength(vars?.dotSize), + "--dot-pattern-bg": `var(--dot-background-color, ${colorVariant.backgroundColor})`, + "--dot-pattern-color": `var(--dot-background-dot-color, ${colorVariant.dotColor})`, + "--dot-pattern-spacing": `var(--dot-background-spacing, ${spacingVariant.spacing})`, + "--dot-pattern-dot-size": `var(--dot-background-dot-size, ${defaultDotBackgroundDotSize})`, + backgroundColor: "var(--dot-pattern-bg)", + backgroundImage: + "radial-gradient(circle, var(--dot-pattern-color) var(--dot-pattern-dot-size), transparent calc(var(--dot-pattern-dot-size) + 0.5px))", + backgroundSize: "var(--dot-pattern-spacing) var(--dot-pattern-spacing)", + backgroundPosition: "0 0", + ...style, + } + + return ( + + ) +} + +export { + DotBackground, + dotBackgroundColorVariants, + dotBackgroundSpacingVariants, + dotBackgroundVariants, +} +export type { + DotBackgroundProps, + DotBackgroundSpacingVariant, + DotBackgroundStyle, + DotBackgroundVariant, + DotBackgroundVars, +}