refactor(component): remove as-child

This commit is contained in:
Tianen Pang 2025-12-08 14:18:28 +08:00
parent 8900d17479
commit 3063bf3637
No known key found for this signature in database
GPG Key ID: 84C345E7CC5DF12F
23 changed files with 161 additions and 408 deletions

View File

@ -55,7 +55,7 @@ const AccordionRoot = ({children, className, ...originalProps}: AccordionRootPro
<AccordionContext value={{slots}}>
{variant === "surface" ? (
// Allows inner components to apply "on-surface" colors for proper contrast
<SurfaceContext.Provider value={{variant: "default"}}>{content}</SurfaceContext.Provider>
<SurfaceContext value={{variant: "default"}}>{content}</SurfaceContext>
) : (
content
)}

View File

@ -1,13 +1,12 @@
"use client";
import type {AlertDialogVariants} from "./alert-dialog.styles";
import type {ComponentPropsWithRef, HTMLAttributes, ReactNode} from "react";
import type {ComponentPropsWithRef, HTMLAttributes} from "react";
import type {
ButtonProps as ButtonPrimitiveProps,
DialogProps as DialogPrimitiveProps,
} from "react-aria-components";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import {createContext, useContext, useMemo} from "react";
import {
DialogTrigger as AlertDialogTriggerPrimitive,
@ -18,7 +17,6 @@ import {
Pressable as PressablePrimitive,
} from "react-aria-components";
import {isNotAsChild} from "../../utils";
import {composeTwRenderProps} from "../../utils/compose";
import {CloseButton} from "../close-button";
import {DangerIcon, InfoIcon, SuccessIcon, WarningIcon} from "../icons";
@ -287,41 +285,18 @@ const AlertDialogIcon = ({
/* -------------------------------------------------------------------------------------------------
* AlertDialog Close Trigger
* -----------------------------------------------------------------------------------------------*/
interface AlertDialogCloseTriggerProps {
asChild?: boolean;
className?: string;
children?: ReactNode;
}
interface AlertDialogCloseTriggerProps extends ButtonPrimitiveProps {}
interface AlertDialogCloseTrigger {
(props: {asChild: true} & ComponentPropsWithRef<"button">): React.JSX.Element;
(props: {asChild?: false} & ButtonPrimitiveProps): React.JSX.Element;
}
const AlertDialogCloseTrigger: AlertDialogCloseTrigger = (props) => {
const AlertDialogCloseTrigger = ({className, ...rest}: AlertDialogCloseTriggerProps) => {
const {slots} = useContext(AlertDialogContext);
if (isNotAsChild(props)) {
const {className, ...rest} = props;
return (
<CloseButton
className={composeTwRenderProps(className, slots?.closeTrigger())}
data-slot="alert-dialog-close-trigger"
slot="close"
{...rest}
/>
);
}
const {asChild: _asChild, children, className, ...rest} = props;
return (
<SlotPrimitive data-slot="alert-dialog-close-trigger" slot="close" {...rest}>
{children ?? (
<CloseButton className={composeTwRenderProps(className, slots?.closeTrigger())} />
)}
</SlotPrimitive>
<CloseButton
className={composeTwRenderProps(className, slots?.closeTrigger())}
data-slot="alert-dialog-close-trigger"
slot="close"
{...rest}
/>
);
};

View File

@ -4,7 +4,6 @@ import type {AlertVariants} from "./alert.styles";
import type {SurfaceVariants} from "../surface";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import React, {createContext, useContext} from "react";
import {DangerIcon, InfoIcon, SuccessIcon, WarningIcon} from "../icons";
@ -25,25 +24,22 @@ const AlertContext = createContext<AlertContext>({});
/* ------------------------------------------------------------------------------------------------
* Alert Root
* --------------------------------------------------------------------------------------------- */
interface AlertRootProps extends ComponentPropsWithRef<"div">, AlertVariants {
asChild?: boolean;
}
interface AlertRootProps extends ComponentPropsWithRef<"div">, AlertVariants {}
const AlertRoot = ({asChild, children, className, status, ...rest}: AlertRootProps) => {
const AlertRoot = ({children, className, status, ...rest}: AlertRootProps) => {
const slots = React.useMemo(() => alertVariants({status}), [status]);
const Component = asChild ? SlotPrimitive : "div";
return (
<AlertContext value={{slots, status}}>
<SurfaceContext.Provider
<SurfaceContext
value={{
variant: "default" as SurfaceVariants["variant"],
}}
>
<Component className={slots?.base({className})} data-slot="alert-root" {...rest}>
<div className={slots?.base({className})} data-slot="alert-root" {...rest}>
{children}
</Component>
</SurfaceContext.Provider>
</div>
</SurfaceContext>
</AlertContext>
);
};
@ -51,13 +47,10 @@ const AlertRoot = ({asChild, children, className, status, ...rest}: AlertRootPro
/* ------------------------------------------------------------------------------------------------
* Alert Indicator
* --------------------------------------------------------------------------------------------- */
type AlertIndicatorProps = ComponentPropsWithRef<"div"> & {
asChild?: boolean;
};
type AlertIndicatorProps = ComponentPropsWithRef<"div">;
const AlertIndicator = ({asChild, children, className, ...rest}: AlertIndicatorProps) => {
const AlertIndicator = ({children, className, ...rest}: AlertIndicatorProps) => {
const {slots, status} = useContext(AlertContext);
const Component = asChild ? SlotPrimitive : "div";
// Map status to default icons
const getDefaultIcon = () => {
@ -76,63 +69,54 @@ const AlertIndicator = ({asChild, children, className, ...rest}: AlertIndicatorP
};
return (
<Component className={slots?.indicator({className})} data-slot="alert-indicator" {...rest}>
<div className={slots?.indicator({className})} data-slot="alert-indicator" {...rest}>
{children ?? getDefaultIcon()}
</Component>
</div>
);
};
/* ------------------------------------------------------------------------------------------------
* Alert Content
* --------------------------------------------------------------------------------------------- */
type AlertContentProps = ComponentPropsWithRef<"div"> & {
asChild?: boolean;
};
type AlertContentProps = ComponentPropsWithRef<"div">;
const AlertContent = ({asChild, children, className, ...rest}: AlertContentProps) => {
const AlertContent = ({children, className, ...rest}: AlertContentProps) => {
const {slots} = useContext(AlertContext);
const Component = asChild ? SlotPrimitive : "div";
return (
<Component className={slots?.content({className})} data-slot="alert-content" {...rest}>
<div className={slots?.content({className})} data-slot="alert-content" {...rest}>
{children}
</Component>
</div>
);
};
/* ------------------------------------------------------------------------------------------------
* Alert Title
* --------------------------------------------------------------------------------------------- */
type AlertTitleProps = ComponentPropsWithRef<"p"> & {
asChild?: boolean;
};
type AlertTitleProps = ComponentPropsWithRef<"p">;
const AlertTitle = ({asChild, children, className, ...rest}: AlertTitleProps) => {
const AlertTitle = ({children, className, ...rest}: AlertTitleProps) => {
const {slots} = useContext(AlertContext);
const Component = asChild ? SlotPrimitive : "p";
return (
<Component className={slots?.title({className})} data-slot="alert-title" {...rest}>
<p className={slots?.title({className})} data-slot="alert-title" {...rest}>
{children}
</Component>
</p>
);
};
/* ------------------------------------------------------------------------------------------------
* Alert Description
* --------------------------------------------------------------------------------------------- */
type AlertDescriptionProps = ComponentPropsWithRef<"span"> & {
asChild?: boolean;
};
type AlertDescriptionProps = ComponentPropsWithRef<"span">;
const AlertDescription = ({asChild, children, className, ...rest}: AlertDescriptionProps) => {
const AlertDescription = ({children, className, ...rest}: AlertDescriptionProps) => {
const {slots} = useContext(AlertContext);
const Component = asChild ? SlotPrimitive : "span";
return (
<Component className={slots?.description({className})} data-slot="alert-description" {...rest}>
<span className={slots?.description({className})} data-slot="alert-description" {...rest}>
{children}
</Component>
</span>
);
};

View File

@ -5,7 +5,6 @@ import type {UseImageProps} from "../../hooks";
import type {ComponentPropsWithRef} from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import React, {createContext} from "react";
import {useImage} from "../../hooks";
@ -46,14 +45,12 @@ const AvatarRoot = ({children, className, color, size, variant, ...props}: Avata
* -----------------------------------------------------------------------------------------------*/
interface AvatarImageProps
extends Omit<ComponentPropsWithRef<typeof AvatarPrimitive.Image>, "onLoadingStatusChange"> {
asChild?: boolean;
ignoreFallback?: UseImageProps["ignoreFallback"];
shouldBypassImageLoad?: UseImageProps["shouldBypassImageLoad"];
onLoadingStatusChange?: UseImageProps["onLoadingStatusChange"];
}
const AvatarImage = ({
asChild = false,
className,
crossOrigin,
ignoreFallback,
@ -68,11 +65,6 @@ const AvatarImage = ({
...props
}: AvatarImageProps) => {
const {slots} = React.useContext(AvatarContext);
const Comp = asChild ? SlotPrimitive : AvatarPrimitive.Image;
if (asChild) {
return <Comp className={slots?.image({className})} {...props} data-loaded />;
}
const loadingStatus = useImage({
src: typeof src === "string" ? src : undefined,
@ -88,7 +80,7 @@ const AvatarImage = ({
});
return (
<Comp
<AvatarPrimitive.Image
className={slots?.image({className})}
crossOrigin={crossOrigin}
data-loaded={dataAttr(loadingStatus === "loaded")}

View File

@ -5,7 +5,7 @@ import React, {useState} from "react";
import {Spinner} from "../spinner";
import {Button} from "./index";
import {Button, buttonVariants} from "./index";
export default {
argTypes: {
@ -55,13 +55,18 @@ const Template = ({isDisabled, size}: Button["RootProps"]) => (
</div>
);
const TemplateWithAsChild = ({isDisabled, size}: Button["RootProps"]) => (
<div className="flex gap-3">
<Button asChild isDisabled={isDisabled} size={size}>
<a href="https://www.google.com" rel="noopener noreferrer" target="_blank">
const TemplateWithLinkButton = ({isIconOnly, size, variant}: Button["RootProps"]) => (
<div className="flex flex-col gap-3">
<div className="flex gap-3">
<a
className={buttonVariants({size, variant, isIconOnly})}
href="https://www.google.com"
rel="noopener noreferrer"
target="_blank"
>
Google
</a>
</Button>
</div>
</div>
);
@ -190,9 +195,9 @@ export const Default = {
render: Template,
};
export const WithAsChild = {
export const WithLinkButton = {
args: defaultArgs,
render: TemplateWithAsChild,
render: TemplateWithLinkButton,
};
export const Sizes = {

View File

@ -3,7 +3,6 @@
import type {ButtonVariants} from "./button.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import {Button as ButtonPrimitive} from "react-aria-components";
import {composeTwRenderProps} from "../../utils";
@ -13,12 +12,9 @@ import {buttonVariants} from "./button.styles";
/* -------------------------------------------------------------------------------------------------
* Button Root
* -----------------------------------------------------------------------------------------------*/
interface ButtonRootProps extends ComponentPropsWithRef<typeof ButtonPrimitive>, ButtonVariants {
asChild?: boolean;
}
interface ButtonRootProps extends ComponentPropsWithRef<typeof ButtonPrimitive>, ButtonVariants {}
const ButtonRoot = ({
asChild,
children,
className,
isIconOnly,
@ -35,19 +31,6 @@ const ButtonRoot = ({
class: typeof className === "string" ? className : undefined,
});
if (asChild) {
return (
<SlotPrimitive
className={styles}
slot={slot as string}
style={style as React.CSSProperties}
{...rest}
>
{typeof children === "function" ? children({} as any) : children}
</SlotPrimitive>
);
}
return (
<ButtonPrimitive
className={composeTwRenderProps(className, styles)}

View File

@ -4,7 +4,6 @@ import type {CardVariants} from "./card.styles";
import type {SurfaceVariants} from "../surface";
import type {ComponentPropsWithRef} from "react";
import {Slot} from "@radix-ui/react-slot";
import React, {createContext, useContext} from "react";
import {SurfaceContext} from "../surface";
@ -22,24 +21,15 @@ const CardContext = createContext<CardContext>({});
/* -------------------------------------------------------------------------------------------------
* Card Root
* -----------------------------------------------------------------------------------------------*/
interface CardRootProps extends ComponentPropsWithRef<"div">, CardVariants {
asChild?: boolean;
}
interface CardRootProps extends ComponentPropsWithRef<"div">, CardVariants {}
const CardRoot = ({
asChild = false,
children,
className,
variant = "default",
...props
}: CardRootProps) => {
const CardRoot = ({children, className, variant = "default", ...props}: CardRootProps) => {
const slots = React.useMemo(() => cardVariants({variant}), [variant]);
const Comp = asChild ? Slot : "div";
const content = (
<Comp className={slots.base({className})} data-slot="card" {...props}>
<div className={slots.base({className})} data-slot="card" {...props}>
{children}
</Comp>
</div>
);
return (
@ -48,13 +38,13 @@ const CardRoot = ({
content
) : (
// Allows inner components to apply "on-surface" colors for proper contrast
<SurfaceContext.Provider
<SurfaceContext
value={{
variant: variant as SurfaceVariants["variant"],
}}
>
{content}
</SurfaceContext.Provider>
</SurfaceContext>
)}
</CardContext>
);
@ -63,73 +53,64 @@ const CardRoot = ({
/* -------------------------------------------------------------------------------------------------
* Card Header
* -----------------------------------------------------------------------------------------------*/
interface CardHeaderProps extends ComponentPropsWithRef<"div"> {
asChild?: boolean;
}
interface CardHeaderProps extends ComponentPropsWithRef<"div"> {}
const CardHeader = ({asChild = false, className, ...props}: CardHeaderProps) => {
const CardHeader = ({className, ...props}: CardHeaderProps) => {
const {slots} = useContext(CardContext);
const Comp = asChild ? Slot : "div";
return <Comp className={slots?.header({className})} data-slot="card-header" {...props} />;
return <div className={slots?.header({className})} data-slot="card-header" {...props} />;
};
/* -------------------------------------------------------------------------------------------------
* Card Title
* -----------------------------------------------------------------------------------------------*/
interface CardTitleProps extends ComponentPropsWithRef<"h3"> {
asChild?: boolean;
}
interface CardTitleProps extends ComponentPropsWithRef<"h3"> {}
const CardTitle = ({asChild = false, className, ...props}: CardTitleProps) => {
const CardTitle = ({children, className, ...props}: CardTitleProps) => {
const {slots} = useContext(CardContext);
const Comp = asChild ? Slot : "h3";
return <Comp className={slots?.title({className})} data-slot="card-title" {...props} />;
return (
<h3 className={slots?.title({className})} data-slot="card-title" {...props}>
{children}
</h3>
);
};
/* -------------------------------------------------------------------------------------------------
* Card Description
* -----------------------------------------------------------------------------------------------*/
interface CardDescriptionProps extends ComponentPropsWithRef<"p"> {
asChild?: boolean;
}
interface CardDescriptionProps extends ComponentPropsWithRef<"p"> {}
const CardDescription = ({asChild = false, className, ...props}: CardDescriptionProps) => {
const CardDescription = ({children, className, ...props}: CardDescriptionProps) => {
const {slots} = useContext(CardContext);
const Comp = asChild ? Slot : "p";
return (
<Comp className={slots?.description({className})} data-slot="card-description" {...props} />
<p className={slots?.description({className})} data-slot="card-description" {...props}>
{children}
</p>
);
};
/* -------------------------------------------------------------------------------------------------
* Card Content
* -----------------------------------------------------------------------------------------------*/
interface CardContentProps extends ComponentPropsWithRef<"div"> {
asChild?: boolean;
}
interface CardContentProps extends ComponentPropsWithRef<"div"> {}
const CardContent = ({asChild = false, className, ...props}: CardContentProps) => {
const CardContent = ({className, ...props}: CardContentProps) => {
const {slots} = useContext(CardContext);
const Comp = asChild ? Slot : "div";
return <Comp className={slots?.content({className})} data-slot="card-content" {...props} />;
return <div className={slots?.content({className})} data-slot="card-content" {...props} />;
};
/* -------------------------------------------------------------------------------------------------
* Card Footer
* -----------------------------------------------------------------------------------------------*/
interface CardFooterProps extends ComponentPropsWithRef<"div"> {
asChild?: boolean;
}
interface CardFooterProps extends ComponentPropsWithRef<"div"> {}
const CardFooter = ({asChild = false, className, ...props}: CardFooterProps) => {
const CardFooter = ({className, ...props}: CardFooterProps) => {
const {slots} = useContext(CardContext);
const Comp = asChild ? Slot : "div";
return <Comp className={slots?.footer({className})} data-slot="card-footer" {...props} />;
return <div className={slots?.footer({className})} data-slot="card-footer" {...props} />;
};
/* -------------------------------------------------------------------------------------------------

View File

@ -3,8 +3,6 @@
import type {ChipVariants} from "./chip.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import {chipVariants} from "./chip.styles";
/* -------------------------------------------------------------------------------------------------
@ -13,24 +11,13 @@ import {chipVariants} from "./chip.styles";
interface ChipRootProps extends Omit<ComponentPropsWithRef<"div">, "type" | "color">, ChipVariants {
className?: string;
children: React.ReactNode;
asChild?: boolean;
}
const ChipRoot = ({
asChild = false,
children,
className,
color,
size,
variant,
...props
}: ChipRootProps) => {
const Comp = asChild ? SlotPrimitive : "span";
const ChipRoot = ({children, className, color, size, variant, ...props}: ChipRootProps) => {
return (
<Comp {...props} className={chipVariants({className, size, color, variant})}>
<span {...props} className={chipVariants({className, size, color, variant})}>
{children}
</Comp>
</span>
);
};

View File

@ -3,7 +3,7 @@
import type {CloseButtonVariants} from "./close-button.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import {useMemo} from "react";
import {Button as ButtonPrimitive} from "react-aria-components";
import {composeTwRenderProps} from "../../utils";
@ -14,14 +14,9 @@ import {closeButtonVariants} from "./close-button.styles";
/* -------------------------------------------------------------------------------------------------
* Close Button Root
* -----------------------------------------------------------------------------------------------*/
interface CloseButtonRootProps
extends ComponentPropsWithRef<typeof ButtonPrimitive>,
CloseButtonVariants {
asChild?: boolean;
}
type CloseButtonRootProps = ComponentPropsWithRef<typeof ButtonPrimitive> & CloseButtonVariants;
const CloseButtonRoot = ({
asChild,
children,
className,
slot,
@ -29,24 +24,14 @@ const CloseButtonRoot = ({
variant,
...rest
}: CloseButtonRootProps) => {
const styles = closeButtonVariants({
variant,
class: typeof className === "string" ? className : undefined,
});
if (asChild) {
return (
<SlotPrimitive
className={styles}
data-slot="close-button"
slot={slot as string}
style={style as React.CSSProperties}
{...rest}
>
{typeof children === "function" ? children({} as any) : children}
</SlotPrimitive>
);
}
const styles = useMemo(
() =>
closeButtonVariants({
variant,
className: typeof className === "string" ? className : undefined,
}),
[variant, className],
);
return (
<ButtonPrimitive

View File

@ -5,7 +5,6 @@ import type {SurfaceVariants} from "../surface";
import type {ComponentPropsWithRef, ReactNode} from "react";
import type {ButtonProps} from "react-aria-components";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import React, {createContext, useContext} from "react";
import {
Button,
@ -16,7 +15,6 @@ import {
import {dataAttr} from "../../utils/assertion";
import {composeTwRenderProps} from "../../utils/compose";
import {isNotAsChild} from "../../utils/props";
import {IconChevronDown} from "../icons";
import {SurfaceContext} from "../surface";
@ -48,7 +46,7 @@ const ComboBoxRoot = <T extends object = object>({
const slots = React.useMemo(() => comboboxVariants(), []);
return (
<ComboBoxContext.Provider value={{slots}}>
<ComboBoxContext value={{slots}}>
<ComboBoxPrimitive
data-slot="combobox"
{...props}
@ -56,7 +54,7 @@ const ComboBoxRoot = <T extends object = object>({
>
{(values) => <>{typeof children === "function" ? children(values) : children}</>}
</ComboBoxPrimitive>
</ComboBoxContext.Provider>
</ComboBoxContext>
);
};
@ -79,50 +77,24 @@ const ComboBoxInputGroup = ({children, className, ...props}: ComboBoxInputGroupP
/* -------------------------------------------------------------------------------------------------
* ComboBox Trigger
* -----------------------------------------------------------------------------------------------*/
interface ComboBoxTriggerProps {
asChild?: boolean;
interface ComboBoxTriggerProps extends ButtonProps {
className?: string;
children?: ReactNode;
}
interface ComboBoxTrigger {
(props: {asChild: true} & ComponentPropsWithRef<"button">): React.JSX.Element;
(props: {asChild?: false} & ButtonProps): React.JSX.Element;
}
const ComboBoxTrigger: ComboBoxTrigger = (props) => {
const ComboBoxTrigger = ({children, className, ...rest}: ComboBoxTriggerProps) => {
const {slots} = useContext(ComboBoxContext);
const state = useContext(ComboBoxStateContext);
if (isNotAsChild(props)) {
const {children, className, ...rest} = props;
return (
<Button
className={composeTwRenderProps(className, slots?.trigger())}
data-open={dataAttr(state?.isOpen)}
data-slot="combobox-trigger"
{...rest}
>
{children ?? <IconChevronDown data-slot="combobox-trigger-default-icon" />}
</Button>
);
}
const {asChild: _asChild, children, className, ...rest} = props;
return (
<SlotPrimitive data-open={dataAttr(state?.isOpen)} data-slot="combobox-trigger" {...rest}>
{children ?? (
<Button
className={composeTwRenderProps(className, slots?.trigger())}
data-open={dataAttr(state?.isOpen)}
data-slot="combobox-trigger-button"
>
<IconChevronDown data-slot="combobox-trigger-default-icon" />
</Button>
)}
</SlotPrimitive>
<Button
className={composeTwRenderProps(className, slots?.trigger())}
data-open={dataAttr(state?.isOpen)}
data-slot="combobox-trigger"
{...rest}
>
{children ?? <IconChevronDown data-slot="combobox-trigger-default-icon" />}
</Button>
);
};
@ -143,7 +115,7 @@ const ComboBoxPopover = ({
const {slots} = useContext(ComboBoxContext);
return (
<SurfaceContext.Provider
<SurfaceContext
value={{
variant: "default" as SurfaceVariants["variant"],
}}
@ -155,7 +127,7 @@ const ComboBoxPopover = ({
>
{children}
</PopoverPrimitive>
</SurfaceContext.Provider>
</SurfaceContext>
);
};

View File

@ -42,9 +42,9 @@ const DropdownRoot = ({children, ...props}: DropdownRootProps) => {
const slots = React.useMemo(() => dropdownVariants(), []);
return (
<DropdownContext.Provider value={{slots}}>
<DropdownContext value={{slots}}>
<MenuTriggerPrimitive {...props}>{children}</MenuTriggerPrimitive>
</DropdownContext.Provider>
</DropdownContext>
);
};
@ -80,7 +80,7 @@ const DropdownPopover = ({children, className, placement, ...props}: DropdownPop
const {slots} = useContext(DropdownContext);
return (
<SurfaceContext.Provider
<SurfaceContext
value={{
variant: "default" as SurfaceVariants["variant"],
}}
@ -92,7 +92,7 @@ const DropdownPopover = ({children, className, placement, ...props}: DropdownPop
>
{children}
</PopoverPrimitive>
</SurfaceContext.Provider>
</SurfaceContext>
);
};

View File

@ -3,7 +3,6 @@
import type {FieldsetVariants} from "./fieldset.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot} from "@radix-ui/react-slot";
import React, {createContext, useContext} from "react";
import {fieldsetVariants} from "./fieldset.styles";
@ -20,18 +19,14 @@ const FieldsetContext = createContext<FieldsetContext>({});
/* -------------------------------------------------------------------------------------------------
* Fieldset Root
* -----------------------------------------------------------------------------------------------*/
interface FieldsetRootProps extends ComponentPropsWithRef<"fieldset">, FieldsetVariants {
asChild?: boolean;
}
const FieldsetRoot = ({asChild = false, className, ...props}: FieldsetRootProps) => {
const Comp = asChild ? Slot : "fieldset";
interface FieldsetRootProps extends ComponentPropsWithRef<"fieldset">, FieldsetVariants {}
const FieldsetRoot = ({className, ...props}: FieldsetRootProps) => {
const slots = React.useMemo(() => fieldsetVariants({}), []);
return (
<FieldsetContext value={{slots}}>
<Comp className={slots?.base({className})} data-slot="fieldset" {...props} />
<fieldset className={slots?.base({className})} data-slot="fieldset" {...props} />
</FieldsetContext>
);
};
@ -39,50 +34,39 @@ const FieldsetRoot = ({asChild = false, className, ...props}: FieldsetRootProps)
/* -------------------------------------------------------------------------------------------------
* Fieldset Legend
* -----------------------------------------------------------------------------------------------*/
interface FieldsetLegendProps extends ComponentPropsWithRef<"legend"> {
asChild?: boolean;
}
const FieldsetLegend = ({asChild = false, className, ...props}: FieldsetLegendProps) => {
const Comp = asChild ? Slot : "legend";
interface FieldsetLegendProps extends ComponentPropsWithRef<"legend"> {}
const FieldsetLegend = ({className, ...props}: FieldsetLegendProps) => {
const {slots} = useContext(FieldsetContext);
return <Comp className={slots?.legend({className})} data-slot="fieldset-legend" {...props} />;
return <legend className={slots?.legend({className})} data-slot="fieldset-legend" {...props} />;
};
/* -------------------------------------------------------------------------------------------------
* Field Group
* -----------------------------------------------------------------------------------------------*/
interface FieldGroupProps extends ComponentPropsWithRef<"div"> {
asChild?: boolean;
}
const FieldGroup = ({asChild = false, className, ...rest}: FieldGroupProps) => {
const Comp = asChild ? Slot : "div";
interface FieldGroupProps extends ComponentPropsWithRef<"div"> {}
const FieldGroup = ({className, ...rest}: FieldGroupProps) => {
const {slots} = useContext(FieldsetContext);
return (
<Comp className={slots?.fieldGroup({className})} data-slot="fieldset-field-group" {...rest} />
<div className={slots?.fieldGroup({className})} data-slot="fieldset-field-group" {...rest} />
);
};
/* -------------------------------------------------------------------------------------------------
* Field Actions
* -----------------------------------------------------------------------------------------------*/
interface FieldsetActionsProps extends ComponentPropsWithRef<"div"> {
asChild?: boolean;
}
interface FieldsetActionsProps extends ComponentPropsWithRef<"div"> {}
const FieldsetActions = ({asChild, children, className, ...rest}: FieldsetActionsProps) => {
const Component = asChild ? Slot : "div";
const FieldsetActions = ({children, className, ...rest}: FieldsetActionsProps) => {
const {slots} = useContext(FieldsetContext);
return (
<Component className={slots?.actions({className})} data-slot="fieldset-actions" {...rest}>
<div className={slots?.actions({className})} data-slot="fieldset-actions" {...rest}>
{children}
</Component>
</div>
);
};

View File

@ -42,19 +42,17 @@ const InputGroupRoot = ({children, className, isOnSurface, ...props}: InputGroup
);
return (
<InputGroupContext.Provider value={{slots}}>
<InputGroupContext value={{slots}}>
<GroupPrimitive
{...props}
className={composeTwRenderProps(className, slots?.base())}
data-slot="input-group"
>
{composeRenderProps(children, (children, renderProps) => (
<InputContext.Provider value={{disabled: renderProps.isDisabled}}>
{children}
</InputContext.Provider>
<InputContext value={{disabled: renderProps.isDisabled}}>{children}</InputContext>
))}
</GroupPrimitive>
</InputGroupContext.Provider>
</InputGroupContext>
);
};

View File

@ -73,7 +73,7 @@ const InputOTPRoot = ({
return (
<InputOTPContext value={{slots, isDisabled, isInvalid}}>
<FieldErrorContext.Provider value={validation}>
<FieldErrorContext value={validation}>
<OTPInput
// OTP Input package uses the `className` prop for the actual `input` element which is not visible to the user so no need to pass it to the base container
className={slots.input({className: inputClassName})}
@ -84,7 +84,7 @@ const InputOTPRoot = ({
disabled={isDisabled}
{...props}
/>
</FieldErrorContext.Provider>
</FieldErrorContext>
</InputOTPContext>
);
};

View File

@ -3,7 +3,6 @@
import type {LinkVariants} from "./link.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import React, {createContext, useContext} from "react";
import {Link as LinkPrimitive} from "react-aria-components";
@ -25,44 +24,14 @@ const LinkContext = createContext<LinkContext>({});
/* ------------------------------------------------------------------------------------------------
* Link Root
* --------------------------------------------------------------------------------------------- */
interface LinkRootProps extends ComponentPropsWithRef<typeof LinkPrimitive>, LinkVariants {
asChild?: boolean;
}
interface LinkRootProps extends ComponentPropsWithRef<typeof LinkPrimitive>, LinkVariants {}
const LinkRoot = ({
asChild,
children,
className,
slot,
style,
underline,
underlineOffset,
...props
}: LinkRootProps) => {
const LinkRoot = ({children, className, underline, underlineOffset, ...props}: LinkRootProps) => {
const slots = React.useMemo(
() => linkVariants({underline, underlineOffset}),
[underline, underlineOffset],
);
const styles = slots?.base({
className: typeof className === "string" ? className : undefined,
});
if (asChild) {
return (
<LinkContext value={{slots}}>
<SlotPrimitive
className={styles}
slot={slot as string}
style={style as React.CSSProperties}
{...(props as any)}
>
{typeof children === "function" ? children({} as any) : children}
</SlotPrimitive>
</LinkContext>
);
}
return (
<LinkContext value={{slots}}>
<LinkPrimitive {...props} className={composeTwRenderProps(className, slots?.base())}>
@ -75,23 +44,20 @@ const LinkRoot = ({
/* ------------------------------------------------------------------------------------------------
* Link Icon
* --------------------------------------------------------------------------------------------- */
type LinkIconProps = ComponentPropsWithRef<"span"> & {
asChild?: boolean;
};
type LinkIconProps = ComponentPropsWithRef<"span">;
const LinkIcon = ({asChild, children, className, ...rest}: LinkIconProps) => {
const LinkIcon = ({children, className, ...rest}: LinkIconProps) => {
const {slots} = useContext(LinkContext);
const Component = asChild ? SlotPrimitive : "span";
return (
<Component
<span
className={slots?.icon({className})}
data-default-icon={dataAttr(!children)}
data-slot="link-icon"
{...rest}
>
{children ?? <ExternalLinkIcon data-slot="link-default-icon" />}
</Component>
</span>
);
};

View File

@ -9,7 +9,6 @@ import type {
DialogProps as DialogPrimitiveProps,
} from "react-aria-components";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import {mergeProps} from "@react-aria/utils";
import {createContext, useContext, useMemo} from "react";
import {
@ -21,7 +20,6 @@ import {
Pressable as PressablePrimitive,
} from "react-aria-components";
import {isNotAsChild} from "../../utils";
import {composeTwRenderProps} from "../../utils/compose";
import {CloseButton} from "../close-button";
import {SurfaceContext} from "../surface";
@ -124,7 +122,7 @@ const ModalContainer = ({
);
return (
<ModalContext.Provider value={updatedModalContext}>
<ModalContext value={updatedModalContext}>
<ModalOverlayPrimitive
className={composeTwRenderProps(backdropClassName, updatedSlots?.backdrop())}
data-slot="modal-backdrop"
@ -139,7 +137,7 @@ const ModalContainer = ({
{(renderProps) => (typeof children === "function" ? children(renderProps) : children)}
</ModalPrimitive>
</ModalOverlayPrimitive>
</ModalContext.Provider>
</ModalContext>
);
};
@ -152,15 +150,11 @@ const ModalDialog = ({children, className, ...props}: ModalDialogProps) => {
const {slots} = useContext(ModalContext);
return (
<SurfaceContext.Provider
value={{
variant: "default" as SurfaceVariants["variant"],
}}
>
<SurfaceContext value={{variant: "default" as SurfaceVariants["variant"]}}>
<DialogPrimitive className={slots?.dialog({className})} data-slot="modal-dialog" {...props}>
{children}
</DialogPrimitive>
</SurfaceContext.Provider>
</SurfaceContext>
);
};
@ -247,41 +241,21 @@ const ModalIcon = ({children, className, ...props}: ModalIconProps) => {
/* -------------------------------------------------------------------------------------------------
* Modal Close Trigger
* -----------------------------------------------------------------------------------------------*/
interface ModalCloseTriggerProps {
asChild?: boolean;
interface ModalCloseTriggerProps extends ComponentPropsWithRef<typeof ButtonPrimitive> {
className?: string;
children?: ReactNode;
}
interface ModalCloseTrigger {
(props: {asChild: true} & ComponentPropsWithRef<"button">): React.JSX.Element;
(props: {asChild?: false} & ComponentPropsWithRef<typeof ButtonPrimitive>): React.JSX.Element;
}
const ModalCloseTrigger: ModalCloseTrigger = (props) => {
const ModalCloseTrigger = ({className, ...rest}: ModalCloseTriggerProps) => {
const {slots} = useContext(ModalContext);
if (isNotAsChild(props)) {
const {className, ...rest} = props;
return (
<CloseButton
className={composeTwRenderProps(className, slots?.closeTrigger())}
data-slot="modal-close-trigger"
slot="close"
{...rest}
/>
);
}
const {asChild: _asChild, children, className, ...rest} = props;
return (
<SlotPrimitive data-slot="modal-close-trigger" slot="close" {...rest}>
{children ?? (
<CloseButton className={composeTwRenderProps(className, slots?.closeTrigger())} />
)}
</SlotPrimitive>
<CloseButton
className={composeTwRenderProps(className, slots?.closeTrigger())}
data-slot="modal-close-trigger"
slot="close"
{...rest}
/>
);
};

View File

@ -43,7 +43,7 @@ const NumberFieldRoot = ({children, className, isOnSurface, ...props}: NumberFie
);
return (
<NumberFieldContext.Provider value={{slots}}>
<NumberFieldContext value={{slots}}>
<NumberFieldPrimitive
data-slot="number-field"
{...props}
@ -51,7 +51,7 @@ const NumberFieldRoot = ({children, className, isOnSurface, ...props}: NumberFie
>
{(values) => <>{typeof children === "function" ? children(values) : children}</>}
</NumberFieldPrimitive>
</NumberFieldContext.Provider>
</NumberFieldContext>
);
};

View File

@ -62,7 +62,7 @@ const PopoverContent = ({children, className, ...props}: PopoverContentProps) =>
return (
<PopoverContext value={{slots}}>
<SurfaceContext.Provider
<SurfaceContext
value={{
variant: "default" as SurfaceVariants["variant"],
}}
@ -70,7 +70,7 @@ const PopoverContent = ({children, className, ...props}: PopoverContentProps) =>
<PopoverPrimitive {...props} className={composeTwRenderProps(className, slots?.base())}>
{children}
</PopoverPrimitive>
</SurfaceContext.Provider>
</SurfaceContext>
</PopoverContext>
);
};

View File

@ -43,7 +43,7 @@ const SearchFieldRoot = ({children, className, isOnSurface, ...props}: SearchFie
);
return (
<SearchFieldContext.Provider value={{slots}}>
<SearchFieldContext value={{slots}}>
<SearchFieldPrimitive
data-slot="search-field"
{...props}
@ -51,7 +51,7 @@ const SearchFieldRoot = ({children, className, isOnSurface, ...props}: SearchFie
>
{(values) => <>{typeof children === "function" ? children(values) : children}</>}
</SearchFieldPrimitive>
</SearchFieldContext.Provider>
</SearchFieldContext>
);
};

View File

@ -53,7 +53,7 @@ const SelectRoot = <T extends object = object, M extends "single" | "multiple" =
);
return (
<SelectContext.Provider value={{slots}}>
<SelectContext value={{slots}}>
<SelectPrimitive
data-slot="select"
{...props}
@ -61,7 +61,7 @@ const SelectRoot = <T extends object = object, M extends "single" | "multiple" =
>
{(values) => <>{typeof children === "function" ? children(values) : children}</>}
</SelectPrimitive>
</SelectContext.Provider>
</SelectContext>
);
};
@ -157,7 +157,7 @@ const SelectPopover = ({
const {slots} = useContext(SelectContext);
return (
<SurfaceContext.Provider
<SurfaceContext
value={{
variant: "default" as SurfaceVariants["variant"],
}}
@ -169,7 +169,7 @@ const SelectPopover = ({
>
{children}
</PopoverPrimitive>
</SurfaceContext.Provider>
</SurfaceContext>
);
};

View File

@ -3,7 +3,6 @@
import type {SurfaceVariants} from "./surface.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import React, {createContext} from "react";
import {surfaceVariants} from "./surface.styles";
@ -20,24 +19,14 @@ const SurfaceContext = createContext<SurfaceContext>({});
/* ------------------------------------------------------------------------------------------------
* Surface Root
* --------------------------------------------------------------------------------------------- */
interface SurfaceRootProps extends ComponentPropsWithRef<"div">, SurfaceVariants {
asChild?: boolean;
}
const SurfaceRoot = ({
asChild,
children,
className,
variant = "default",
...rest
}: SurfaceRootProps) => {
const Component = asChild ? SlotPrimitive : "div";
interface SurfaceRootProps extends ComponentPropsWithRef<"div">, SurfaceVariants {}
const SurfaceRoot = ({children, className, variant = "default", ...rest}: SurfaceRootProps) => {
return (
<SurfaceContext value={{variant}}>
<Component className={surfaceVariants({variant, className})} data-slot="surface" {...rest}>
<div className={surfaceVariants({variant, className})} data-slot="surface" {...rest}>
{children}
</Component>
</div>
</SurfaceContext>
);
};

View File

@ -3,7 +3,6 @@
import type {TextVariants} from "./text.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import {Text as TextPrimitive} from "react-aria-components";
import {textVariants} from "./text.styles";
@ -11,28 +10,11 @@ import {textVariants} from "./text.styles";
/* -------------------------------------------------------------------------------------------------
* Text Root
* -----------------------------------------------------------------------------------------------*/
interface TextRootProps extends ComponentPropsWithRef<typeof TextPrimitive>, TextVariants {
asChild?: boolean;
}
interface TextRootProps extends ComponentPropsWithRef<typeof TextPrimitive>, TextVariants {}
const TextRoot = ({
asChild = false,
children,
className,
size,
variant,
...rest
}: TextRootProps) => {
const TextRoot = ({children, className, size, variant, ...rest}: TextRootProps) => {
const styles = textVariants({size, variant, className});
if (asChild) {
return (
<SlotPrimitive className={styles} {...rest}>
{children}
</SlotPrimitive>
);
}
return (
<TextPrimitive className={styles} {...rest}>
{children}

View File

@ -3,7 +3,6 @@
import type {TooltipVariants} from "./tooltip.styles";
import type {ComponentPropsWithRef} from "react";
import {Slot as SlotPrimitive} from "@radix-ui/react-slot";
import React, {createContext, useContext} from "react";
import {
Focusable as FocusablePrimitive,
@ -112,24 +111,21 @@ const TooltipArrow = ({children, className, ...props}: TooltipArrowProps) => {
/* -------------------------------------------------------------------------------------------------
* Tooltip Trigger
* -----------------------------------------------------------------------------------------------*/
interface TooltipTriggerProps extends ComponentPropsWithRef<"div"> {
asChild?: boolean;
}
interface TooltipTriggerProps extends ComponentPropsWithRef<"div"> {}
const TooltipTrigger = ({asChild = false, children, className, ...props}: TooltipTriggerProps) => {
const TooltipTrigger = ({children, className, ...props}: TooltipTriggerProps) => {
const {slots} = useContext(TooltipContext);
const Comp = asChild ? SlotPrimitive : "div";
return (
<FocusablePrimitive>
<Comp
<div
className={slots?.trigger({className})}
data-slot="tooltip-trigger"
role="button"
{...props}
>
{children}
</Comp>
</div>
</FocusablePrimitive>
);
};