[]previous and next page

Input and Card

Components that wrap one element need no primitive.

For agentsUpdated

Components that wrap one element need no primitive. mergeElementProps replaces cn(base, className) and also merges handlers, style and ref, so {...props} forwarding is complete.

src/ui/input.tsxtsx
import { useEffect } from "@shadow-js/core";
import { mergeElementProps } from "@shadow-js/primitives";
import type { JSX } from "@shadow-js/web";

export function Input(props: JSX.IntrinsicElements["input"]) {
  let element: HTMLInputElement | undefined;
  // A `value` that arrives through a spread is written as an attribute, which stops following the
  // field once the user has typed. Keep the live property in step with a controlled value.
  useEffect(() => {
    const value = props.value;
    if (element === undefined || value === undefined || value === null) return;
    if (element.value !== String(value)) element.value = String(value);
  });
  return (
    <input
      {...mergeElementProps(
        {
          class:
            "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
        },
        props,
        {
          ref: (node: HTMLInputElement) => {
            element = node;
          },
        },
      )}
    />
  );
}

export function InputDemo() {
  return <Input aria-label="Email" type="email" placeholder="you@example.com" />;
}
src/ui/card.tsxtsx
import { mergeElementProps } from "@shadow-js/primitives";
import type { JSX } from "@shadow-js/web";

export function Card(props: JSX.IntrinsicElements["div"]) {
  return <div {...mergeElementProps({ class: "rounded-xl border bg-card text-card-foreground shadow" }, props)} />;
}

export function CardHeader(props: JSX.IntrinsicElements["div"]) {
  return <div {...mergeElementProps({ class: "flex flex-col space-y-1.5 p-6" }, props)} />;
}

export function CardTitle(props: JSX.IntrinsicElements["h3"]) {
  return <h3 {...mergeElementProps({ class: "font-semibold leading-none tracking-tight" }, props)} />;
}

export function CardDescription(props: JSX.IntrinsicElements["p"]) {
  return <p {...mergeElementProps({ class: "text-sm text-muted-foreground" }, props)} />;
}

export function CardContent(props: JSX.IntrinsicElements["div"]) {
  return <div {...mergeElementProps({ class: "p-6 pt-0" }, props)} />;
}

export function CardFooter(props: JSX.IntrinsicElements["div"]) {
  return <div {...mergeElementProps({ class: "flex items-center p-6 pt-0" }, props)} />;
}

export function CardDemo() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Account</CardTitle>
        <CardDescription>Manage your account.</CardDescription>
      </CardHeader>
      <CardContent>Hello</CardContent>
    </Card>
  );
}