diff --git a/app/globals.css b/app/globals.css index 41f1a57..2e5241a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,6 +1,8 @@ @import "tailwindcss"; @import "@cloudflare/kumo/styles"; +@source "../node_modules/@cloudflare/kumo/dist"; + @custom-variant dark (&:is(.dark *)); @theme inline { @@ -138,6 +140,207 @@ a, z-index: 40; } +.teatea-button { + min-height: 2.5rem; + border-radius: var(--radius-md); + font-weight: 600; + transition: + background-color 140ms ease, + border-color 140ms ease, + color 140ms ease, + box-shadow 140ms ease, + opacity 140ms ease; +} + +.teatea-button:focus-visible, +.teatea-control:focus-visible, +:where([data-kumo-component="Select"][data-kumo-part="trigger"]):focus-visible { + outline: none; + box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 24%, transparent); +} + +.teatea-button-default { + border: 1px solid var(--primary); + background: var(--primary); + color: var(--primary-foreground); +} + +.teatea-button-default:hover { + background: color-mix(in oklch, var(--primary) 88%, black); + border-color: color-mix(in oklch, var(--primary) 88%, black); +} + +.teatea-button-outline, +.teatea-button-secondary { + border: 1px solid var(--border); + background: var(--card); + color: var(--foreground); +} + +.teatea-button-outline:hover, +.teatea-button-secondary:hover, +.teatea-button-ghost:hover { + border-color: color-mix(in oklch, var(--primary) 45%, var(--border)); + background: color-mix(in oklch, var(--secondary) 72%, white); + color: var(--accent-foreground); +} + +.teatea-button-ghost { + border: 1px solid transparent; + background: transparent; + color: var(--foreground); +} + +.teatea-button-destructive { + border: 1px solid var(--destructive); + background: var(--destructive); + color: var(--destructive-foreground); +} + +.teatea-control, +:where([data-kumo-component="Select"][data-kumo-part="trigger"]) { + min-height: 2.5rem; + width: 100%; + border: 1px solid var(--input); + border-radius: var(--radius-md); + background: var(--card); + color: var(--foreground); + font-size: 0.875rem; + font-weight: 500; + box-shadow: 0 1px 2px color-mix(in oklch, var(--foreground) 7%, transparent); + transition: + background-color 140ms ease, + border-color 140ms ease, + box-shadow 140ms ease; +} + +.teatea-control { + padding: 0.5rem 0.75rem; +} + +:where([data-kumo-component="Select"][data-kumo-part="trigger"]) { + appearance: none; + outline: none; + padding: 0.5rem 0.75rem; + text-align: left; +} + +.teatea-control:hover, +:where([data-kumo-component="Select"][data-kumo-part="trigger"]):hover { + border-color: color-mix(in oklch, var(--primary) 45%, var(--input)); + background: color-mix(in oklch, var(--secondary) 34%, white); +} + +.teatea-control::placeholder { + color: var(--muted-foreground); +} + +.teatea-control:disabled, +.teatea-button:disabled, +:where([data-kumo-component="Select"][data-kumo-part="trigger"][data-disabled]) { + cursor: not-allowed; + opacity: 0.55; +} + +.teatea-select :where(svg), +:where([data-kumo-component="Select"][data-kumo-part="trigger"]) :where(svg) { + color: var(--primary); +} + +:where([role="listbox"]:has([data-kumo-component="Select"][data-kumo-part="option"])) { + z-index: 60; + outline: none; + max-height: min(18rem, var(--available-height, 18rem)); + min-width: var(--anchor-width, 12rem); + overflow-y: auto; + overscroll-behavior: contain; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--card); + color: var(--foreground); + padding: 0.375rem; + box-shadow: + 0 18px 44px color-mix(in oklch, var(--foreground) 14%, transparent), + 0 0 0 1px color-mix(in oklch, var(--primary) 10%, transparent); +} + +:where([data-kumo-component="Select"][data-kumo-part="option"]) { + min-height: 2.25rem; + cursor: pointer; + outline: none; + border-radius: calc(var(--radius-md) - 2px); + padding: 0.5rem 0.625rem; + color: var(--foreground); + font-size: 0.875rem; +} + +:where([data-kumo-component="Select"][data-kumo-part="option"][data-highlighted]), +:where([data-kumo-component="Select"][data-kumo-part="option"]):hover { + background: var(--secondary); + color: var(--secondary-foreground); +} + +:where([data-kumo-component="Select"][data-kumo-part="option"][data-selected]) { + color: var(--primary); + font-weight: 600; +} + +.teatea-checkbox :where([data-kumo-component="Checkbox"]) { + border: 1px solid var(--input); + border-radius: var(--radius-sm); + background: var(--card); + color: var(--primary-foreground); +} + +.teatea-badge { + border-radius: var(--radius-sm); + font-weight: 600; +} + +.teatea-card { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--card); + color: var(--card-foreground); + box-shadow: + 0 1px 2px color-mix(in oklch, var(--foreground) 6%, transparent), + 0 0 0 1px color-mix(in oklch, var(--primary) 4%, transparent); +} + +.teatea-card-header { + background: color-mix(in oklch, var(--secondary) 18%, var(--card)); +} + +.teatea-table { + width: 100%; + border-collapse: collapse; + color: var(--foreground); +} + +.teatea-table :where(thead) { + border-bottom: 1px solid var(--border); + background: var(--secondary); +} + +.teatea-table :where(th) { + color: var(--muted-foreground); + font-weight: 600; +} + +.teatea-table :where(tbody tr:hover) { + background: color-mix(in oklch, var(--secondary) 42%, transparent); +} + +.teatea-dialog { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--card); + color: var(--foreground); + box-shadow: + 0 26px 80px color-mix(in oklch, var(--foreground) 16%, transparent), + 0 0 0 1px color-mix(in oklch, var(--primary) 10%, transparent); +} + .loading-progress { width: 55%; animation: loading-progress 900ms ease-in-out infinite; diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index f31587c..633db80 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -1,6 +1,8 @@ import * as React from "react"; import { Badge as KumoBadge } from "@cloudflare/kumo/components/badge"; +import { cn } from "@/lib/utils"; + type KumoBadgeProps = React.ComponentProps; type LegacyBadgeVariant = "default" | "secondary" | "outline" | "success" | "warning" | "danger"; @@ -19,8 +21,8 @@ export type BadgeProps = Omit & { children?: React.ReactNode; }; -export function Badge({ variant = "default", ...props }: BadgeProps): React.ReactElement { +export function Badge({ className, variant = "default", ...props }: BadgeProps): React.ReactElement { const mappedVariant = variant in variantMap ? variantMap[variant as LegacyBadgeVariant] : (variant as KumoBadgeProps["variant"]); - return ; + return ; } diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 396bc2b..2291a03 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -6,6 +6,8 @@ import { type LinkButtonProps as KumoLinkButtonProps, } from "@cloudflare/kumo/components/button"; +import { cn } from "@/lib/utils"; + type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive"; type ButtonSize = "default" | "sm" | "lg" | "icon"; @@ -35,10 +37,11 @@ export type LinkButtonProps = Omit & { }; export const Button = React.forwardRef(function Button( - { variant = "default", size = "default", ...props }, + { className, variant = "default", size = "default", ...props }, ref, ): React.ReactElement { const kumoProps = { + className: cn("teatea-button", `teatea-button-${variant}`, className), ref, shape: size === "icon" ? "square" : "base", size: sizeMap[size], @@ -52,8 +55,16 @@ export const Button = React.forwardRef(function }); export const LinkButton = React.forwardRef(function LinkButton( - { variant = "default", size = "default", ...props }, + { className, variant = "default", size = "default", ...props }, ref, ): React.ReactElement { - return ; + return ( + + ); }); diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 2b747b2..0510ad6 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -6,7 +6,7 @@ export function Card({ className, ...props }: React.HTMLAttributes): React.ReactElement { - return
; + return
; } export function CardTitle({ className, ...props }: React.HTMLAttributes): React.ReactElement { diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx index 7f031f0..d5b0108 100644 --- a/components/ui/checkbox.tsx +++ b/components/ui/checkbox.tsx @@ -3,6 +3,8 @@ import * as React from "react"; import { Checkbox as KumoCheckbox, type CheckboxProps as KumoCheckboxProps } from "@cloudflare/kumo/components/checkbox"; +import { cn } from "@/lib/utils"; + type CheckboxProps = Omit & { checked?: boolean; defaultChecked?: boolean; @@ -11,6 +13,7 @@ type CheckboxProps = Omit & { export function Checkbox({ checked, + className, defaultChecked = false, name, onCheckedChange, @@ -21,9 +24,10 @@ export function Checkbox({ const currentChecked = isControlled ? checked : uncontrolledChecked; return ( - <> + { const booleanValue = Boolean(nextChecked); if (!isControlled) { @@ -34,6 +38,6 @@ export function Checkbox({ {...props} /> {name && currentChecked ? : null} - + ); } diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index 3e95d29..45156c8 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -40,7 +40,7 @@ export function Dialog({ > (function Inp { className, ...props }, ref, ): React.ReactElement { - return ; + return ; }); export const Textarea = React.forwardRef(function Textarea( { className, ...props }, ref, ): React.ReactElement { - return ; + return ; }); diff --git a/components/ui/select.tsx b/components/ui/select.tsx index ddf9026..ff63d5d 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -37,6 +37,7 @@ export function Select({ return ( className={cn( + "teatea-select", "min-h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-left text-sm font-medium text-foreground shadow-sm", "transition-colors hover:border-primary/45 hover:bg-secondary/35", "focus-visible:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/25", diff --git a/components/ui/table.tsx b/components/ui/table.tsx index 0a010f3..83f3e8e 100644 --- a/components/ui/table.tsx +++ b/components/ui/table.tsx @@ -47,7 +47,7 @@ const TableRoot = React.forwardRef(function Table( return ( );