diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 0000000000..d8ba14f224 --- /dev/null +++ b/apps/web/components.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "../../packages/coss-ui/src/styles/globals.css", + "baseColor": "zinc", + "cssVariables": true + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "hooks": "@coss/ui/hooks", + "utils": "@coss/ui/lib/utils", + "ui": "@coss/ui/components" + } +} diff --git a/apps/web/next.config.js b/apps/web/next.config.js index a33756b7ae..530484c383 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -219,6 +219,7 @@ const nextConfig = (phase) => { "@calcom/lib", "@calcom/prisma", "@calcom/trpc", + "@coss/ui", ], modularizeImports: { "@calcom/features/insights/components": { diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index 5c8d91d360..056a06aebe 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -1,7 +1,7 @@ @import "tailwindcss"; -@import "../../../packages/coss-ui/styles.css"; @import "../../../packages/config/theme/shared-sources.css"; @import "../../../packages/config/theme/tokens.css"; +@import "../../../packages/coss-ui/src/styles/globals.css"; @import "./raqb.css"; ::-moz-selection { diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 86febc051b..8d4b233f5e 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -9,7 +9,8 @@ "@lib/*": ["lib/*"], "@server/*": ["server/*"], "@prisma/client/*": ["@calcom/prisma/client/*"], - "@calcom/repository/*": ["@calcom/lib/server/repository/*"] + "@calcom/repository/*": ["@calcom/lib/server/repository/*"], + "@coss/ui/*": ["../../packages/coss-ui/src/*"] }, "plugins": [ { diff --git a/packages/config/package.json b/packages/config/package.json index 2df355f2a9..2bd0c63124 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -11,7 +11,6 @@ ], "dependencies": { "@calcom/eslint-plugin-eslint": "workspace:*", - "@tailwindcss/forms": "^0.5.10", "@todesktop/tailwind-variants": "^1.0.0", "eslint-config-next": "^13.2.1", "eslint-config-prettier": "^8.6.0", diff --git a/packages/config/theme/shared-sources.css b/packages/config/theme/shared-sources.css index 3ae814980f..7458798401 100644 --- a/packages/config/theme/shared-sources.css +++ b/packages/config/theme/shared-sources.css @@ -5,4 +5,5 @@ @source "../../../packages/app-store/**/*{components,pages}/**/*.{js,ts,jsx,tsx}"; @source "../../../packages/features/**/*.{js,ts,jsx,tsx}"; @source "../../../packages/ui/**/*.{js,ts,jsx,tsx}"; -@source "../../../packages/platform/atoms/**/*.{js,ts,jsx,tsx}"; \ No newline at end of file +@source "../../../packages/platform/atoms/**/*.{js,ts,jsx,tsx}"; +@source "../../../packages/coss-ui/src/**/*.{js,ts,jsx,tsx}"; diff --git a/packages/config/theme/tokens.css b/packages/config/theme/tokens.css index 645d382e4b..f09ee8b2f8 100644 --- a/packages/config/theme/tokens.css +++ b/packages/config/theme/tokens.css @@ -1,7 +1,6 @@ /** This file is used in webapp as well as inside shadow DOM of embed elements. **/ @import "tw-animate-css"; @plugin "@tailwindcss/typography"; -@plugin "@tailwindcss/forms"; @plugin "tailwindcss-radix"; @plugin 'tailwind-scrollbar' { diff --git a/packages/coss-ui/components.json b/packages/coss-ui/components.json index 06c704d2c8..e349a79986 100644 --- a/packages/coss-ui/components.json +++ b/packages/coss-ui/components.json @@ -4,7 +4,7 @@ "rsc": true, "tsx": true, "tailwind": { - "css": "./styles.css", + "css": "src/styles/globals.css", "baseColor": "zinc", "cssVariables": true }, @@ -12,8 +12,11 @@ "aliases": { "components": "@coss/ui/components", "utils": "@coss/ui/lib/utils", - "hooks": "@coss/ui/hooks", + "ui": "@coss/ui/components", "lib": "@coss/ui/lib", - "ui": "@coss/ui/components" + "hooks": "@coss/ui/hooks" + }, + "registries": { + "@coss": "https://coss.com/ui/r/{name}.json" } } diff --git a/packages/coss-ui/components/button.tsx b/packages/coss-ui/components/button.tsx deleted file mode 100644 index 148aa606a9..0000000000 --- a/packages/coss-ui/components/button.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from "react" -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 { cn } from "@coss/ui/lib/utils" - -const buttonVariants = cva( - "relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border bg-clip-padding text-sm font-medium whitespace-nowrap transition-shadow outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] 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 pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", - { - variants: { - variant: { - default: - "border-primary bg-primary text-primary-foreground shadow-xs shadow-primary/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-primary/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none", - outline: - "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:not-data-pressed: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", - secondary: - "border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90", - destructive: - "border-destructive bg-destructive text-white shadow-xs shadow-destructive/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-destructive/90 [&:is(:active,[data-pressed])]:inset-shadow-[0_1px_--theme(--color-black/8%)] [&:is(:disabled,:active,[data-pressed])]:shadow-none", - "destructive-outline": - "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", - ghost: "border-transparent hover:bg-accent data-pressed:bg-accent", - link: "border-transparent underline-offset-4 hover:underline", - }, - size: { - default: - "min-h-8 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)]", - 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", - sm: "min-h-7 gap-1.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)]", - lg: "min-h-9 px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2)-1px)]", - xl: "min-h-10 px-[calc(--spacing(4)-1px)] py-[calc(--spacing(2)-1px)] text-base [&_svg:not([class*='size-'])]:size-4.5", - icon: "size-8", - "icon-sm": "size-7", - "icon-lg": "size-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -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 = { - "data-slot": "button", - className: cn(buttonVariants({ variant, size, className })), - type: typeValue, - } - - return useRender({ - defaultTagName: "button", - render, - props: mergeProps<"button">(defaultProps, props), - }) -} - -export { Button, buttonVariants } \ No newline at end of file diff --git a/packages/coss-ui/components/input.tsx b/packages/coss-ui/components/input.tsx deleted file mode 100644 index bafa4b4fd6..0000000000 --- a/packages/coss-ui/components/input.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client" - -import { Input as InputPrimitive } from "@base-ui-components/react/input" - -import { cn } from "@coss/ui/lib/utils" - -function Input({ - className, - size = "default", - ...props -}: Omit & { - size?: "sm" | "default" | "lg" | number -}) { - return ( - - - - ) -} - -export { Input } diff --git a/packages/coss-ui/lib/utils.ts b/packages/coss-ui/lib/utils.ts deleted file mode 100644 index bd0c391ddd..0000000000 --- a/packages/coss-ui/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} diff --git a/packages/coss-ui/package.json b/packages/coss-ui/package.json index 45c8913aa3..d1c0806568 100644 --- a/packages/coss-ui/package.json +++ b/packages/coss-ui/package.json @@ -3,14 +3,28 @@ "private": true, "version": "0.0.0", "exports": { - "./styles.css": "./styles.css", - "./components/*": "./components/*.tsx", - "./lib/*": "./lib/*.ts" + "./styles.css": "./src/styles/globals.css", + "./lib/*": "./src/lib/*.ts", + "./components/*": "./src/components/*.tsx", + "./hooks/*": "./src/hooks/*.ts" + }, + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "type-check": "tsc --pretty --noEmit", + "type-check:ci": "tsc-absolute --pretty --noEmit", + "registry:pull": "ts-node ../../scripts/pull-coss-ui-components.ts" }, "dependencies": { - "@base-ui-components/react": "^1.0.0-beta.4", - "class-variance-authority": "^0.4.0", - "clsx": "^2.0.0", - "tailwind-merge": "^2.0.0" + "@base-ui/react": "^1.0.0", + "class-variance-authority": "0.7.1", + "clsx": "^2.1.1", + "lucide-react": "0.555.0", + "tailwind-merge": "^3.4.0" + }, + "devDependencies": { + "ts-node": "^10.9.2", + "tsc-absolute": "1.0.0", + "typescript": "5.9.0-beta" } } diff --git a/packages/coss-ui/src/components/accordion.tsx b/packages/coss-ui/src/components/accordion.tsx new file mode 100644 index 0000000000..fdba1a25fb --- /dev/null +++ b/packages/coss-ui/src/components/accordion.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"; +import { ChevronDownIcon } from "lucide-react"; + +import { cn } from "@coss/ui/lib/utils"; + +function Accordion(props: AccordionPrimitive.Root.Props) { + return ; +} + +function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) { + return ( + + ); +} + +function AccordionTrigger({ + className, + children, + ...props +}: AccordionPrimitive.Trigger.Props) { + return ( + + svg]:rotate-180", + className, + )} + data-slot="accordion-trigger" + {...props} + > + {children} + + + + ); +} + +function AccordionPanel({ + className, + children, + ...props +}: AccordionPrimitive.Panel.Props) { + return ( + +
{children}
+
+ ); +} + +export { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionPanel, + AccordionPanel as AccordionContent, +}; diff --git a/packages/coss-ui/src/components/alert-dialog.tsx b/packages/coss-ui/src/components/alert-dialog.tsx new file mode 100644 index 0000000000..f9b3abe31a --- /dev/null +++ b/packages/coss-ui/src/components/alert-dialog.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; + +import { cn } from "@coss/ui/lib/utils"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) { + return ( + + ); +} + +function AlertDialogBackdrop({ + className, + ...props +}: AlertDialogPrimitive.Backdrop.Props) { + return ( + + ); +} + +function AlertDialogViewport({ + className, + ...props +}: AlertDialogPrimitive.Viewport.Props) { + return ( + + ); +} + +function AlertDialogPopup({ + className, + ...props +}: AlertDialogPrimitive.Popup.Props) { + return ( + + + + + + + ); +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogFooter({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & { + variant?: "default" | "bare"; +}) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props +}: AlertDialogPrimitive.Title.Props) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: AlertDialogPrimitive.Description.Props) { + return ( + + ); +} + +function AlertDialogClose(props: AlertDialogPrimitive.Close.Props) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogBackdrop, + AlertDialogBackdrop as AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogPopup, + AlertDialogPopup as AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogClose, + AlertDialogViewport, +}; diff --git a/packages/coss-ui/src/components/alert.tsx b/packages/coss-ui/src/components/alert.tsx new file mode 100644 index 0000000000..0a2cc3a85f --- /dev/null +++ b/packages/coss-ui/src/components/alert.tsx @@ -0,0 +1,80 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; + +import { cn } from "@coss/ui/lib/utils"; + +const alertVariants = cva( + "relative grid w-full items-start gap-x-2 gap-y-0.5 rounded-xl border px-3.5 py-3 text-card-foreground text-sm has-[>svg]:has-data-[slot=alert-action]:grid-cols-[calc(var(--spacing)*4)_1fr_auto] has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-data-[slot=alert-action]:grid-cols-[1fr_auto] has-[>svg]:gap-x-2 [&>svg]:h-lh [&>svg]:w-4", + { + defaultVariants: { + variant: "default", + }, + variants: { + variant: { + default: + "bg-transparent dark:bg-input/32 [&>svg]:text-muted-foreground", + error: + "border-destructive/32 bg-destructive/4 [&>svg]:text-destructive", + info: "border-info/32 bg-info/4 [&>svg]:text-info", + success: "border-success/32 bg-success/4 [&>svg]:text-success", + warning: "border-warning/32 bg-warning/4 [&>svg]:text-warning", + }, + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription, AlertAction }; diff --git a/packages/coss-ui/src/components/autocomplete.tsx b/packages/coss-ui/src/components/autocomplete.tsx new file mode 100644 index 0000000000..0894c3afa7 --- /dev/null +++ b/packages/coss-ui/src/components/autocomplete.tsx @@ -0,0 +1,284 @@ +"use client"; + +import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete"; +import { ChevronsUpDownIcon, XIcon } from "lucide-react"; + +import { cn } from "@coss/ui/lib/utils"; +import { Input } from "@coss/ui/components/input"; +import { ScrollArea } from "@coss/ui/components/scroll-area"; + +const Autocomplete = AutocompletePrimitive.Root; + +function AutocompleteInput({ + className, + showTrigger = false, + showClear = false, + size, + ...props +}: Omit & { + showTrigger?: boolean; + showClear?: boolean; + size?: "sm" | "default" | "lg" | number; +}) { + const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; + + return ( +
+ } + {...props} + /> + {showTrigger && ( + + + + )} + {showClear && ( + + + + )} +
+ ); +} + +function AutocompletePopup({ + className, + children, + sideOffset = 4, + ...props +}: AutocompletePrimitive.Popup.Props & { + sideOffset?: number; +}) { + return ( + + + + + {children} + + + + + ); +} + +function AutocompleteItem({ + className, + children, + ...props +}: AutocompletePrimitive.Item.Props) { + return ( + + {children} + + ); +} + +function AutocompleteSeparator({ + className, + ...props +}: AutocompletePrimitive.Separator.Props) { + return ( + + ); +} + +function AutocompleteGroup({ + className, + ...props +}: AutocompletePrimitive.Group.Props) { + return ( + + ); +} + +function AutocompleteGroupLabel({ + className, + ...props +}: AutocompletePrimitive.GroupLabel.Props) { + return ( + + ); +} + +function AutocompleteEmpty({ + className, + ...props +}: AutocompletePrimitive.Empty.Props) { + return ( + + ); +} + +function AutocompleteRow({ + className, + ...props +}: AutocompletePrimitive.Row.Props) { + return ( + + ); +} + +function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) { + return ( + + ); +} + +function AutocompleteList({ + className, + ...props +}: AutocompletePrimitive.List.Props) { + return ( + + + + ); +} + +function AutocompleteClear({ + className, + ...props +}: AutocompletePrimitive.Clear.Props) { + return ( + + + + ); +} + +function AutocompleteStatus({ + className, + ...props +}: AutocompletePrimitive.Status.Props) { + return ( + + ); +} + +function AutocompleteCollection({ + ...props +}: AutocompletePrimitive.Collection.Props) { + return ( + + ); +} + +function AutocompleteTrigger({ + className, + ...props +}: AutocompletePrimitive.Trigger.Props) { + return ( + + ); +} + +export { + Autocomplete, + AutocompleteInput, + AutocompleteTrigger, + AutocompletePopup, + AutocompleteItem, + AutocompleteSeparator, + AutocompleteGroup, + AutocompleteGroupLabel, + AutocompleteEmpty, + AutocompleteValue, + AutocompleteList, + AutocompleteClear, + AutocompleteStatus, + AutocompleteRow, + AutocompleteCollection, +}; diff --git a/packages/coss-ui/src/components/avatar.tsx b/packages/coss-ui/src/components/avatar.tsx new file mode 100644 index 0000000000..9f2e50839d --- /dev/null +++ b/packages/coss-ui/src/components/avatar.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"; + +import { cn } from "@coss/ui/lib/utils"; + +function Avatar({ className, ...props }: AvatarPrimitive.Root.Props) { + return ( + + ); +} + +function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props +}: AvatarPrimitive.Fallback.Props) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/packages/coss-ui/src/components/badge.tsx b/packages/coss-ui/src/components/badge.tsx new file mode 100644 index 0000000000..7ebfe78acc --- /dev/null +++ b/packages/coss-ui/src/components/badge.tsx @@ -0,0 +1,58 @@ +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@coss/ui/lib/utils"; + +const badgeVariants = cva( + "relative inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-sm border border-transparent font-medium outline-none transition-shadow 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*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [button,a&]:cursor-pointer [button,a&]:pointer-coarse:after:absolute [button,a&]:pointer-coarse:after:size-full [button,a&]:pointer-coarse:after:min-h-11 [button,a&]:pointer-coarse:after:min-w-11", + { + defaultVariants: { + size: "default", + variant: "default", + }, + variants: { + size: { + default: + "h-5.5 min-w-5.5 px-[calc(--spacing(1)-1px)] text-sm sm:h-4.5 sm:min-w-4.5 sm:text-xs", + lg: "h-6.5 min-w-6.5 px-[calc(--spacing(1.5)-1px)] text-base sm:h-5.5 sm:min-w-5.5 sm:text-sm", + sm: "h-5 min-w-5 rounded-[calc(var(--radius-sm)-2px)] px-[calc(--spacing(1)-1px)] text-xs sm:h-4 sm:min-w-4 sm:text-[.625rem]", + }, + variant: { + default: + "bg-primary text-primary-foreground [button,a&]:hover:bg-primary/90", + destructive: + "bg-destructive text-white [button,a&]:hover:bg-destructive/90", + error: + "bg-destructive/8 text-destructive-foreground dark:bg-destructive/16", + info: "bg-info/8 text-info-foreground dark:bg-info/16", + outline: + "border-border bg-transparent dark:bg-input/32 [button,a&]:hover:bg-accent/50 dark:[button,a&]:hover:bg-input/48", + secondary: + "bg-secondary text-secondary-foreground [button,a&]:hover:bg-secondary/90", + success: "bg-success/8 text-success-foreground dark:bg-success/16", + warning: "bg-warning/8 text-warning-foreground dark:bg-warning/16", + }, + }, + }, +); + +interface BadgeProps extends useRender.ComponentProps<"span"> { + variant?: VariantProps["variant"]; + size?: VariantProps["size"]; +} + +function Badge({ className, variant, size, render, ...props }: BadgeProps) { + const defaultProps = { + className: cn(badgeVariants({ className, size, variant })), + "data-slot": "badge", + }; + + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">(defaultProps, props), + render, + }); +} + +export { Badge, badgeVariants }; diff --git a/packages/coss-ui/src/components/breadcrumb.tsx b/packages/coss-ui/src/components/breadcrumb.tsx new file mode 100644 index 0000000000..d325b54d68 --- /dev/null +++ b/packages/coss-ui/src/components/breadcrumb.tsx @@ -0,0 +1,110 @@ +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; +import type * as React from "react"; + +import { cn } from "@coss/ui/lib/utils"; + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return