fix: harden kumo base component styling
This commit is contained in:
203
app/globals.css
203
app/globals.css
@@ -1,6 +1,8 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "@cloudflare/kumo/styles";
|
@import "@cloudflare/kumo/styles";
|
||||||
|
|
||||||
|
@source "../node_modules/@cloudflare/kumo/dist";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -138,6 +140,207 @@ a,
|
|||||||
z-index: 40;
|
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 {
|
.loading-progress {
|
||||||
width: 55%;
|
width: 55%;
|
||||||
animation: loading-progress 900ms ease-in-out infinite;
|
animation: loading-progress 900ms ease-in-out infinite;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Badge as KumoBadge } from "@cloudflare/kumo/components/badge";
|
import { Badge as KumoBadge } from "@cloudflare/kumo/components/badge";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type KumoBadgeProps = React.ComponentProps<typeof KumoBadge>;
|
type KumoBadgeProps = React.ComponentProps<typeof KumoBadge>;
|
||||||
|
|
||||||
type LegacyBadgeVariant = "default" | "secondary" | "outline" | "success" | "warning" | "danger";
|
type LegacyBadgeVariant = "default" | "secondary" | "outline" | "success" | "warning" | "danger";
|
||||||
@@ -19,8 +21,8 @@ export type BadgeProps = Omit<KumoBadgeProps, "variant" | "children"> & {
|
|||||||
children?: React.ReactNode;
|
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"]);
|
const mappedVariant = variant in variantMap ? variantMap[variant as LegacyBadgeVariant] : (variant as KumoBadgeProps["variant"]);
|
||||||
|
|
||||||
return <KumoBadge variant={mappedVariant} {...(props as KumoBadgeProps)} />;
|
return <KumoBadge className={cn("teatea-badge", className)} variant={mappedVariant} {...(props as KumoBadgeProps)} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
type LinkButtonProps as KumoLinkButtonProps,
|
type LinkButtonProps as KumoLinkButtonProps,
|
||||||
} from "@cloudflare/kumo/components/button";
|
} from "@cloudflare/kumo/components/button";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive";
|
type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive";
|
||||||
type ButtonSize = "default" | "sm" | "lg" | "icon";
|
type ButtonSize = "default" | "sm" | "lg" | "icon";
|
||||||
|
|
||||||
@@ -35,10 +37,11 @@ export type LinkButtonProps = Omit<KumoLinkButtonProps, "variant" | "size"> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||||
{ variant = "default", size = "default", ...props },
|
{ className, variant = "default", size = "default", ...props },
|
||||||
ref,
|
ref,
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
const kumoProps = {
|
const kumoProps = {
|
||||||
|
className: cn("teatea-button", `teatea-button-${variant}`, className),
|
||||||
ref,
|
ref,
|
||||||
shape: size === "icon" ? "square" : "base",
|
shape: size === "icon" ? "square" : "base",
|
||||||
size: sizeMap[size],
|
size: sizeMap[size],
|
||||||
@@ -52,8 +55,16 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>(function LinkButton(
|
export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>(function LinkButton(
|
||||||
{ variant = "default", size = "default", ...props },
|
{ className, variant = "default", size = "default", ...props },
|
||||||
ref,
|
ref,
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
return <KumoLinkButton ref={ref} size={sizeMap[size]} variant={variantMap[variant]} {...props} />;
|
return (
|
||||||
|
<KumoLinkButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn("teatea-button", `teatea-button-${variant}`, className)}
|
||||||
|
size={sizeMap[size]}
|
||||||
|
variant={variantMap[variant]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElemen
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"overflow-hidden rounded-lg border border-kumo-line bg-kumo-base text-card-foreground shadow-sm",
|
"teatea-card overflow-hidden rounded-lg border border-kumo-line bg-kumo-base text-card-foreground shadow-sm",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -15,7 +15,7 @@ export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||||
return <div className={cn("flex flex-col gap-1.5 bg-kumo-elevated p-5", className)} {...props} />;
|
return <div className={cn("teatea-card-header flex flex-col gap-1.5 bg-kumo-elevated p-5", className)} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): React.ReactElement {
|
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): React.ReactElement {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Checkbox as KumoCheckbox, type CheckboxProps as KumoCheckboxProps } from "@cloudflare/kumo/components/checkbox";
|
import { Checkbox as KumoCheckbox, type CheckboxProps as KumoCheckboxProps } from "@cloudflare/kumo/components/checkbox";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type CheckboxProps = Omit<KumoCheckboxProps, "checked" | "onCheckedChange"> & {
|
type CheckboxProps = Omit<KumoCheckboxProps, "checked" | "onCheckedChange"> & {
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
defaultChecked?: boolean;
|
defaultChecked?: boolean;
|
||||||
@@ -11,6 +13,7 @@ type CheckboxProps = Omit<KumoCheckboxProps, "checked" | "onCheckedChange"> & {
|
|||||||
|
|
||||||
export function Checkbox({
|
export function Checkbox({
|
||||||
checked,
|
checked,
|
||||||
|
className,
|
||||||
defaultChecked = false,
|
defaultChecked = false,
|
||||||
name,
|
name,
|
||||||
onCheckedChange,
|
onCheckedChange,
|
||||||
@@ -21,9 +24,10 @@ export function Checkbox({
|
|||||||
const currentChecked = isControlled ? checked : uncontrolledChecked;
|
const currentChecked = isControlled ? checked : uncontrolledChecked;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<span className="teatea-checkbox contents">
|
||||||
<KumoCheckbox
|
<KumoCheckbox
|
||||||
checked={currentChecked}
|
checked={currentChecked}
|
||||||
|
className={cn("teatea-checkbox-control", className)}
|
||||||
onCheckedChange={(nextChecked) => {
|
onCheckedChange={(nextChecked) => {
|
||||||
const booleanValue = Boolean(nextChecked);
|
const booleanValue = Boolean(nextChecked);
|
||||||
if (!isControlled) {
|
if (!isControlled) {
|
||||||
@@ -34,6 +38,6 @@ export function Checkbox({
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
{name && currentChecked ? <input name={name} type="hidden" value="on" /> : null}
|
{name && currentChecked ? <input name={name} type="hidden" value="on" /> : null}
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function Dialog({
|
|||||||
>
|
>
|
||||||
<KumoDialog
|
<KumoDialog
|
||||||
className={cn(
|
className={cn(
|
||||||
"dialog-panel-enter fixed left-1/2 top-1/2 z-50 flex max-h-[min(92svh,52rem)] w-[min(calc(100vw-1.5rem),48rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border border-kumo-line bg-kumo-base p-0 shadow-xl outline-none ring-1 ring-kumo-line",
|
"teatea-dialog dialog-panel-enter fixed left-1/2 top-1/2 z-50 flex max-h-[min(92svh,52rem)] w-[min(calc(100vw-1.5rem),48rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border border-kumo-line bg-kumo-base p-0 shadow-xl outline-none ring-1 ring-kumo-line",
|
||||||
"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0",
|
"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Inp
|
|||||||
{ className, ...props },
|
{ className, ...props },
|
||||||
ref,
|
ref,
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
return <KumoInput ref={ref} className={cn("w-full", className)} {...props} />;
|
return <KumoInput ref={ref} className={cn("teatea-control w-full", className)} {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
|
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
|
||||||
{ className, ...props },
|
{ className, ...props },
|
||||||
ref,
|
ref,
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
return <KumoTextarea ref={ref} className={cn("min-h-24 w-full", className)} {...props} />;
|
return <KumoTextarea ref={ref} className={cn("teatea-control min-h-24 w-full", className)} {...props} />;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export function Select({
|
|||||||
return (
|
return (
|
||||||
<KumoSelect<string>
|
<KumoSelect<string>
|
||||||
className={cn(
|
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",
|
"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",
|
"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",
|
"focus-visible:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/25",
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const TableRoot = React.forwardRef<HTMLTableElement, TableProps>(function Table(
|
|||||||
return (
|
return (
|
||||||
<table
|
<table
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("border-collapse text-kumo-default", layout === "fixed" && "table-fixed", className)}
|
className={cn("teatea-table border-collapse text-kumo-default", layout === "fixed" && "table-fixed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user