From 2f5ee61a482add1693b23ced86058021408b7c51 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Oct 2024 14:13:32 -0400 Subject: [PATCH] chore(ui): Add `` descriptors (#4365) --- .changeset/empty-mangos-help.md | 2 + .../ui/src/contexts/AppearanceContext.tsx | 20 +- packages/ui/src/primitives/card.tsx | 322 +++++++++++++----- 3 files changed, 253 insertions(+), 91 deletions(-) create mode 100644 .changeset/empty-mangos-help.md diff --git a/.changeset/empty-mangos-help.md b/.changeset/empty-mangos-help.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/empty-mangos-help.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/ui/src/contexts/AppearanceContext.tsx b/packages/ui/src/contexts/AppearanceContext.tsx index 2a9bd62e12..466e449416 100644 --- a/packages/ui/src/contexts/AppearanceContext.tsx +++ b/packages/ui/src/contexts/AppearanceContext.tsx @@ -32,7 +32,25 @@ type ButtonDescriptorIdentifier = type ConnectionDescriptorIdentifier = 'connectionList' | 'connectionListItem'; type SeparatorDescriptorIdentifier = 'separator'; type SpinnerDescriptorIdentifier = 'spinner'; -type CardDescriptorIdentifier = 'logoBox' | 'logoLink' | 'logoImage'; +type CardDescriptorIdentifier = + | 'cardRoot' + | 'cardRootDefault' + | 'cardRootInner' + | 'cardHeader' + | 'cardContent' + | 'cardTitle' + | 'cardDescription' + | 'cardBody' + | 'cardActions' + | 'cardFooter' + | 'cardFooterAction' + | 'cardFooterActionText' + | 'cardFooterActionLink' + | 'cardFooterActionButton' + | 'cardFooterActionPageLink' + | 'cardLogoBox' + | 'cardLogoLink' + | 'cardLogoImage'; /** * Union of all valid descriptors used throughout the components. diff --git a/packages/ui/src/primitives/card.tsx b/packages/ui/src/primitives/card.tsx index 3e0ee817d0..d031e0068e 100644 --- a/packages/ui/src/primitives/card.tsx +++ b/packages/ui/src/primitives/card.tsx @@ -1,57 +1,82 @@ -import { cva, cx } from 'cva'; +import { cx } from 'cva'; import * as React from 'react'; import { useAppearance } from '~/contexts'; import { mergeDescriptors, type ParsedElementsFragment } from '~/contexts/AppearanceContext'; import type { PolymorphicForwardRefExoticComponent, PolymorphicPropsWithoutRef } from '~/types/utils'; +import { applyDescriptors } from '~/utils/dva'; import { ClerkLogo } from './clerk-logo'; import { Image } from './image'; +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardRoot + */ const RootDefaultElement = 'div'; type RootOwnProps = { children?: React.ReactNode; banner?: React.ReactNode; }; +const cardRootLayoutStyle = { + cardRoot: { + className: [ + '[--card-banner-height:theme(size.4)]', + '[--card-body-px:theme(spacing.10)]', + '[--card-body-py:theme(spacing.8)]', + '[--card-content-rounded-b:theme(borderRadius.lg)]', + 'relative w-full max-w-[25rem]', + ].join(' '), + }, + cardRootDefault: {}, + cardRootInner: {}, +}; +const cardRootVisualStyle = { + cardRoot: { + className: 'bg-gray-2 ring-gray-a3 rounded-xl ring-1', + }, + cardRootDefault: { + className: 'shadow-[0px_5px_15px_0px_theme(colors.gray.a4),0px_15px_35px_-5px_theme(colors.gray.a4)]', + }, + cardRootInner: { + className: 'overflow-hidden rounded-[inherit]', + }, +}; + export const Root: PolymorphicForwardRefExoticComponent = React.forwardRef( function CardRoot( - { as, banner, children, className, ...props }: PolymorphicPropsWithoutRef, + { as, banner, children, ...props }: PolymorphicPropsWithoutRef, forwardedRef: React.ForwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; const Element: React.ElementType = as || RootDefaultElement; + const cardRootDescriptors = applyDescriptors(elements, 'cardRoot'); + const cardRootDefaultDescriptors = applyDescriptors(elements, 'cardRootDefault'); return ( {banner && (

@@ -59,71 +84,99 @@ export const Root: PolymorphicForwardRefExoticComponent

)} - {children && ( -
- {children} -
- )} + {children &&
{children}
}
); }, ); +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardContent + */ +const cardContentLayoutStyle = { + cardContent: { + className: 'relative flex flex-col gap-8 px-[--card-body-px] py-[--card-body-py]', + }, +} satisfies ParsedElementsFragment; +const cardContentVisualStyle = { + cardContent: { + className: [ + 'bg-gray-surface rounded-b-[--card-content-rounded-b] rounded-t-none', + 'ring-gray-a3 shadow-[0px_0px_2px_0px_theme(colors.gray.a4),0px_1px_2px_0px_theme(colors.gray.a3)] ring-1', + ].join(' '), + }, +} satisfies ParsedElementsFragment; + export const Content = React.forwardRef>(function CardContent( { children, className, ...props }, forwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; return (
{children}
); }); +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardHeader + */ +const cardHeaderLayoutStyle = { + cardHeader: { + className: 'z-1 flex flex-col items-center gap-1 text-center', + }, +} satisfies ParsedElementsFragment; +const cardHeaderVisualStyle = { + cardHeader: {}, +} satisfies ParsedElementsFragment; + export const Header = React.forwardRef>(function CardHeader( { children, className, ...props }, forwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; return (
{children}
); }); -const logoLayoutStyle = { - logoBox: { +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardLogo + */ +const cardLogoLayoutStyle = { + cardLogoBox: { className: 'z-1 mb-5 flex h-8 justify-center', }, - logoLink: { + cardLogoLink: { className: '-m-0.5 rounded-sm p-0.5 outline-none focus-visible:ring', }, - logoImage: { + cardLogoImage: { className: 'size-full object-contain', }, } satisfies ParsedElementsFragment; -const logoVisualStyle = { - logoBox: {}, - logoLink: {}, - logoImage: {}, +const cardLogoVisualStyle = { + cardLogoBox: {}, + cardLogoLink: {}, + cardLogoImage: {}, } satisfies ParsedElementsFragment; + export const Logo = React.forwardRef(function CardLogo( { href, @@ -142,19 +195,18 @@ export const Logo = React.forwardRef(function CardLogo( const img = ( ); return ( -
+
{href ? ( {img} @@ -165,15 +217,30 @@ export const Logo = React.forwardRef(function CardLogo( ); }); +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardTitle + */ +const cardTitleLayoutStyle = { + cardTitle: {}, +} satisfies ParsedElementsFragment; +const cardTitleVisualStyle = { + cardTitle: { + className: 'leading-medium text-gray-12 text-lg font-bold', + }, +} satisfies ParsedElementsFragment; + export const Title = React.forwardRef>(function CardTitle( { children, className, ...props }, forwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; return (

{children} @@ -181,13 +248,28 @@ export const Title = React.forwardRef>( function CardDescription({ children, className, ...props }, forwardedRef) { + const { elements } = useAppearance().parsedAppearance; return (

{children}

@@ -195,64 +277,111 @@ export const Description = React.forwardRef>(function CardBody( { children, className, ...props }, forwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; + return (
{children}
); }); +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardActions + */ +const cardActionsLayoutStyle = { + cardActions: { + className: [ + 'z-1 flex flex-col gap-3', + // Note: + // Prevents underline interractions triggering outside of the link text + // https://linear.app/clerk/issue/SDKI-192/#comment-ebf943b0 + '[&_[data-link]]:self-center', + ].join(' '), + }, +} satisfies ParsedElementsFragment; +const cardActionsVisualStyle = { + cardActions: {}, +} satisfies ParsedElementsFragment; + export const Actions = React.forwardRef>(function CardActions( { children, className, ...props }, forwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; + return (
{children}
); }); -export const Banner = React.forwardRef(function CardBanner( - { children, className, ...props }: React.HTMLAttributes, - forwardedRef: React.ForwardedRef, -) { - return ( -
-

- {children} -

-
- ); -}); +//////////////////////////////////////////////////////////////////////////////// + +/** + * CardFooter + */ +const cardFooterLayoutStyle = { + cardFooter: { + className: 'grid', + }, + cardFooterAction: { + className: 'px-6 py-4', + }, + cardFooterActionText: {}, + cardFooterActionLink: {}, + cardFooterActionButton: {}, + cardFooterActionPageLink: {}, +} satisfies ParsedElementsFragment; +const cardFooterVisualStyle = { + cardFooter: {}, + cardFooterAction: { + className: 'border-gray-a3 border-b last-of-type:border-b-transparent', + }, + cardFooterActionText: { + className: 'text-gray-a11 text-center text-base', + }, + cardFooterActionLink: { + className: + 'text-accent-a10 text-base font-medium hover:underline rounded-sm outline-none focus-visible:ring -mx-0.5 px-0.5', + }, + cardFooterActionButton: { + className: + 'text-accent-a10 text-base font-medium hover:underline rounded-sm outline-none focus-visible:ring -mx-0.5 px-0.5', + }, + cardFooterActionPageLink: { + className: 'text-gray-a11 text-base font-medium hover:underline', + }, +} satisfies ParsedElementsFragment; export const Footer = React.forwardRef(function CardFooter( { @@ -271,6 +400,7 @@ export const Footer = React.forwardRef(function CardFooter( } & React.HTMLAttributes, forwardedRef: React.ForwardedRef, ) { + const { elements } = useAppearance().parsedAppearance; const hasPageLinks = helpPageUrl || privacyPageUrl || termsPageUrl; const renderFooter = branded || hasPageLinks || children; const renderSubFooter = branded || hasPageLinks; @@ -279,9 +409,8 @@ export const Footer = React.forwardRef(function CardFooter( return renderFooter ? (
{children} @@ -323,12 +452,12 @@ export const Footer = React.forwardRef(function CardFooter( export const FooterAction = React.forwardRef>( function CardFooterAction({ children, className, ...props }, forwardedRef) { + const { elements } = useAppearance().parsedAppearance; return (
{children}
@@ -338,12 +467,12 @@ export const FooterAction = React.forwardRef>( function CardFooterActionText({ children, className, ...props }, forwardedRef) { + const { elements } = useAppearance().parsedAppearance; return (

{children}

@@ -351,20 +480,16 @@ export const FooterActionText = React.forwardRef>( function CardFooterActionButton({ children, className, type = 'button', ...props }, forwardedRef) { + const { elements } = useAppearance().parsedAppearance; return ( @@ -374,12 +499,12 @@ export const FooterActionButton = React.forwardRef>( function CardFooterActionLink({ children, className, ...props }, forwardedRef) { + const { elements } = useAppearance().parsedAppearance; return ( {children} @@ -389,13 +514,14 @@ export const FooterActionLink = React.forwardRef>( function CardFooterPageLink({ children, className, ...props }, forwardedRef) { + const { elements } = useAppearance().parsedAppearance; return ( {children} @@ -404,9 +530,25 @@ const FooterPageLink = React.forwardRef