Registry package scaffold
Adds the shadcn-style copy-paste layer as a new monorepo package. Includes theme token CSS custom properties (light/dark), a cn() class-merging utility with tests, and a reference Dialog styled component wrapping the headless pettyui/dialog primitive with Tailwind classes.
This commit is contained in:
parent
56f06c961d
commit
b4ec99fe4a
19
packages/registry/package.json
Normal file
19
packages/registry/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "pettyui-registry",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "PettyUI styled component registry — shadcn model for SolidJS",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pettyui": "workspace:*",
|
||||
"solid-js": "^1.9.12",
|
||||
"vitest": "^4.1.2"
|
||||
}
|
||||
}
|
||||
40
packages/registry/src/components/dialog.tsx
Normal file
40
packages/registry/src/components/dialog.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { Dialog as DialogPrimitive } from "pettyui/dialog";
|
||||
import { cn } from "../utils";
|
||||
import type { JSX } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
const Dialog = DialogPrimitive;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
const DialogTitle = DialogPrimitive.Title;
|
||||
const DialogDescription = DialogPrimitive.Description;
|
||||
|
||||
/** Renders a dimmed backdrop behind the dialog panel. */
|
||||
function DialogOverlay(props: JSX.HTMLAttributes<HTMLDivElement>): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
class={cn("fixed inset-0 z-50 bg-black/80", local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** Pre-composed dialog panel: wraps Content inside Portal with Overlay included. */
|
||||
function DialogContent(props: JSX.HTMLAttributes<HTMLDivElement> & { children?: JSX.Element }): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["class", "children"]);
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
class={cn("fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border bg-background p-6 shadow-lg", local.class)}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
export { Dialog, DialogTrigger, DialogContent, DialogOverlay, DialogPortal, DialogClose, DialogTitle, DialogDescription };
|
||||
44
packages/registry/src/tokens.css
Normal file
44
packages/registry/src/tokens.css
Normal file
@ -0,0 +1,44 @@
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
7
packages/registry/src/utils.ts
Normal file
7
packages/registry/src/utils.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Merges class name strings, filtering out falsy values (undefined, null, false).
|
||||
* Drop-in replacement for clsx for simple conditional class composition.
|
||||
*/
|
||||
export function cn(...inputs: (string | undefined | null | false)[]): string {
|
||||
return inputs.filter(Boolean).join(" ");
|
||||
}
|
||||
14
packages/registry/tests/utils.test.ts
Normal file
14
packages/registry/tests/utils.test.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { cn } from "../src/utils";
|
||||
|
||||
describe("cn utility", () => {
|
||||
it("joins class names", () => {
|
||||
expect(cn("foo", "bar")).toBe("foo bar");
|
||||
});
|
||||
it("filters falsy values", () => {
|
||||
expect(cn("foo", undefined, null, false, "bar")).toBe("foo bar");
|
||||
});
|
||||
it("returns empty string for no classes", () => {
|
||||
expect(cn()).toBe("");
|
||||
});
|
||||
});
|
||||
11
packages/registry/tsconfig.json
Normal file
11
packages/registry/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user