From 02ddbe5b6f2c8fe5601ac4fe87ce3d7208849966 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Fri, 5 Dec 2025 11:58:25 +0100 Subject: [PATCH 1/7] feat(ui): add ButtonLink component --- apps/ui/app/docs/[[...slug]]/page.tsx | 8 +- apps/ui/components/main-nav.tsx | 6 +- apps/ui/content/docs/(root)/index.mdx | 2 +- .../content/docs/components/button-link.mdx | 88 +++++++++++++++++++ apps/ui/content/docs/components/button.mdx | 32 +++++-- .../content/docs/components/input-group.mdx | 2 +- apps/ui/content/docs/components/meta.json | 1 + apps/ui/lib/docs.ts | 1 + apps/ui/package.json | 2 +- apps/ui/public/llms.txt | 3 +- apps/ui/public/r/button-link.json | 15 ++++ apps/ui/public/r/button.json | 2 +- apps/ui/public/r/p-button-17.json | 4 +- apps/ui/public/r/p-toolbar-1.json | 2 +- apps/ui/public/r/pagination.json | 2 +- apps/ui/public/r/registry.json | 16 +++- apps/ui/public/r/ui.json | 1 + apps/ui/registry.json | 16 +++- apps/ui/registry/__index__.tsx | 20 ++++- .../default/particles/p-button-17.tsx | 9 +- .../default/particles/p-toolbar-1.tsx | 1 - apps/ui/registry/default/ui/button-link.tsx | 35 ++++++++ apps/ui/registry/default/ui/button.tsx | 39 ++++---- apps/ui/registry/default/ui/pagination.tsx | 2 + apps/ui/registry/registry-particles.ts | 2 +- apps/ui/registry/registry-ui.ts | 12 +++ bun.lock | 6 +- packages/ui/src/components/github-link.tsx | 4 +- packages/ui/src/components/site-cta.tsx | 13 +-- packages/ui/src/styles/globals.css | 2 +- packages/ui/src/ui/button-link.tsx | 35 ++++++++ packages/ui/src/ui/button.tsx | 39 ++++---- packages/ui/src/ui/pagination.tsx | 2 + 33 files changed, 338 insertions(+), 86 deletions(-) create mode 100644 apps/ui/content/docs/components/button-link.mdx create mode 100644 apps/ui/public/r/button-link.json create mode 100644 apps/ui/registry/default/ui/button-link.tsx create mode 100644 packages/ui/src/ui/button-link.tsx diff --git a/apps/ui/app/docs/[[...slug]]/page.tsx b/apps/ui/app/docs/[[...slug]]/page.tsx index 625225a87..0765de12f 100644 --- a/apps/ui/app/docs/[[...slug]]/page.tsx +++ b/apps/ui/app/docs/[[...slug]]/page.tsx @@ -12,7 +12,7 @@ import { DocsTableOfContents } from "@/components/docs-toc"; import { SiteFooter } from "@/components/site-footer"; import { source } from "@/lib/source"; import { mdxComponents } from "@/mdx-components"; -import { Button } from "@/registry/default/ui/button"; +import { ButtonLink } from "@/registry/default/ui/button-link"; export const revalidate = false; export const dynamic = "force-static"; @@ -82,7 +82,7 @@ export default async function Page(props: {
{links?.doc && ( - + ))} ); diff --git a/apps/ui/content/docs/(root)/index.mdx b/apps/ui/content/docs/(root)/index.mdx index 940fc53b4..3d79d4152 100644 --- a/apps/ui/content/docs/(root)/index.mdx +++ b/apps/ui/content/docs/(root)/index.mdx @@ -9,7 +9,7 @@ We think Base UI is the best foundation for modern web applications. We've taken This is the component library we'll be progressively adopting for [cal.com](https://cal.com). We're building it in the open for anyone who wants to create beautiful, reliable user interfaces. - + Early Access diff --git a/apps/ui/content/docs/components/button-link.mdx b/apps/ui/content/docs/components/button-link.mdx new file mode 100644 index 000000000..c567b2edd --- /dev/null +++ b/apps/ui/content/docs/components/button-link.mdx @@ -0,0 +1,88 @@ +--- +title: ButtonLink +description: A link component that looks like a button, designed for navigation. +--- + + + +## Installation + + + + + CLI + Manual + + + +```bash +npx shadcn@latest add @coss/button-link +``` + + + + + + + +Install the following dependencies: + +```bash +npm install @base-ui-components/react +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { ButtonLink } from "@/components/ui/button-link" +``` + +```tsx +About +``` + +## Overview + +`ButtonLink` renders links that look like buttons. It uses the same visual styling as `Button` but renders an `` element by default, ensuring correct semantics and behavior for navigation. + +Buttons and links have fundamentally different behaviors: +- `` triggers via `Enter`, ` + @@ -205,9 +217,17 @@ We've added a new `destructive-outline` variant for better UX patterns: ```tsx title="coss ui" -// [!code word:render] - +// [!code word:render:3] +// [!code word:nativeButton:3] +// For custom components: + + +// For links (renders ): +import { ButtonLink } from "@/components/ui/button-link" +Login + +// For Next.js Link: +import { ButtonLink } from "@/components/ui/button-link" +}>Login ``` - + diff --git a/apps/ui/content/docs/components/input-group.mdx b/apps/ui/content/docs/components/input-group.mdx index 68b716373..189c988fe 100644 --- a/apps/ui/content/docs/components/input-group.mdx +++ b/apps/ui/content/docs/components/input-group.mdx @@ -151,7 +151,7 @@ A container for addons like icons, text, buttons, and other elements. Can be pos | `className` | `string` | | Additional CSS classes to apply to the component | | `...props` | `React.ComponentProps<'div'>` | | All standard div attributes are supported | - + For proper focus navigation, the `InputGroupAddon` component should be placed diff --git a/apps/ui/content/docs/components/meta.json b/apps/ui/content/docs/components/meta.json index f9728a630..42d6dc960 100644 --- a/apps/ui/content/docs/components/meta.json +++ b/apps/ui/content/docs/components/meta.json @@ -8,6 +8,7 @@ "badge", "breadcrumb", "button", + "button-link", "card", "checkbox", "checkbox-group", diff --git a/apps/ui/lib/docs.ts b/apps/ui/lib/docs.ts index 818b66209..bc6c38c73 100644 --- a/apps/ui/lib/docs.ts +++ b/apps/ui/lib/docs.ts @@ -1,3 +1,4 @@ export const PAGES_NEW = [ // "/docs/components/{component-name}", + "/docs/components/button-link", ]; diff --git a/apps/ui/package.json b/apps/ui/package.json index 9ccf00fea..1ccfe0a9c 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@base-ui-components/react": "^1.0.0-beta.7", + "@base-ui-components/react": "^1.0.0-rc.0", "@coss/ui": "workspace:*", "@hugeicons/core-free-icons": "^2.0.0", "@hugeicons/react": "^1.1.1", diff --git a/apps/ui/public/llms.txt b/apps/ui/public/llms.txt index b9b24cef0..559fd434a 100644 --- a/apps/ui/public/llms.txt +++ b/apps/ui/public/llms.txt @@ -17,7 +17,8 @@ - [Avatar](https://coss.com/ui/docs/components/avatar.md): A visual representation of a user or entity. - [Badge](https://coss.com/ui/docs/components/badge.md): A small status indicator or label component. - [Breadcrumb](https://coss.com/ui/docs/components/breadcrumb.md): Displays the path to the current resource using a hierarchy of links. -- [Button](https://coss.com/ui/docs/components/button.md): A button or a component that looks like a button. +- [Button](https://coss.com/ui/docs/components/button.md): A button component that can be rendered as another tag or focusable when disabled. +- [Button Link](https://coss.com/ui/docs/components/button-link.md): A link component that looks like a button. - [Card](https://coss.com/ui/docs/components/card.md): A content container for grouping related information. - [Checkbox](https://coss.com/ui/docs/components/checkbox.md): A binary toggle input for selecting one or multiple options. - [Checkbox Group](https://coss.com/ui/docs/components/checkbox-group.md): A collection of related checkboxes with group-level control. diff --git a/apps/ui/public/r/button-link.json b/apps/ui/public/r/button-link.json new file mode 100644 index 000000000..49d9f42e7 --- /dev/null +++ b/apps/ui/public/r/button-link.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "button-link", + "type": "registry:ui", + "dependencies": [ + "@base-ui-components/react" + ], + "files": [ + { + "path": "registry/default/ui/button-link.tsx", + "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui-components/react/merge-props\";\nimport { useRender } from \"@base-ui-components/react/use-render\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport { buttonVariants } from \"./button\";\n\ninterface ButtonLinkProps extends useRender.ComponentProps<\"a\"> {\n variant?: VariantProps[\"variant\"];\n size?: VariantProps[\"size\"];\n}\n\nfunction ButtonLink({\n className,\n variant,\n size,\n render,\n ...props\n}: ButtonLinkProps) {\n const defaultProps = {\n className: cn(buttonVariants({ className, size, variant })),\n \"data-slot\": \"button\",\n };\n\n return useRender({\n defaultTagName: \"a\",\n props: mergeProps<\"a\">(defaultProps, props),\n render,\n });\n}\n\nexport { ButtonLink };\n", + "type": "registry:ui" + } + ] +} \ No newline at end of file diff --git a/apps/ui/public/r/button.json b/apps/ui/public/r/button.json index 9a8264ec8..1b0375cee 100644 --- a/apps/ui/public/r/button.json +++ b/apps/ui/public/r/button.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/ui/button.tsx", - "content": "import { mergeProps } from \"@base-ui-components/react/merge-props\";\nimport { useRender } from \"@base-ui-components/react/use-render\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border bg-clip-padding font-medium text-sm outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default:\n \"min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7\",\n \"icon-xl\": \"size-10 [&_svg:not([class*='size-'])]:size-4.5\",\n \"icon-xs\":\n \"size-6 rounded-md before:rounded-[calc(var(--radius-md)-1px)]\",\n lg: \"min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]\",\n sm: \"min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]\",\n xl: \"min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5\",\n xs: \"min-h-6 gap-1 rounded-md px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n destructive:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n \"destructive-outline\":\n \"border-border bg-transparent text-destructive-foreground shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:border-destructive/32 [&:is(:hover,[data-pressed])]:bg-destructive/4\",\n ghost: \"border-transparent hover:bg-accent data-pressed:bg-accent\",\n link: \"border-transparent underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:bg-accent/50 dark:[&:is(:hover,[data-pressed])]:bg-input/64\",\n secondary:\n \"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90\",\n },\n },\n },\n);\n\ninterface ButtonProps extends useRender.ComponentProps<\"button\"> {\n variant?: VariantProps[\"variant\"];\n size?: VariantProps[\"size\"];\n}\n\nfunction Button({ className, variant, size, render, ...props }: ButtonProps) {\n const typeValue: React.ButtonHTMLAttributes[\"type\"] =\n render ? undefined : \"button\";\n\n const defaultProps = {\n className: cn(buttonVariants({ className, size, variant })),\n \"data-slot\": \"button\",\n type: typeValue,\n };\n\n return useRender({\n defaultTagName: \"button\",\n props: mergeProps<\"button\">(defaultProps, props),\n render,\n });\n}\n\nexport { Button, buttonVariants };\n", + "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui-components/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border bg-clip-padding font-medium text-sm outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default:\n \"min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7\",\n \"icon-xl\": \"size-10 [&_svg:not([class*='size-'])]:size-4.5\",\n \"icon-xs\":\n \"size-6 rounded-md before:rounded-[calc(var(--radius-md)-1px)]\",\n lg: \"min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]\",\n sm: \"min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]\",\n xl: \"min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5\",\n xs: \"min-h-6 gap-1 rounded-md px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n destructive:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n \"destructive-outline\":\n \"border-border bg-transparent text-destructive-foreground shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:border-destructive/32 [&:is(:hover,[data-pressed])]:bg-destructive/4\",\n ghost: \"border-transparent hover:bg-accent data-pressed:bg-accent\",\n link: \"border-transparent underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:bg-accent/50 dark:[&:is(:hover,[data-pressed])]:bg-input/64\",\n secondary:\n \"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90\",\n },\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n ...props\n}: ButtonPrimitive.Props & VariantProps) {\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n", "type": "registry:ui" } ], diff --git a/apps/ui/public/r/p-button-17.json b/apps/ui/public/r/p-button-17.json index 3df1b90b1..5dd03defe 100644 --- a/apps/ui/public/r/p-button-17.json +++ b/apps/ui/public/r/p-button-17.json @@ -4,12 +4,12 @@ "type": "registry:block", "description": "Link rendered as button", "registryDependencies": [ - "@coss/button" + "@coss/button-link" ], "files": [ { "path": "registry/default/particles/p-button-17.tsx", - "content": "import Link from \"next/link\";\n\nimport { Button } from \"@/registry/default/ui/button\";\n\nexport default function Particle() {\n return ;\n}\n", + "content": "import Link from \"next/link\";\n\nimport { ButtonLink } from \"@/registry/default/ui/button-link\";\n\nexport default function Particle() {\n return (\n
\n Link\n }>Next.js Link\n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-toolbar-1.json b/apps/ui/public/r/p-toolbar-1.json index f856d4ad7..04ee51128 100644 --- a/apps/ui/public/r/p-toolbar-1.json +++ b/apps/ui/public/r/p-toolbar-1.json @@ -16,7 +16,7 @@ "files": [ { "path": "registry/default/particles/p-toolbar-1.tsx", - "content": "import {\n AlignCenterIcon,\n AlignLeftIcon,\n AlignRightIcon,\n DollarSignIcon,\n PercentIcon,\n} from \"lucide-react\";\n\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n Select,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\nimport { Toggle, ToggleGroup } from \"@/registry/default/ui/toggle-group\";\nimport {\n Toolbar,\n ToolbarButton,\n ToolbarGroup,\n ToolbarSeparator,\n} from \"@/registry/default/ui/toolbar\";\nimport {\n Tooltip,\n TooltipPopup,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nconst items = [\n { label: \"Helvetica\", value: \"helvetica\" },\n { label: \"Arial\", value: \"arial\" },\n { label: \"Times New Roman\", value: \"times-new-roman\" },\n];\n\nexport default function Particle() {\n return (\n \n \n \n \n }\n >\n \n \n }\n />\n Align left\n \n \n }\n >\n \n \n }\n />\n Align center\n \n \n }\n >\n \n \n }\n />\n Align right\n \n \n \n \n \n }\n >\n \n \n }\n />\n Format as currency\n \n \n }\n >\n \n \n }\n />\n Format as percent\n \n \n \n \n \n \n \n \n }>Save\n \n \n \n );\n}\n", + "content": "import {\n AlignCenterIcon,\n AlignLeftIcon,\n AlignRightIcon,\n DollarSignIcon,\n PercentIcon,\n} from \"lucide-react\";\n\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n Select,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\nimport { Toggle, ToggleGroup } from \"@/registry/default/ui/toggle-group\";\nimport {\n Toolbar,\n ToolbarButton,\n ToolbarGroup,\n ToolbarSeparator,\n} from \"@/registry/default/ui/toolbar\";\nimport {\n Tooltip,\n TooltipPopup,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nconst items = [\n { label: \"Helvetica\", value: \"helvetica\" },\n { label: \"Arial\", value: \"arial\" },\n { label: \"Times New Roman\", value: \"times-new-roman\" },\n];\n\nexport default function Particle() {\n return (\n \n \n \n \n }\n >\n \n \n }\n />\n Align left\n \n \n }\n >\n \n \n }\n />\n Align center\n \n \n }\n >\n \n \n }\n />\n Align right\n \n \n \n \n \n }\n >\n \n \n }\n />\n Format as currency\n \n \n }\n >\n \n \n }\n />\n Format as percent\n \n \n \n \n \n \n \n \n }>Save\n \n \n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/pagination.json b/apps/ui/public/r/pagination.json index 34e048af9..8f811ecd4 100644 --- a/apps/ui/public/r/pagination.json +++ b/apps/ui/public/r/pagination.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/ui/pagination.tsx", - "content": "import { mergeProps } from \"@base-ui-components/react/merge-props\";\nimport { useRender } from \"@base-ui-components/react/use-render\";\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n MoreHorizontalIcon,\n} from \"lucide-react\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type Button, buttonVariants } from \"@/registry/default/ui/button\";\n\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n return (\n \n );\n}\n\nfunction PaginationContent({\n className,\n ...props\n}: React.ComponentProps<\"ul\">) {\n return (\n \n );\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n return
  • ;\n}\n\ntype PaginationLinkProps = {\n isActive?: boolean;\n size?: React.ComponentProps[\"size\"];\n} & useRender.ComponentProps<\"a\">;\n\nfunction PaginationLink({\n className,\n isActive,\n size = \"icon\",\n render,\n ...props\n}: PaginationLinkProps) {\n const defaultProps = {\n \"aria-current\": isActive ? (\"page\" as const) : undefined,\n className: render\n ? className\n : cn(\n buttonVariants({\n size,\n variant: isActive ? \"outline\" : \"ghost\",\n }),\n className,\n ),\n \"data-active\": isActive,\n \"data-slot\": \"pagination-link\",\n };\n\n return useRender({\n defaultTagName: \"a\",\n props: mergeProps<\"a\">(defaultProps, props),\n render,\n });\n}\n\nfunction PaginationPrevious({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n \n Previous\n \n );\n}\n\nfunction PaginationNext({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n Next\n \n \n );\n}\n\nfunction PaginationEllipsis({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n \n \n More pages\n \n );\n}\n\nexport {\n Pagination,\n PaginationContent,\n PaginationLink,\n PaginationItem,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n};\n", + "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui-components/react/merge-props\";\nimport { useRender } from \"@base-ui-components/react/use-render\";\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n MoreHorizontalIcon,\n} from \"lucide-react\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type Button, buttonVariants } from \"@/registry/default/ui/button\";\n\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n return (\n \n );\n}\n\nfunction PaginationContent({\n className,\n ...props\n}: React.ComponentProps<\"ul\">) {\n return (\n \n );\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n return
  • ;\n}\n\ntype PaginationLinkProps = {\n isActive?: boolean;\n size?: React.ComponentProps[\"size\"];\n} & useRender.ComponentProps<\"a\">;\n\nfunction PaginationLink({\n className,\n isActive,\n size = \"icon\",\n render,\n ...props\n}: PaginationLinkProps) {\n const defaultProps = {\n \"aria-current\": isActive ? (\"page\" as const) : undefined,\n className: render\n ? className\n : cn(\n buttonVariants({\n size,\n variant: isActive ? \"outline\" : \"ghost\",\n }),\n className,\n ),\n \"data-active\": isActive,\n \"data-slot\": \"pagination-link\",\n };\n\n return useRender({\n defaultTagName: \"a\",\n props: mergeProps<\"a\">(defaultProps, props),\n render,\n });\n}\n\nfunction PaginationPrevious({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n \n Previous\n \n );\n}\n\nfunction PaginationNext({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n Next\n \n \n );\n}\n\nfunction PaginationEllipsis({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n \n \n More pages\n \n );\n}\n\nexport {\n Pagination,\n PaginationContent,\n PaginationLink,\n PaginationItem,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n};\n", "type": "registry:ui" } ] diff --git a/apps/ui/public/r/registry.json b/apps/ui/public/r/registry.json index d3ac6fe36..e68a8887c 100644 --- a/apps/ui/public/r/registry.json +++ b/apps/ui/public/r/registry.json @@ -12,6 +12,7 @@ "@coss/badge", "@coss/breadcrumb", "@coss/button", + "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", @@ -209,6 +210,19 @@ "name": "button", "type": "registry:ui" }, + { + "dependencies": [ + "@base-ui-components/react" + ], + "files": [ + { + "path": "registry/default/ui/button-link.tsx", + "type": "registry:ui" + } + ], + "name": "button-link", + "type": "registry:ui" + }, { "dependencies": [], "files": [ @@ -1990,7 +2004,7 @@ ], "name": "p-button-17", "registryDependencies": [ - "@coss/button" + "@coss/button-link" ], "type": "registry:block" }, diff --git a/apps/ui/public/r/ui.json b/apps/ui/public/r/ui.json index e78d0cce0..78db4bfc1 100644 --- a/apps/ui/public/r/ui.json +++ b/apps/ui/public/r/ui.json @@ -11,6 +11,7 @@ "@coss/badge", "@coss/breadcrumb", "@coss/button", + "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", diff --git a/apps/ui/registry.json b/apps/ui/registry.json index d3ac6fe36..e68a8887c 100644 --- a/apps/ui/registry.json +++ b/apps/ui/registry.json @@ -12,6 +12,7 @@ "@coss/badge", "@coss/breadcrumb", "@coss/button", + "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", @@ -209,6 +210,19 @@ "name": "button", "type": "registry:ui" }, + { + "dependencies": [ + "@base-ui-components/react" + ], + "files": [ + { + "path": "registry/default/ui/button-link.tsx", + "type": "registry:ui" + } + ], + "name": "button-link", + "type": "registry:ui" + }, { "dependencies": [], "files": [ @@ -1990,7 +2004,7 @@ ], "name": "p-button-17", "registryDependencies": [ - "@coss/button" + "@coss/button-link" ], "type": "registry:block" }, diff --git a/apps/ui/registry/__index__.tsx b/apps/ui/registry/__index__.tsx index 19ef5def6..2ec74d6e5 100644 --- a/apps/ui/registry/__index__.tsx +++ b/apps/ui/registry/__index__.tsx @@ -151,6 +151,24 @@ export const Index: Record = { categories: undefined, meta: undefined, }, + "button-link": { + name: "button-link", + description: "", + type: "registry:ui", + registryDependencies: undefined, + files: [{ + path: "registry/default/ui/button-link.tsx", + type: "registry:ui", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/ui/button-link.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: undefined, + meta: undefined, + }, "card": { name: "card", description: "", @@ -2009,7 +2027,7 @@ export const Index: Record = { name: "p-button-17", description: "Link rendered as button", type: "registry:block", - registryDependencies: ["@coss/button"], + registryDependencies: ["@coss/button-link"], files: [{ path: "registry/default/particles/p-button-17.tsx", type: "registry:block", diff --git a/apps/ui/registry/default/particles/p-button-17.tsx b/apps/ui/registry/default/particles/p-button-17.tsx index 6901bbd01..d66d71bae 100644 --- a/apps/ui/registry/default/particles/p-button-17.tsx +++ b/apps/ui/registry/default/particles/p-button-17.tsx @@ -1,7 +1,12 @@ import Link from "next/link"; -import { Button } from "@/registry/default/ui/button"; +import { ButtonLink } from "@/registry/default/ui/button-link"; export default function Particle() { - return ; + return ( +
    + Link + }>Next.js Link +
    + ); } diff --git a/apps/ui/registry/default/particles/p-toolbar-1.tsx b/apps/ui/registry/default/particles/p-toolbar-1.tsx index d7a47c5f9..7bf8af1da 100644 --- a/apps/ui/registry/default/particles/p-toolbar-1.tsx +++ b/apps/ui/registry/default/particles/p-toolbar-1.tsx @@ -115,7 +115,6 @@ export default function Particle() { diff --git a/apps/ui/registry/default/ui/button-link.tsx b/apps/ui/registry/default/ui/button-link.tsx new file mode 100644 index 000000000..1670c4752 --- /dev/null +++ b/apps/ui/registry/default/ui/button-link.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { mergeProps } from "@base-ui-components/react/merge-props"; +import { useRender } from "@base-ui-components/react/use-render"; +import type { VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +import { buttonVariants } from "./button"; + +interface ButtonLinkProps extends useRender.ComponentProps<"a"> { + variant?: VariantProps["variant"]; + size?: VariantProps["size"]; +} + +function ButtonLink({ + className, + variant, + size, + render, + ...props +}: ButtonLinkProps) { + const defaultProps = { + className: cn(buttonVariants({ className, size, variant })), + "data-slot": "button", + }; + + return useRender({ + defaultTagName: "a", + props: mergeProps<"a">(defaultProps, props), + render, + }); +} + +export { ButtonLink }; diff --git a/apps/ui/registry/default/ui/button.tsx b/apps/ui/registry/default/ui/button.tsx index a93d1d677..6786baa33 100644 --- a/apps/ui/registry/default/ui/button.tsx +++ b/apps/ui/registry/default/ui/button.tsx @@ -1,7 +1,7 @@ -import { mergeProps } from "@base-ui-components/react/merge-props"; -import { useRender } from "@base-ui-components/react/use-render"; +"use client"; + +import { Button as ButtonPrimitive } from "@base-ui-components/react/button"; import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; import { cn } from "@/lib/utils"; @@ -45,26 +45,19 @@ const buttonVariants = cva( }, ); -interface ButtonProps extends useRender.ComponentProps<"button"> { - variant?: VariantProps["variant"]; - size?: VariantProps["size"]; -} - -function Button({ className, variant, size, render, ...props }: ButtonProps) { - const typeValue: React.ButtonHTMLAttributes["type"] = - render ? undefined : "button"; - - const defaultProps = { - className: cn(buttonVariants({ className, size, variant })), - "data-slot": "button", - type: typeValue, - }; - - return useRender({ - defaultTagName: "button", - props: mergeProps<"button">(defaultProps, props), - render, - }); +function Button({ + className, + variant, + size, + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ( + + ); } export { Button, buttonVariants }; diff --git a/apps/ui/registry/default/ui/pagination.tsx b/apps/ui/registry/default/ui/pagination.tsx index 7c09e6a9c..48e7e84d7 100644 --- a/apps/ui/registry/default/ui/pagination.tsx +++ b/apps/ui/registry/default/ui/pagination.tsx @@ -1,3 +1,5 @@ +"use client"; + import { mergeProps } from "@base-ui-components/react/merge-props"; import { useRender } from "@base-ui-components/react/use-render"; import { diff --git a/apps/ui/registry/registry-particles.ts b/apps/ui/registry/registry-particles.ts index 3b8370b5a..c23c139f8 100644 --- a/apps/ui/registry/registry-particles.ts +++ b/apps/ui/registry/registry-particles.ts @@ -627,7 +627,7 @@ export const particles: ParticleItem[] = [ description: "Link rendered as button", files: [{ path: "particles/p-button-17.tsx", type: "registry:block" }], name: "p-button-17", - registryDependencies: ["@coss/button"], + registryDependencies: ["@coss/button-link"], type: "registry:block", }, { diff --git a/apps/ui/registry/registry-ui.ts b/apps/ui/registry/registry-ui.ts index 16ebb1e6f..3adab62f9 100644 --- a/apps/ui/registry/registry-ui.ts +++ b/apps/ui/registry/registry-ui.ts @@ -12,6 +12,7 @@ export const ui: Registry["items"] = [ "@coss/badge", "@coss/breadcrumb", "@coss/button", + "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", @@ -192,6 +193,17 @@ export const ui: Registry["items"] = [ name: "button", type: "registry:ui", }, + { + dependencies: ["@base-ui-components/react"], + files: [ + { + path: "ui/button-link.tsx", + type: "registry:ui", + }, + ], + name: "button-link", + type: "registry:ui", + }, { dependencies: [], files: [ diff --git a/bun.lock b/bun.lock index 3843d6a99..d8f334c4a 100644 --- a/bun.lock +++ b/bun.lock @@ -74,7 +74,7 @@ "name": "ui", "version": "0.1.0", "dependencies": { - "@base-ui-components/react": "^1.0.0-beta.7", + "@base-ui-components/react": "^1.0.0-rc.0", "@coss/ui": "workspace:*", "@hugeicons/core-free-icons": "^2.0.0", "@hugeicons/react": "^1.1.1", @@ -235,9 +235,9 @@ "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "@base-ui-components/react": ["@base-ui-components/react@1.0.0-beta.7", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui-components/utils": "0.2.1", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-jZoU6ZzPPDhiP62ToFpIZVkLDzYleL8NJhiw4Dm9Q+N7Q+/rOW+N6YN0URuQ9zMdcl8jvIks/8iY1cyD/VC87A=="], + "@base-ui-components/react": ["@base-ui-components/react@1.0.0-rc.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui-components/utils": "0.2.2", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-9lhUFbJcbXvc9KulLev1WTFxS/alJRBWDH/ibKSQaNvmDwMFS2gKp1sTeeldYSfKuS/KC1w2MZutc0wHu2hRHQ=="], - "@base-ui-components/utils": ["@base-ui-components/utils@0.2.1", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-6D3PNibMXWPxiXCW/aMTclTypNG/QPCUc5vwGS6yKIcoYGDmjLiG3VyJ7hlGAnXiMGJMWH2TVl335N+zkRAKZQ=="], + "@base-ui-components/utils": ["@base-ui-components/utils@0.2.2", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-rNJCD6TFy3OSRDKVHJDzLpxO3esTV1/drRtWNUpe7rCpPN9HZVHUCuP+6rdDYDGWfXnQHbqi05xOyRP2iZAlkw=="], "@biomejs/biome": ["@biomejs/biome@2.3.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.6", "@biomejs/cli-darwin-x64": "2.3.6", "@biomejs/cli-linux-arm64": "2.3.6", "@biomejs/cli-linux-arm64-musl": "2.3.6", "@biomejs/cli-linux-x64": "2.3.6", "@biomejs/cli-linux-x64-musl": "2.3.6", "@biomejs/cli-win32-arm64": "2.3.6", "@biomejs/cli-win32-x64": "2.3.6" }, "bin": { "biome": "bin/biome" } }, "sha512-oqUhWyU6tae0MFsr/7iLe++QWRg+6jtUhlx9/0GmCWDYFFrK366sBLamNM7D9Y+c7YSynUFKr8lpEp1r6Sk7eA=="], diff --git a/packages/ui/src/components/github-link.tsx b/packages/ui/src/components/github-link.tsx index cf53a7bbf..a0d995d2b 100644 --- a/packages/ui/src/components/github-link.tsx +++ b/packages/ui/src/components/github-link.tsx @@ -1,5 +1,5 @@ import { siteConfig } from "@coss/ui/lib/config"; -import { Button } from "@coss/ui/ui/button"; +import { ButtonLink } from "@coss/ui/ui/button-link"; import { Skeleton } from "@coss/ui/ui/skeleton"; import { GithubIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; @@ -8,7 +8,7 @@ import * as React from "react"; export function GitHubLink() { return ( - - +
  • ); diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css index 998222d08..929829691 100644 --- a/packages/ui/src/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -355,7 +355,7 @@ [data-lib="radix-ui"] & { &::before { - background-color: --alpha(var(--color-error) / 20%); + background-color: --alpha(var(--color-destructive) / 20%); } } diff --git a/packages/ui/src/ui/button-link.tsx b/packages/ui/src/ui/button-link.tsx new file mode 100644 index 000000000..46f2eae6a --- /dev/null +++ b/packages/ui/src/ui/button-link.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { mergeProps } from "@base-ui-components/react/merge-props"; +import { useRender } from "@base-ui-components/react/use-render"; +import type { VariantProps } from "class-variance-authority"; + +import { cn } from "@coss/ui/lib/utils"; + +import { buttonVariants } from "./button"; + +interface ButtonLinkProps extends useRender.ComponentProps<"a"> { + variant?: VariantProps["variant"]; + size?: VariantProps["size"]; +} + +function ButtonLink({ + className, + variant, + size, + render, + ...props +}: ButtonLinkProps) { + const defaultProps = { + className: cn(buttonVariants({ className, size, variant })), + "data-slot": "button", + }; + + return useRender({ + defaultTagName: "a", + props: mergeProps<"a">(defaultProps, props), + render, + }); +} + +export { ButtonLink }; diff --git a/packages/ui/src/ui/button.tsx b/packages/ui/src/ui/button.tsx index a48051924..913ec93d7 100644 --- a/packages/ui/src/ui/button.tsx +++ b/packages/ui/src/ui/button.tsx @@ -1,7 +1,7 @@ -import { mergeProps } from "@base-ui-components/react/merge-props"; -import { useRender } from "@base-ui-components/react/use-render"; +"use client"; + +import { Button as ButtonPrimitive } from "@base-ui-components/react/button"; import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; import { cn } from "@coss/ui/lib/utils"; @@ -45,26 +45,19 @@ const buttonVariants = cva( }, ); -interface ButtonProps extends useRender.ComponentProps<"button"> { - variant?: VariantProps["variant"]; - size?: VariantProps["size"]; -} - -function Button({ className, variant, size, render, ...props }: ButtonProps) { - const typeValue: React.ButtonHTMLAttributes["type"] = - render ? undefined : "button"; - - const defaultProps = { - className: cn(buttonVariants({ className, size, variant })), - "data-slot": "button", - type: typeValue, - }; - - return useRender({ - defaultTagName: "button", - props: mergeProps<"button">(defaultProps, props), - render, - }); +function Button({ + className, + variant, + size, + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ( + + ); } export { Button, buttonVariants }; diff --git a/packages/ui/src/ui/pagination.tsx b/packages/ui/src/ui/pagination.tsx index 89ca9ce4c..26b9ce297 100644 --- a/packages/ui/src/ui/pagination.tsx +++ b/packages/ui/src/ui/pagination.tsx @@ -1,3 +1,5 @@ +"use client"; + import { mergeProps } from "@base-ui-components/react/merge-props"; import { useRender } from "@base-ui-components/react/use-render"; import { From cadcfbc60d7e72ccccb196967d6660afe164f53a Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Fri, 5 Dec 2025 12:41:46 +0100 Subject: [PATCH 2/7] feat: allow passing refs --- apps/ui/public/r/button.json | 2 +- apps/ui/public/r/p-toast-7.json | 2 +- apps/ui/registry/default/particles/p-toast-7.tsx | 9 +++------ apps/ui/registry/default/ui/button.tsx | 5 ++++- packages/ui/src/ui/button.tsx | 5 ++++- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/ui/public/r/button.json b/apps/ui/public/r/button.json index 1b0375cee..2441c7627 100644 --- a/apps/ui/public/r/button.json +++ b/apps/ui/public/r/button.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/ui/button.tsx", - "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui-components/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border bg-clip-padding font-medium text-sm outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default:\n \"min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7\",\n \"icon-xl\": \"size-10 [&_svg:not([class*='size-'])]:size-4.5\",\n \"icon-xs\":\n \"size-6 rounded-md before:rounded-[calc(var(--radius-md)-1px)]\",\n lg: \"min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]\",\n sm: \"min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]\",\n xl: \"min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5\",\n xs: \"min-h-6 gap-1 rounded-md px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n destructive:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n \"destructive-outline\":\n \"border-border bg-transparent text-destructive-foreground shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:border-destructive/32 [&:is(:hover,[data-pressed])]:bg-destructive/4\",\n ghost: \"border-transparent hover:bg-accent data-pressed:bg-accent\",\n link: \"border-transparent underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:bg-accent/50 dark:[&:is(:hover,[data-pressed])]:bg-input/64\",\n secondary:\n \"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90\",\n },\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n ...props\n}: ButtonPrimitive.Props & VariantProps) {\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n", + "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui-components/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border bg-clip-padding font-medium text-sm outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default:\n \"min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7\",\n \"icon-xl\": \"size-10 [&_svg:not([class*='size-'])]:size-4.5\",\n \"icon-xs\":\n \"size-6 rounded-md before:rounded-[calc(var(--radius-md)-1px)]\",\n lg: \"min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]\",\n sm: \"min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]\",\n xl: \"min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5\",\n xs: \"min-h-6 gap-1 rounded-md px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n destructive:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n \"destructive-outline\":\n \"border-border bg-transparent text-destructive-foreground shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:border-destructive/32 [&:is(:hover,[data-pressed])]:bg-destructive/4\",\n ghost: \"border-transparent hover:bg-accent data-pressed:bg-accent\",\n link: \"border-transparent underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:bg-accent/50 dark:[&:is(:hover,[data-pressed])]:bg-input/64\",\n secondary:\n \"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90\",\n },\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n ...props\n}: ButtonPrimitive.Props &\n VariantProps &\n React.ComponentPropsWithRef) {\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n", "type": "registry:ui" } ], diff --git a/apps/ui/public/r/p-toast-7.json b/apps/ui/public/r/p-toast-7.json index 27941a703..baf95dfd1 100644 --- a/apps/ui/public/r/p-toast-7.json +++ b/apps/ui/public/r/p-toast-7.json @@ -12,7 +12,7 @@ "files": [ { "path": "registry/default/particles/p-toast-7.tsx", - "content": "\"use client\";\n\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { useCopyToClipboard } from \"@/registry/default/hooks/use-copy-to-clipboard\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { anchoredToastManager } from \"@/registry/default/ui/toast\";\nimport {\n Tooltip,\n TooltipPopup,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nexport default function Particle() {\n const copyButtonRef = React.useRef(null);\n const toastTimeout = 2000;\n\n const { copyToClipboard, isCopied } = useCopyToClipboard({\n onCopy: () => {\n if (copyButtonRef.current) {\n anchoredToastManager.add({\n data: {\n tooltipStyle: true,\n },\n positionerProps: {\n anchor: copyButtonRef.current,\n },\n timeout: toastTimeout,\n title: \"Copied!\",\n });\n }\n },\n timeout: toastTimeout,\n });\n\n function handleCopy() {\n const url = \"https://coss.com\";\n copyToClipboard(url);\n }\n\n return (\n \n \n }\n >\n {isCopied ? (\n \n ) : (\n \n )}\n \n \n

    Copy to clipboard

    \n
    \n
    \n );\n}\n", + "content": "\"use client\";\n\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { useCopyToClipboard } from \"@/registry/default/hooks/use-copy-to-clipboard\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { anchoredToastManager } from \"@/registry/default/ui/toast\";\nimport {\n Tooltip,\n TooltipPopup,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nexport default function Particle() {\n const copyButtonRef = React.useRef(null);\n const toastTimeout = 2000;\n\n const { copyToClipboard, isCopied } = useCopyToClipboard({\n onCopy: () => {\n if (copyButtonRef.current) {\n anchoredToastManager.add({\n data: {\n tooltipStyle: true,\n },\n positionerProps: {\n anchor: copyButtonRef.current,\n },\n timeout: toastTimeout,\n title: \"Copied!\",\n });\n }\n },\n timeout: toastTimeout,\n });\n\n function handleCopy() {\n const url = \"https://coss.com\";\n copyToClipboard(url);\n }\n\n return (\n \n \n }\n >\n {isCopied ? : }\n \n \n

    Copy to clipboard

    \n
    \n
    \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/registry/default/particles/p-toast-7.tsx b/apps/ui/registry/default/particles/p-toast-7.tsx index 05b7b2ec4..ea3a0d08d 100644 --- a/apps/ui/registry/default/particles/p-toast-7.tsx +++ b/apps/ui/registry/default/particles/p-toast-7.tsx @@ -13,7 +13,7 @@ import { } from "@/registry/default/ui/tooltip"; export default function Particle() { - const copyButtonRef = React.useRef(null); + const copyButtonRef = React.useRef(null); const toastTimeout = 2000; const { copyToClipboard, isCopied } = useCopyToClipboard({ @@ -46,6 +46,7 @@ export default function Particle() {
    +// Render a native button element + + +// Render a div of role="button" that looks like a button + + +// Render an anchor tag that looks like a button +About + +// Render a custom component (e.g., Next.js Link) that looks like a button +}>About ``` -## Link +`ButtonLink` was introduced because the Base UI `Button` component adds attributes (like `role="button"`) that are incompatible with anchor tags when using the `render` prop. -You can use the [`render`](https://base-ui.com/react/utils/use-render#migrating-from-radix-ui) prop to make another component look like a button. Here's an example of a link that looks like a button. +For links styled as buttons, always use `ButtonLink` instead of `Button` with `render`: ```tsx -import Link from "next/link" - -import { Button } from "@/components/ui/button" +// Correct: Use ButtonLink for links +}>About -export function LinkAsButton() { - return -} +// Incorrect: Button adds incompatible attributes to anchor elements + ``` ## Examples @@ -151,19 +159,41 @@ export function LinkAsButton() { ### With Link - - Use ButtonLink for Links - - For links that look like buttons, use the ButtonLink component instead of using `Button` with the `render` prop. `ButtonLink` is specifically designed for this use case and renders an `` tag by default. - - - ### Loading +## API Reference + +### Button + +`Button` supports the following props: + +| Prop | Type | Default | Description | +| :---------- | :------------------------------------------------------------ | :-------- | :--------------------------------------------- | +| `variant` | `"default" \| "destructive" \| "outline" \| "ghost" \| ...` | `"default"` | The visual style variant | +| `size` | `"xs" \| "sm" \| "default" \| "lg" \| "xl" \| "icon" \| ...` | `"default"` | The size of the button | +| `render` | `ReactElement \| ((props) => ReactElement)` | - | Custom element to render | +| `className` | `string` | - | Additional CSS classes | + +All standard button (` ``` ```tsx title="coss ui" +// [!code word:ButtonLink] // [!code word:render:3] // [!code word:nativeButton:3] // For custom components: - - // For links (renders ): -import { ButtonLink } from "@/components/ui/button-link" -Login +import { ButtonLink } from "@/components/ui/button" +Link // For Next.js Link: -import { ButtonLink } from "@/components/ui/button-link" -}>Login +import { ButtonLink } from "@/components/ui/button" +}>Next.js Link ``` diff --git a/apps/ui/content/docs/components/meta.json b/apps/ui/content/docs/components/meta.json index 42d6dc960..f9728a630 100644 --- a/apps/ui/content/docs/components/meta.json +++ b/apps/ui/content/docs/components/meta.json @@ -8,7 +8,6 @@ "badge", "breadcrumb", "button", - "button-link", "card", "checkbox", "checkbox-group", diff --git a/apps/ui/lib/docs.ts b/apps/ui/lib/docs.ts index bc6c38c73..818b66209 100644 --- a/apps/ui/lib/docs.ts +++ b/apps/ui/lib/docs.ts @@ -1,4 +1,3 @@ export const PAGES_NEW = [ // "/docs/components/{component-name}", - "/docs/components/button-link", ]; diff --git a/apps/ui/public/r/button-link.json b/apps/ui/public/r/button-link.json deleted file mode 100644 index 6920a7a51..000000000 --- a/apps/ui/public/r/button-link.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "button-link", - "type": "registry:ui", - "dependencies": [ - "@base-ui-components/react" - ], - "registryDependencies": [ - "@coss/button" - ], - "files": [ - { - "path": "registry/default/ui/button-link.tsx", - "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui-components/react/merge-props\";\nimport { useRender } from \"@base-ui-components/react/use-render\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport { buttonVariants } from \"@/registry/default/ui/button\";\n\ninterface ButtonLinkProps extends useRender.ComponentProps<\"a\"> {\n variant?: VariantProps[\"variant\"];\n size?: VariantProps[\"size\"];\n}\n\nfunction ButtonLink({\n className,\n variant,\n size,\n render,\n ...props\n}: ButtonLinkProps) {\n const defaultProps = {\n className: cn(buttonVariants({ className, size, variant })),\n \"data-slot\": \"button\",\n };\n\n return useRender({\n defaultTagName: \"a\",\n props: mergeProps<\"a\">(defaultProps, props),\n render,\n });\n}\n\nexport { ButtonLink };\n", - "type": "registry:ui" - } - ] -} \ No newline at end of file diff --git a/apps/ui/public/r/button.json b/apps/ui/public/r/button.json index 2441c7627..432d02283 100644 --- a/apps/ui/public/r/button.json +++ b/apps/ui/public/r/button.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/ui/button.tsx", - "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui-components/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border bg-clip-padding font-medium text-sm outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default:\n \"min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7\",\n \"icon-xl\": \"size-10 [&_svg:not([class*='size-'])]:size-4.5\",\n \"icon-xs\":\n \"size-6 rounded-md before:rounded-[calc(var(--radius-md)-1px)]\",\n lg: \"min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]\",\n sm: \"min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]\",\n xl: \"min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5\",\n xs: \"min-h-6 gap-1 rounded-md px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n destructive:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n \"destructive-outline\":\n \"border-border bg-transparent text-destructive-foreground shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:border-destructive/32 [&:is(:hover,[data-pressed])]:bg-destructive/4\",\n ghost: \"border-transparent hover:bg-accent data-pressed:bg-accent\",\n link: \"border-transparent underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:bg-accent/50 dark:[&:is(:hover,[data-pressed])]:bg-input/64\",\n secondary:\n \"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90\",\n },\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n ...props\n}: ButtonPrimitive.Props &\n VariantProps &\n React.ComponentPropsWithRef) {\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n", + "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui-components/react/button\";\nimport { mergeProps } from \"@base-ui-components/react/merge-props\";\nimport { useRender } from \"@base-ui-components/react/use-render\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border bg-clip-padding font-medium text-sm outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default:\n \"min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7\",\n \"icon-xl\": \"size-10 [&_svg:not([class*='size-'])]:size-4.5\",\n \"icon-xs\":\n \"size-6 rounded-md before:rounded-[calc(var(--radius-md)-1px)]\",\n lg: \"min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]\",\n sm: \"min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]\",\n xl: \"min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5\",\n xs: \"min-h-6 gap-1 rounded-md px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n destructive:\n \"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none\",\n \"destructive-outline\":\n \"border-border bg-transparent text-destructive-foreground shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:border-destructive/32 [&:is(:hover,[data-pressed])]:bg-destructive/4\",\n ghost: \"border-transparent hover:bg-accent data-pressed:bg-accent\",\n link: \"border-transparent underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background shadow-xs not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-in-data-[slot=group]:bg-clip-border dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/4%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none [&:is(:hover,[data-pressed])]:bg-accent/50 dark:[&:is(:hover,[data-pressed])]:bg-input/64\",\n secondary:\n \"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90\",\n },\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n ...props\n}: ButtonPrimitive.Props &\n VariantProps &\n React.ComponentPropsWithRef) {\n return (\n \n );\n}\n\nfunction ButtonLink({\n className,\n variant,\n size,\n render,\n ...props\n}: useRender.ComponentProps<\"a\"> & VariantProps) {\n const defaultProps = {\n className: cn(buttonVariants({ className, size, variant })),\n \"data-slot\": \"button\",\n };\n\n return useRender({\n defaultTagName: \"a\",\n props: mergeProps<\"a\">(defaultProps, props),\n render,\n });\n}\n\nexport { Button, ButtonLink, buttonVariants };\n", "type": "registry:ui" } ], diff --git a/apps/ui/public/r/p-button-17.json b/apps/ui/public/r/p-button-17.json index 5dd03defe..e727ed4a8 100644 --- a/apps/ui/public/r/p-button-17.json +++ b/apps/ui/public/r/p-button-17.json @@ -4,12 +4,12 @@ "type": "registry:block", "description": "Link rendered as button", "registryDependencies": [ - "@coss/button-link" + "@coss/button" ], "files": [ { "path": "registry/default/particles/p-button-17.tsx", - "content": "import Link from \"next/link\";\n\nimport { ButtonLink } from \"@/registry/default/ui/button-link\";\n\nexport default function Particle() {\n return (\n
    \n Link\n }>Next.js Link\n
    \n );\n}\n", + "content": "import Link from \"next/link\";\n\nimport { ButtonLink } from \"@/registry/default/ui/button\";\n\nexport default function Particle() {\n return (\n
    \n Link\n }>Next.js Link\n
    \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/registry.json b/apps/ui/public/r/registry.json index 098620e54..d3ac6fe36 100644 --- a/apps/ui/public/r/registry.json +++ b/apps/ui/public/r/registry.json @@ -12,7 +12,6 @@ "@coss/badge", "@coss/breadcrumb", "@coss/button", - "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", @@ -210,22 +209,6 @@ "name": "button", "type": "registry:ui" }, - { - "dependencies": [ - "@base-ui-components/react" - ], - "files": [ - { - "path": "registry/default/ui/button-link.tsx", - "type": "registry:ui" - } - ], - "name": "button-link", - "registryDependencies": [ - "@coss/button" - ], - "type": "registry:ui" - }, { "dependencies": [], "files": [ @@ -2007,7 +1990,7 @@ ], "name": "p-button-17", "registryDependencies": [ - "@coss/button-link" + "@coss/button" ], "type": "registry:block" }, diff --git a/apps/ui/public/r/ui.json b/apps/ui/public/r/ui.json index 78db4bfc1..e78d0cce0 100644 --- a/apps/ui/public/r/ui.json +++ b/apps/ui/public/r/ui.json @@ -11,7 +11,6 @@ "@coss/badge", "@coss/breadcrumb", "@coss/button", - "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", diff --git a/apps/ui/registry.json b/apps/ui/registry.json index 098620e54..d3ac6fe36 100644 --- a/apps/ui/registry.json +++ b/apps/ui/registry.json @@ -12,7 +12,6 @@ "@coss/badge", "@coss/breadcrumb", "@coss/button", - "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", @@ -210,22 +209,6 @@ "name": "button", "type": "registry:ui" }, - { - "dependencies": [ - "@base-ui-components/react" - ], - "files": [ - { - "path": "registry/default/ui/button-link.tsx", - "type": "registry:ui" - } - ], - "name": "button-link", - "registryDependencies": [ - "@coss/button" - ], - "type": "registry:ui" - }, { "dependencies": [], "files": [ @@ -2007,7 +1990,7 @@ ], "name": "p-button-17", "registryDependencies": [ - "@coss/button-link" + "@coss/button" ], "type": "registry:block" }, diff --git a/apps/ui/registry/__index__.tsx b/apps/ui/registry/__index__.tsx index d99b20813..19ef5def6 100644 --- a/apps/ui/registry/__index__.tsx +++ b/apps/ui/registry/__index__.tsx @@ -151,24 +151,6 @@ export const Index: Record = { categories: undefined, meta: undefined, }, - "button-link": { - name: "button-link", - description: "", - type: "registry:ui", - registryDependencies: ["@coss/button"], - files: [{ - path: "registry/default/ui/button-link.tsx", - type: "registry:ui", - target: "" - }], - component: React.lazy(async () => { - const mod = await import("@/registry/default/ui/button-link.tsx") - const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name - return { default: mod.default || mod[exportName] } - }), - categories: undefined, - meta: undefined, - }, "card": { name: "card", description: "", @@ -2027,7 +2009,7 @@ export const Index: Record = { name: "p-button-17", description: "Link rendered as button", type: "registry:block", - registryDependencies: ["@coss/button-link"], + registryDependencies: ["@coss/button"], files: [{ path: "registry/default/particles/p-button-17.tsx", type: "registry:block", diff --git a/apps/ui/registry/default/particles/p-button-17.tsx b/apps/ui/registry/default/particles/p-button-17.tsx index d66d71bae..03eb6b2f6 100644 --- a/apps/ui/registry/default/particles/p-button-17.tsx +++ b/apps/ui/registry/default/particles/p-button-17.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; -import { ButtonLink } from "@/registry/default/ui/button-link"; +import { ButtonLink } from "@/registry/default/ui/button"; export default function Particle() { return ( diff --git a/apps/ui/registry/default/ui/button-link.tsx b/apps/ui/registry/default/ui/button-link.tsx deleted file mode 100644 index 47bf203f3..000000000 --- a/apps/ui/registry/default/ui/button-link.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { mergeProps } from "@base-ui-components/react/merge-props"; -import { useRender } from "@base-ui-components/react/use-render"; -import type { VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -import { buttonVariants } from "@/registry/default/ui/button"; - -interface ButtonLinkProps extends useRender.ComponentProps<"a"> { - variant?: VariantProps["variant"]; - size?: VariantProps["size"]; -} - -function ButtonLink({ - className, - variant, - size, - render, - ...props -}: ButtonLinkProps) { - const defaultProps = { - className: cn(buttonVariants({ className, size, variant })), - "data-slot": "button", - }; - - return useRender({ - defaultTagName: "a", - props: mergeProps<"a">(defaultProps, props), - render, - }); -} - -export { ButtonLink }; diff --git a/apps/ui/registry/default/ui/button.tsx b/apps/ui/registry/default/ui/button.tsx index 12915864c..3223f52a7 100644 --- a/apps/ui/registry/default/ui/button.tsx +++ b/apps/ui/registry/default/ui/button.tsx @@ -1,6 +1,8 @@ "use client"; import { Button as ButtonPrimitive } from "@base-ui-components/react/button"; +import { mergeProps } from "@base-ui-components/react/merge-props"; +import { useRender } from "@base-ui-components/react/use-render"; import { cva, type VariantProps } from "class-variance-authority"; import type * as React from "react"; @@ -63,4 +65,23 @@ function Button({ ); } -export { Button, buttonVariants }; +function ButtonLink({ + className, + variant, + size, + render, + ...props +}: useRender.ComponentProps<"a"> & VariantProps) { + const defaultProps = { + className: cn(buttonVariants({ className, size, variant })), + "data-slot": "button", + }; + + return useRender({ + defaultTagName: "a", + props: mergeProps<"a">(defaultProps, props), + render, + }); +} + +export { Button, ButtonLink, buttonVariants }; diff --git a/apps/ui/registry/registry-particles.ts b/apps/ui/registry/registry-particles.ts index c23c139f8..3b8370b5a 100644 --- a/apps/ui/registry/registry-particles.ts +++ b/apps/ui/registry/registry-particles.ts @@ -627,7 +627,7 @@ export const particles: ParticleItem[] = [ description: "Link rendered as button", files: [{ path: "particles/p-button-17.tsx", type: "registry:block" }], name: "p-button-17", - registryDependencies: ["@coss/button-link"], + registryDependencies: ["@coss/button"], type: "registry:block", }, { diff --git a/apps/ui/registry/registry-ui.ts b/apps/ui/registry/registry-ui.ts index 8107c945c..16ebb1e6f 100644 --- a/apps/ui/registry/registry-ui.ts +++ b/apps/ui/registry/registry-ui.ts @@ -12,7 +12,6 @@ export const ui: Registry["items"] = [ "@coss/badge", "@coss/breadcrumb", "@coss/button", - "@coss/button-link", "@coss/card", "@coss/checkbox", "@coss/checkbox-group", @@ -193,18 +192,6 @@ export const ui: Registry["items"] = [ name: "button", type: "registry:ui", }, - { - dependencies: ["@base-ui-components/react"], - files: [ - { - path: "ui/button-link.tsx", - type: "registry:ui", - }, - ], - name: "button-link", - registryDependencies: ["@coss/button"], - type: "registry:ui", - }, { dependencies: [], files: [ diff --git a/packages/ui/src/components/github-link.tsx b/packages/ui/src/components/github-link.tsx index a0d995d2b..4ce844a59 100644 --- a/packages/ui/src/components/github-link.tsx +++ b/packages/ui/src/components/github-link.tsx @@ -1,5 +1,5 @@ import { siteConfig } from "@coss/ui/lib/config"; -import { ButtonLink } from "@coss/ui/ui/button-link"; +import { ButtonLink } from "@coss/ui/ui/button"; import { Skeleton } from "@coss/ui/ui/skeleton"; import { GithubIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; diff --git a/packages/ui/src/components/site-cta.tsx b/packages/ui/src/components/site-cta.tsx index ffbae8173..3b4dfac67 100644 --- a/packages/ui/src/components/site-cta.tsx +++ b/packages/ui/src/components/site-cta.tsx @@ -1,4 +1,4 @@ -import { ButtonLink } from "@coss/ui/ui/button-link"; +import { ButtonLink } from "@coss/ui/ui/button"; import Link from "next/link"; export function SiteCta() { diff --git a/packages/ui/src/ui/button-link.tsx b/packages/ui/src/ui/button-link.tsx deleted file mode 100644 index 09dd055d9..000000000 --- a/packages/ui/src/ui/button-link.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { mergeProps } from "@base-ui-components/react/merge-props"; -import { useRender } from "@base-ui-components/react/use-render"; -import type { VariantProps } from "class-variance-authority"; - -import { cn } from "@coss/ui/lib/utils"; - -import { buttonVariants } from "@coss/ui/ui/button"; - -interface ButtonLinkProps extends useRender.ComponentProps<"a"> { - variant?: VariantProps["variant"]; - size?: VariantProps["size"]; -} - -function ButtonLink({ - className, - variant, - size, - render, - ...props -}: ButtonLinkProps) { - const defaultProps = { - className: cn(buttonVariants({ className, size, variant })), - "data-slot": "button", - }; - - return useRender({ - defaultTagName: "a", - props: mergeProps<"a">(defaultProps, props), - render, - }); -} - -export { ButtonLink }; diff --git a/packages/ui/src/ui/button.tsx b/packages/ui/src/ui/button.tsx index f6826354c..821470441 100644 --- a/packages/ui/src/ui/button.tsx +++ b/packages/ui/src/ui/button.tsx @@ -1,6 +1,8 @@ "use client"; import { Button as ButtonPrimitive } from "@base-ui-components/react/button"; +import { mergeProps } from "@base-ui-components/react/merge-props"; +import { useRender } from "@base-ui-components/react/use-render"; import { cva, type VariantProps } from "class-variance-authority"; import type * as React from "react"; @@ -63,4 +65,23 @@ function Button({ ); } -export { Button, buttonVariants }; +function ButtonLink({ + className, + variant, + size, + render, + ...props +}: useRender.ComponentProps<"a"> & VariantProps) { + const defaultProps = { + className: cn(buttonVariants({ className, size, variant })), + "data-slot": "button", + }; + + return useRender({ + defaultTagName: "a", + props: mergeProps<"a">(defaultProps, props), + render, + }); +} + +export { Button, ButtonLink, buttonVariants }; From d3a196a88b6557da109247c4d17b901439a566df Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Fri, 5 Dec 2025 16:46:13 +0100 Subject: [PATCH 7/7] docs(button): remove outdated ButtonLink import examples --- apps/ui/content/docs/components/button.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/ui/content/docs/components/button.mdx b/apps/ui/content/docs/components/button.mdx index 0c233acd0..52be655ce 100644 --- a/apps/ui/content/docs/components/button.mdx +++ b/apps/ui/content/docs/components/button.mdx @@ -250,13 +250,8 @@ We've added a new `destructive-outline` variant for better UX patterns: // [!code word:ButtonLink] // [!code word:render:3] // [!code word:nativeButton:3] -// For custom components: -// For links (renders
    ): -import { ButtonLink } from "@/components/ui/button" Link -// For Next.js Link: -import { ButtonLink } from "@/components/ui/button" }>Next.js Link ```