From f4277c81a2f6b45966e76473d4e219331949443a Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Thu, 18 Apr 2024 16:57:10 +1000 Subject: [PATCH 01/22] feat: add polymorphic types --- .eslintrc | 70 +++++++++++++++----- packages/react/src/utilities/polymorphic.tsx | 27 ++++++++ 2 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 packages/react/src/utilities/polymorphic.tsx diff --git a/.eslintrc b/.eslintrc index ea1e485..4382a66 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,26 +3,61 @@ "env": { "browser": false, "es2021": true, - "node": true + "node": true, }, "parser": "@typescript-eslint/parser", "extends": ["airbnb", "airbnb-typescript", "airbnb/hooks", "prettier", "plugin:storybook/recommended"], "parserOptions": { "project": "tsconfig.json", "ecmaFeatures": { - "jsx": true + "jsx": true, }, "ecmaVersion": 12, - "sourceType": "module" + "sourceType": "module", }, "settings": { "react": { - "version": "detect" - } + "version": "detect", + }, }, "rules": { "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "default", + "format": ["camelCase", "PascalCase", "snake_case", "UPPER_CASE"], + }, + { + "selector": "import", + "format": ["camelCase", "PascalCase"], + }, + { + "selector": "variable", + "format": ["camelCase", "PascalCase", "snake_case", "UPPER_CASE"], + "leadingUnderscore": "allowSingleOrDouble", + "trailingUnderscore": "allowSingleOrDouble", + }, + { + "selector": "function", + "format": ["camelCase", "PascalCase"], + }, + { + "selector": "typeLike", + "format": ["PascalCase"], + }, + { + "selector": "enumMember", + "format": ["UPPER_CASE"], + }, + { + "selector": "property", + "format": null, + }, + ], "no-console": "warn", + "no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["accu"] }], + "no-underscore-dangle": ["error", { "allow": ["__ELEMENT_TYPE__"] }], "react/prop-types": "off", "react/jsx-uses-react": "off", "react/react-in-jsx-scope": "off", @@ -35,9 +70,10 @@ "callbacksLast": true, "shorthandFirst": true, "noSortAlphabetically": false, - "reservedFirst": true - } + "reservedFirst": true, + }, ], + "import/prefer-default-export": "off", "import/extensions": "off", "import/order": [ "error", @@ -47,19 +83,19 @@ { "pattern": "~/**", "group": "external", - "position": "after" - } + "position": "after", + }, ], "newlines-between": "always", - "alphabetize": { "order": "asc", "caseInsensitive": true } - } + "alphabetize": { "order": "asc", "caseInsensitive": true }, + }, ], "sort-imports": [ "error", { "ignoreDeclarationSort": true, - "ignoreMemberSort": false - } + "ignoreMemberSort": false, + }, ], "padding-line-between-statements": [ "warn", @@ -68,8 +104,8 @@ { "blankLine": "any", "prev": ["const", "let", "var"], - "next": ["const", "let", "var"] - } - ] - } + "next": ["const", "let", "var"], + }, + ], + }, } diff --git a/packages/react/src/utilities/polymorphic.tsx b/packages/react/src/utilities/polymorphic.tsx new file mode 100644 index 0000000..31373d4 --- /dev/null +++ b/packages/react/src/utilities/polymorphic.tsx @@ -0,0 +1,27 @@ +type PropsOf> = + JSX.LibraryManagedAttributes> + +type AsProp = { + as?: C +} + +type ExtendableProps, OverrideProps = Record> = OverrideProps & + Omit + +type InheritableElementProps> = ExtendableProps< + PropsOf, + Props +> + +type ComponentProps> = InheritableElementProps< + C, + Props & AsProp +> + +type Ref = React.ComponentPropsWithRef['ref'] + +type ComponentPropsWithRef> = ComponentProps & { + ref?: Ref +} + +export type { ComponentPropsWithRef, ComponentProps, Ref } From 46e1039dbc0e6dab3a217de05d6234b7e665d4e6 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Thu, 18 Apr 2024 16:58:10 +1000 Subject: [PATCH 02/22] feat(button): add polymorphic component support --- .../src/components/button/Button.stories.tsx | 11 ++++ .../react/src/components/button/Button.tsx | 57 ++++++++++++------- .../src/components/button/use-button.hook.ts | 18 ------ 3 files changed, 47 insertions(+), 39 deletions(-) delete mode 100644 packages/react/src/components/button/use-button.hook.ts diff --git a/packages/react/src/components/button/Button.stories.tsx b/packages/react/src/components/button/Button.stories.tsx index d3f8e7e..362146d 100644 --- a/packages/react/src/components/button/Button.stories.tsx +++ b/packages/react/src/components/button/Button.stories.tsx @@ -4,6 +4,7 @@ import type { Meta, StoryFn } from '@storybook/react' import { button } from '@giantnodes/theme' import { Button } from '@/components/button' +import Link from '@/components/link/Link' const Component: Meta = { title: 'Components/Button', @@ -52,4 +53,14 @@ Default.args = { ...defaultProps, } +export const LinkButton: StoryFn = (args: ButtonProps) => ( + +) + +LinkButton.args = { + ...defaultProps, +} + export default Component diff --git a/packages/react/src/components/button/Button.tsx b/packages/react/src/components/button/Button.tsx index e2d50af..c8153a1 100644 --- a/packages/react/src/components/button/Button.tsx +++ b/packages/react/src/components/button/Button.tsx @@ -1,31 +1,46 @@ -import type { UseButtonProps } from '@/components/button/use-button.hook' -import type { ComponentWithoutAs } from '@/utilities/types' -import type { ButtonProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { ButtonVariantProps } from '@giantnodes/theme' +import type { ButtonProps } from 'react-aria-components' +import { button } from '@giantnodes/theme' import React from 'react' -import { Button as Component } from 'react-aria-components' +import { Button } from 'react-aria-components' -import { useButton } from '@/components/button/use-button.hook' +const __ELEMENT_TYPE__ = 'button' -export type ButtonProps = ComponentWithoutAs<'button'> & ComponentProps & UseButtonProps +type ComponentOwnProps = ButtonVariantProps -const Button = React.forwardRef((props, ref) => { - const { children, className, color, shape, size, variant, ...rest } = props +type ComponentProps = Polymophic.ComponentPropsWithRef - const { slots } = useButton({ color, shape, size, variant }) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: slots.button({ className }), - ...rest, - }), - [ref, slots, className, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, isLoading = false, isDisabled = false, color, shape, size, variant, ...rest } = props - return {children} -}) + const Element = as ?? Button -Button.displayName = 'Button' + const slots = React.useMemo(() => button({ color, shape, size, variant }), [color, shape, size, variant]) -export default Button + const component = React.useMemo( + () => ({ + 'data-loading': isLoading, + className: slots.button(), + isDisabled: isLoading || isDisabled, + ...rest, + }), + [isDisabled, isLoading, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as ButtonProps } +export default Component diff --git a/packages/react/src/components/button/use-button.hook.ts b/packages/react/src/components/button/use-button.hook.ts deleted file mode 100644 index cddf09c..0000000 --- a/packages/react/src/components/button/use-button.hook.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ButtonVariantProps } from '@giantnodes/theme' - -import { button } from '@giantnodes/theme' -import React from 'react' - -export type UseButtonProps = ButtonVariantProps - -export type UseButtonReturn = ReturnType - -export const useButton = (props: UseButtonProps) => { - const { color, shape, size, variant } = props - - const slots = React.useMemo(() => button({ color, shape, size, variant }), [color, shape, size, variant]) - - return { - slots, - } -} From 87d03ef792ce06914efde95988302ba6a5209ce6 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Thu, 18 Apr 2024 17:16:39 +1000 Subject: [PATCH 03/22] feat(alert): add polymorphic component support --- packages/react/src/components/alert/Alert.tsx | 60 +++++++++++-------- .../react/src/components/alert/AlertBody.tsx | 51 +++++++++------- .../src/components/alert/AlertHeading.tsx | 51 +++++++++------- .../react/src/components/alert/AlertItem.tsx | 55 +++++++++-------- .../react/src/components/alert/AlertList.tsx | 55 +++++++++-------- .../react/src/components/alert/AlertText.tsx | 51 +++++++++------- .../src/components/alert/use-alert.hook.ts | 4 +- 7 files changed, 192 insertions(+), 135 deletions(-) diff --git a/packages/react/src/components/alert/Alert.tsx b/packages/react/src/components/alert/Alert.tsx index 8dbce59..18f728b 100644 --- a/packages/react/src/components/alert/Alert.tsx +++ b/packages/react/src/components/alert/Alert.tsx @@ -1,5 +1,5 @@ -import type { UseAlertProps } from '@/components/alert/use-alert.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { AlertVariantProps } from '@giantnodes/theme' import React from 'react' @@ -10,36 +10,44 @@ import AlertList from '@/components/alert/AlertList' import AlertText from '@/components/alert/AlertText' import { AlertContext, useAlert } from '@/components/alert/use-alert.hook' -export type AlertProps = Component<'div'> & UseAlertProps +const __ELEMENT_TYPE__ = 'div' -const Alert = React.forwardRef((props, ref) => { - const { as, children, className, color, ...rest } = props +type ComponentOwnProps = AlertVariantProps - const Component = as || 'div' +type ComponentProps = Polymophic.ComponentPropsWithRef - const context = useAlert({ color }) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.base({ - class: className, - }), - ...rest, - }), - [ref, context.slots, className, rest] - ) - - return ( - - {children} - - ) -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, color, ...rest } = props -Alert.displayName = 'Alert' + const Element = as ?? __ELEMENT_TYPE__ -export default Object.assign(Alert, { + const context = useAlert({ color }) + + const component = React.useMemo( + () => ({ + className: context.slots.base({ className }), + ...rest, + }), + [context.slots, className, rest] + ) + + return ( + + + {children} + + + ) + } +) + +export type { ComponentOwnProps as AlertProps } +export default Object.assign(Component, { Body: AlertBody, Heading: AlertHeading, Item: AlertItem, diff --git a/packages/react/src/components/alert/AlertBody.tsx b/packages/react/src/components/alert/AlertBody.tsx index fe97806..ab56004 100644 --- a/packages/react/src/components/alert/AlertBody.tsx +++ b/packages/react/src/components/alert/AlertBody.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAlertContext } from '@/components/alert/use-alert.hook' -export type AlertBodyProps = Component<'div'> +const __ELEMENT_TYPE__ = 'div' -const AlertBody = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useAlertContext() +type ComponentOwnProps = {} - const Component = as || 'div' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.body({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -AlertBody.displayName = 'Alert.Body' + const { slots } = useAlertContext() -export default AlertBody + const component = React.useMemo( + () => ({ + className: slots.body({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AlertBodyProps } +export default Component diff --git a/packages/react/src/components/alert/AlertHeading.tsx b/packages/react/src/components/alert/AlertHeading.tsx index 4afc383..26e935f 100644 --- a/packages/react/src/components/alert/AlertHeading.tsx +++ b/packages/react/src/components/alert/AlertHeading.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAlertContext } from '@/components/alert/use-alert.hook' -export type AlertHeadingProps = Component<'h3'> +const __ELEMENT_TYPE__ = 'h3' -const AlertHeading = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useAlertContext() +type ComponentOwnProps = {} - const Component = as || 'h3' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.heading({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -AlertHeading.displayName = 'Alert.Heading' + const { slots } = useAlertContext() -export default AlertHeading + const component = React.useMemo( + () => ({ + className: slots.heading({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AlertHeadingProps } +export default Component diff --git a/packages/react/src/components/alert/AlertItem.tsx b/packages/react/src/components/alert/AlertItem.tsx index 1a2a7cc..fa57120 100644 --- a/packages/react/src/components/alert/AlertItem.tsx +++ b/packages/react/src/components/alert/AlertItem.tsx @@ -1,35 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAlertContext } from '@/components/alert/use-alert.hook' -export type AlertItemProps = Component<'li'> +const __ELEMENT_TYPE__ = 'li' -const AlertItem = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useAlertContext() +type ComponentOwnProps = {} - const Component = as || 'li' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.item({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? __ELEMENT_TYPE__ -AlertItem.displayName = 'Alert.Item' + const { slots } = useAlertContext() -export default AlertItem + const component = React.useMemo( + () => ({ + className: slots.item({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AlertItemProps } +export default Component diff --git a/packages/react/src/components/alert/AlertList.tsx b/packages/react/src/components/alert/AlertList.tsx index fc170cd..244eebd 100644 --- a/packages/react/src/components/alert/AlertList.tsx +++ b/packages/react/src/components/alert/AlertList.tsx @@ -1,35 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAlertContext } from '@/components/alert/use-alert.hook' -export type AlertListProps = Component<'ul'> +const __ELEMENT_TYPE__ = 'ul' -const AlertList = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useAlertContext() +type ComponentOwnProps = {} - const Component = as || 'ul' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.list({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? __ELEMENT_TYPE__ -AlertList.displayName = 'Alert.List' + const { slots } = useAlertContext() -export default AlertList + const component = React.useMemo( + () => ({ + className: slots.list({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AlertListProps } +export default Component diff --git a/packages/react/src/components/alert/AlertText.tsx b/packages/react/src/components/alert/AlertText.tsx index 3f1e632..c12cabe 100644 --- a/packages/react/src/components/alert/AlertText.tsx +++ b/packages/react/src/components/alert/AlertText.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAlertContext } from '@/components/alert/use-alert.hook' -export type AlertTextProps = Component<'p'> +const __ELEMENT_TYPE__ = 'p' -const AlertText = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useAlertContext() +type ComponentOwnProps = {} - const Component = as || 'p' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.text({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -AlertText.displayName = 'Alert.Text' + const { slots } = useAlertContext() -export default AlertText + const component = React.useMemo( + () => ({ + className: slots.text({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AlertTextProps } +export default Component diff --git a/packages/react/src/components/alert/use-alert.hook.ts b/packages/react/src/components/alert/use-alert.hook.ts index 7d9741a..b4cf5ce 100644 --- a/packages/react/src/components/alert/use-alert.hook.ts +++ b/packages/react/src/components/alert/use-alert.hook.ts @@ -9,7 +9,9 @@ export type UseAlertProps = AlertVariantProps export type UseAlertReturn = ReturnType -export const useAlert = ({ color }: UseAlertProps) => { +export const useAlert = (props: UseAlertProps) => { + const { color } = props + const slots = React.useMemo(() => alert({ color }), [color]) return { From c6d64570c0af05a2e8b40e754cd18a600b3fe98a Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Thu, 18 Apr 2024 17:20:49 +1000 Subject: [PATCH 04/22] feat(link): add polymorphic component support --- packages/react/src/components/link/Link.tsx | 48 +++++++++++++-------- packages/theme/src/components/link.ts | 25 ++++++++--- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/packages/react/src/components/link/Link.tsx b/packages/react/src/components/link/Link.tsx index f9ef26c..06ffe20 100644 --- a/packages/react/src/components/link/Link.tsx +++ b/packages/react/src/components/link/Link.tsx @@ -1,30 +1,44 @@ -import type { ComponentWithoutAs } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { LinkVariantProps } from '@giantnodes/theme' import type { LinkProps } from 'react-aria-components' import { link } from '@giantnodes/theme' import React from 'react' import { Link } from 'react-aria-components' -type ComponentProps = ComponentWithoutAs<'a'> & LinkProps +const __ELEMENT_TYPE__ = 'a' -const Component = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = LinkVariantProps & LinkProps - const slots = React.useMemo(() => link({}), []) +type ComponentProps = Polymophic.ComponentPropsWithRef - const component = React.useMemo( - () => ({ - ref, - className: slots.link({ class: className?.toString() }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return {children} -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, size, underline, ...rest } = props -Component.displayName = 'Link' + const Element = as ?? Link -export { ComponentProps as LinkProps } + const slots = React.useMemo(() => link({ size, underline }), [size, underline]) + + const component = React.useMemo( + () => ({ + className: slots.link({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as LinkProps } export default Component diff --git a/packages/theme/src/components/link.ts b/packages/theme/src/components/link.ts index 85a1d29..5c06de6 100644 --- a/packages/theme/src/components/link.ts +++ b/packages/theme/src/components/link.ts @@ -5,14 +5,29 @@ import { tv } from 'tailwind-variants' export const link = tv({ slots: { link: [ - 'text-sm', 'text-inherit', - 'hover:underline hover:text-sky-600', - 'disabled:cursor-default disabled:text-shark-200 disabled:hover:text-shark-200 disabled:hover:no-underline', + 'hover:text-sky-600', + 'disabled:cursor-default disabled:text-shark-200 disabled:hover:text-shark-200', ], }, - variants: {}, - defaultVariants: {}, + variants: { + size: { + sm: 'text-sm', + md: 'text-md', + lg: 'text-lg', + }, + underline: { + none: 'no-underline', + hover: 'hover:underline disabled:hover:no-underline', + always: 'underline', + active: 'active:underline', + focus: 'focus:underline', + }, + }, + defaultVariants: { + size: 'sm', + underline: 'none', + }, }) export type LinkVariantProps = VariantProps From 39611c533de3874305acde12a54a707aecf9b496 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:32:46 +1000 Subject: [PATCH 05/22] feat(alert): use Heading and Text base components --- packages/react/src/components/alert/Alert.tsx | 2 +- packages/react/src/components/alert/AlertBody.tsx | 2 +- .../react/src/components/alert/AlertHeading.tsx | 15 +++++++++------ packages/react/src/components/alert/AlertItem.tsx | 2 +- packages/react/src/components/alert/AlertList.tsx | 2 +- packages/react/src/components/alert/AlertText.tsx | 8 +++++--- .../react/src/components/alert/use-alert.hook.ts | 4 ++-- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/react/src/components/alert/Alert.tsx b/packages/react/src/components/alert/Alert.tsx index 18f728b..eb0947c 100644 --- a/packages/react/src/components/alert/Alert.tsx +++ b/packages/react/src/components/alert/Alert.tsx @@ -28,7 +28,7 @@ const Component: ComponentType = React.forwardRef( const context = useAlert({ color }) - const component = React.useMemo( + const component = React.useMemo>( () => ({ className: context.slots.base({ className }), ...rest, diff --git a/packages/react/src/components/alert/AlertBody.tsx b/packages/react/src/components/alert/AlertBody.tsx index ab56004..e4d509c 100644 --- a/packages/react/src/components/alert/AlertBody.tsx +++ b/packages/react/src/components/alert/AlertBody.tsx @@ -22,7 +22,7 @@ const Component: ComponentType = React.forwardRef( const { slots } = useAlertContext() - const component = React.useMemo( + const component = React.useMemo>( () => ({ className: slots.body({ className }), ...rest, diff --git a/packages/react/src/components/alert/AlertHeading.tsx b/packages/react/src/components/alert/AlertHeading.tsx index 26e935f..a0f9d6f 100644 --- a/packages/react/src/components/alert/AlertHeading.tsx +++ b/packages/react/src/components/alert/AlertHeading.tsx @@ -1,12 +1,14 @@ import type * as Polymophic from '@/utilities/polymorphic' +import type { HeadingProps } from 'react-aria-components' import React from 'react' +import { Heading } from 'react-aria-components' import { useAlertContext } from '@/components/alert/use-alert.hook' -const __ELEMENT_TYPE__ = 'h3' +const __ELEMENT_TYPE__ = 'h1' -type ComponentOwnProps = {} +type ComponentOwnProps = HeadingProps type ComponentProps = Polymophic.ComponentPropsWithRef @@ -16,18 +18,19 @@ type ComponentType = ( const Component: ComponentType = React.forwardRef( (props: ComponentProps, ref: Polymophic.Ref) => { - const { as, children, className, ...rest } = props + const { as, children, className, level = 3, ...rest } = props - const Element = as ?? __ELEMENT_TYPE__ + const Element = as ?? Heading const { slots } = useAlertContext() - const component = React.useMemo( + const component = React.useMemo>( () => ({ + level, className: slots.heading({ className }), ...rest, }), - [className, rest, slots] + [className, level, rest, slots] ) return ( diff --git a/packages/react/src/components/alert/AlertItem.tsx b/packages/react/src/components/alert/AlertItem.tsx index fa57120..9e7b01f 100644 --- a/packages/react/src/components/alert/AlertItem.tsx +++ b/packages/react/src/components/alert/AlertItem.tsx @@ -22,7 +22,7 @@ const Component: ComponentType = React.forwardRef( const { slots } = useAlertContext() - const component = React.useMemo( + const component = React.useMemo>( () => ({ className: slots.item({ className }), ...rest, diff --git a/packages/react/src/components/alert/AlertList.tsx b/packages/react/src/components/alert/AlertList.tsx index 244eebd..a51e39b 100644 --- a/packages/react/src/components/alert/AlertList.tsx +++ b/packages/react/src/components/alert/AlertList.tsx @@ -22,7 +22,7 @@ const Component: ComponentType = React.forwardRef( const { slots } = useAlertContext() - const component = React.useMemo( + const component = React.useMemo>( () => ({ className: slots.list({ className }), ...rest, diff --git a/packages/react/src/components/alert/AlertText.tsx b/packages/react/src/components/alert/AlertText.tsx index c12cabe..2c38b45 100644 --- a/packages/react/src/components/alert/AlertText.tsx +++ b/packages/react/src/components/alert/AlertText.tsx @@ -1,12 +1,14 @@ import type * as Polymophic from '@/utilities/polymorphic' +import type { TextProps } from 'react-aria-components' import React from 'react' +import { Text } from 'react-aria-components' import { useAlertContext } from '@/components/alert/use-alert.hook' const __ELEMENT_TYPE__ = 'p' -type ComponentOwnProps = {} +type ComponentOwnProps = TextProps type ComponentProps = Polymophic.ComponentPropsWithRef @@ -18,11 +20,11 @@ const Component: ComponentType = React.forwardRef( (props: ComponentProps, ref: Polymophic.Ref) => { const { as, children, className, ...rest } = props - const Element = as ?? __ELEMENT_TYPE__ + const Element = as ?? Text const { slots } = useAlertContext() - const component = React.useMemo( + const component = React.useMemo>( () => ({ className: slots.text({ className }), ...rest, diff --git a/packages/react/src/components/alert/use-alert.hook.ts b/packages/react/src/components/alert/use-alert.hook.ts index b4cf5ce..df82df7 100644 --- a/packages/react/src/components/alert/use-alert.hook.ts +++ b/packages/react/src/components/alert/use-alert.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseAlertProps = AlertVariantProps +type UseAlertProps = AlertVariantProps -export type UseAlertReturn = ReturnType +type UseAlertReturn = ReturnType export const useAlert = (props: UseAlertProps) => { const { color } = props From 43eb86a96c7eb074306d8ed3351c2b41b46ee8b5 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:33:26 +1000 Subject: [PATCH 06/22] feat(avatar): add polymorphic component support --- .../react/src/components/avatar/Avatar.tsx | 82 ++++++++++--------- .../src/components/avatar/AvatarGroup.tsx | 77 +++++++++-------- .../src/components/avatar/AvatarIcon.tsx | 51 +++++++----- .../src/components/avatar/AvatarImage.tsx | 53 +++++++----- .../components/avatar/AvatarNotification.tsx | 51 +++++++----- .../src/components/avatar/use-avatar.hook.ts | 10 ++- 6 files changed, 179 insertions(+), 145 deletions(-) diff --git a/packages/react/src/components/avatar/Avatar.tsx b/packages/react/src/components/avatar/Avatar.tsx index dd4e749..6380dc2 100644 --- a/packages/react/src/components/avatar/Avatar.tsx +++ b/packages/react/src/components/avatar/Avatar.tsx @@ -1,5 +1,5 @@ -import type { UseAvatarProps } from '@/components/avatar/use-avatar.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { AvatarVariantProps } from '@giantnodes/theme' import React from 'react' @@ -7,50 +7,52 @@ import AvatarGroup from '@/components/avatar/AvatarGroup' import AvatarIcon from '@/components/avatar/AvatarIcon' import AvatarImage from '@/components/avatar/AvatarImage' import AvatarNotification from '@/components/avatar/AvatarNotification' -import { AvatarContext, useAvatar } from '@/components/avatar/use-avatar.hook' +import { AvatarContext, useAvatar, useAvatarContext } from '@/components/avatar/use-avatar.hook' -export type AvatarProps = Component<'span'> & UseAvatarProps +const __ELEMENT_TYPE__ = 'span' -const Avatar = React.forwardRef((props, ref) => { - const { as, className, children, color, radius, size, zoomed, ...rest } = props +type ComponentOwnProps = AvatarVariantProps - const Component = as || 'span' - const context = useAvatar({ color, radius, size, zoomed }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.base({ - class: className, - }), - ...rest, - }), - [ref, context.slots, className, rest] - ) - - return ( - - - {React.Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return child - } - - return React.cloneElement(child, { - color: color ?? child.props.color, - radius: radius ?? child.props.radius, - size: size ?? child.props.size, - zoomed: zoomed ?? child.props.zoomed, - }) - })} - - - ) -}) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, color, radius, size, zoomed, ...rest } = props -Avatar.displayName = 'Avatar' + const Element = as ?? __ELEMENT_TYPE__ -export default Object.assign(Avatar, { + const parent = useAvatarContext() + const context = useAvatar({ + color: parent?.color ?? color, + radius: parent?.radius ?? radius, + size: parent?.size ?? size, + zoomed: parent?.zoomed ?? zoomed, + }) + + const component = React.useMemo>( + () => ({ + className: context.slots.base({ className }), + ...rest, + }), + [context.slots, className, rest] + ) + + return ( + + + {children} + + + ) + } +) + +export type { ComponentOwnProps as AvatarProps } +export default Object.assign(Component, { Group: AvatarGroup, Image: AvatarImage, Icon: AvatarIcon, diff --git a/packages/react/src/components/avatar/AvatarGroup.tsx b/packages/react/src/components/avatar/AvatarGroup.tsx index f0f56e7..d93f400 100644 --- a/packages/react/src/components/avatar/AvatarGroup.tsx +++ b/packages/react/src/components/avatar/AvatarGroup.tsx @@ -1,48 +1,45 @@ -import type { AvatarProps } from '@/components/avatar/Avatar' -import type { UseAvatarProps } from '@/components/avatar/use-avatar.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { AvatarVariantProps } from '@giantnodes/theme' import React from 'react' -import { useAvatar } from '@/components/avatar/use-avatar.hook' +import { AvatarContext, useAvatar } from '@/components/avatar/use-avatar.hook' -export type AvatarGroupProps = Component<'div'> & UseAvatarProps +const __ELEMENT_TYPE__ = 'span' -const AvatarGroup = React.forwardRef((props, ref) => { - const { as, children, className, color, radius, size, zoomed, ...rest } = props +type ComponentOwnProps = AvatarVariantProps - const Component = as || 'div' - const { slots } = useAvatar({ color, radius, size, zoomed }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.group({ - class: className, +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, color, radius, size, zoomed, ...rest } = props + + const Element = as ?? __ELEMENT_TYPE__ + + const context = useAvatar({ color, radius, size, zoomed }) + + const component = React.useMemo>( + () => ({ + className: context.slots.group({ className }), + ...rest, }), - ...rest, - }), - [ref, slots, className, rest] - ) - - return ( - - {React.Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return child - } - - return React.cloneElement(child, { - color: color ?? child.props.color, - radius: radius ?? child.props.radius, - size: size ?? child.props.size, - zoomed: zoomed ?? child.props.zoomed, - }) - })} - - ) -}) - -AvatarGroup.displayName = 'Avatar.Group' - -export default AvatarGroup + [context.slots, className, rest] + ) + + return ( + + + {children} + + + ) + } +) + +export type { ComponentOwnProps as AvatarGroupProps } +export default Component diff --git a/packages/react/src/components/avatar/AvatarIcon.tsx b/packages/react/src/components/avatar/AvatarIcon.tsx index 9f957c8..58c733b 100644 --- a/packages/react/src/components/avatar/AvatarIcon.tsx +++ b/packages/react/src/components/avatar/AvatarIcon.tsx @@ -1,33 +1,44 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAvatarContext } from '@/components/avatar/use-avatar.hook' -export type AvatarIconProps = Component<'span'> & { +const __ELEMENT_TYPE__ = 'span' + +type ComponentOwnProps = { icon: React.ReactNode } -const AvatarIcon = React.forwardRef((props, ref) => { - const { as, className, icon, ...rest } = props +type ComponentProps = Polymophic.ComponentPropsWithRef - const Component = as || 'span' - const { slots } = useAvatarContext() +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: slots.icon({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, className, icon, ...rest } = props - return {icon} -}) + const Element = as ?? __ELEMENT_TYPE__ -AvatarIcon.displayName = 'Avatar.Icon' + const { slots } = useAvatarContext() -export default AvatarIcon + const component = React.useMemo>( + () => ({ + className: slots.icon({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {icon} + + ) + } +) + +export type { ComponentOwnProps as AvatarIconProps } +export default Component diff --git a/packages/react/src/components/avatar/AvatarImage.tsx b/packages/react/src/components/avatar/AvatarImage.tsx index 8abdfa1..dd977c5 100644 --- a/packages/react/src/components/avatar/AvatarImage.tsx +++ b/packages/react/src/components/avatar/AvatarImage.tsx @@ -1,33 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAvatarContext } from '@/components/avatar/use-avatar.hook' -export type AvatarImageProps = Component<'img'> +const __ELEMENT_TYPE__ = 'img' -const AvatarImage = React.forwardRef((props, ref) => { - const { as, children, className, src, alt, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'img' - const { slots } = useAvatarContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - src, - alt, - className: slots.img({ - class: className, - }), - ...rest, - }), - [ref, src, alt, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -AvatarImage.displayName = 'Avatar.Image' + const { slots } = useAvatarContext() -export default AvatarImage + const component = React.useMemo>( + () => ({ + className: slots.img({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AvatarImageProps } +export default Component diff --git a/packages/react/src/components/avatar/AvatarNotification.tsx b/packages/react/src/components/avatar/AvatarNotification.tsx index 97defc7..84562eb 100644 --- a/packages/react/src/components/avatar/AvatarNotification.tsx +++ b/packages/react/src/components/avatar/AvatarNotification.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useAvatarContext } from '@/components/avatar/use-avatar.hook' -export type AvatarNotificationProps = Component<'span'> +const __ELEMENT_TYPE__ = 'span' -const AvatarNotification = React.forwardRef((props, ref) => { - const { as, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'span' - const { slots } = useAvatarContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.notification({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return -}) + const Element = as ?? __ELEMENT_TYPE__ -AvatarNotification.displayName = 'Avatar.Notification' + const { slots } = useAvatarContext() -export default AvatarNotification + const component = React.useMemo>( + () => ({ + className: slots.notification({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as AvatarNotificationProps } +export default Component diff --git a/packages/react/src/components/avatar/use-avatar.hook.ts b/packages/react/src/components/avatar/use-avatar.hook.ts index f419b1c..c80f87b 100644 --- a/packages/react/src/components/avatar/use-avatar.hook.ts +++ b/packages/react/src/components/avatar/use-avatar.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseAvatarProps = AvatarVariantProps +type UseAvatarProps = AvatarVariantProps -export type UseAvatarReturn = ReturnType +type UseAvatarReturn = ReturnType export const useAvatar = (props: UseAvatarProps) => { const { color, radius, size, zoomed } = props @@ -25,11 +25,15 @@ export const useAvatar = (props: UseAvatarProps) => { return { slots, + color, + radius, + size, + zoomed, } } export const [AvatarContext, useAvatarContext] = createContext({ name: 'AvatarContext', - strict: true, + strict: false, errorMessage: 'useAvatarContext: `context` is undefined. Seems you forgot to wrap component within ', }) From 6abf846ee145401200965925826e010522b3b3cf Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:33:41 +1000 Subject: [PATCH 07/22] feat(table): add polymorphic component support --- packages/react/src/components/table/Table.tsx | 77 +++++++++++-------- .../react/src/components/table/TableBody.tsx | 50 ++++++++---- .../react/src/components/table/TableCell.tsx | 51 ++++++++---- .../src/components/table/TableColumn.tsx | 52 ++++++++----- .../react/src/components/table/TableHead.tsx | 48 ++++++++---- .../react/src/components/table/TableRow.tsx | 48 ++++++++---- packages/react/src/components/table/index.ts | 5 ++ .../src/components/table/use-table.hook.ts | 4 +- 8 files changed, 218 insertions(+), 117 deletions(-) diff --git a/packages/react/src/components/table/Table.tsx b/packages/react/src/components/table/Table.tsx index d08f6b0..b830db7 100644 --- a/packages/react/src/components/table/Table.tsx +++ b/packages/react/src/components/table/Table.tsx @@ -1,9 +1,9 @@ -import type { UseTableProps } from '@/components/table/use-table.hook' -import type { TableProps as ComponentProps, SelectionMode } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { TableVariantProps } from '@giantnodes/theme' +import type { TableProps } from 'react-aria-components' -import clsx from 'clsx' import React from 'react' -import { Table as Component } from 'react-aria-components' +import { Table } from 'react-aria-components' import TableBody from '@/components/table/TableBody' import TableCell from '@/components/table/TableCell' @@ -12,41 +12,50 @@ import TableHead from '@/components/table/TableHead' import TableRow from '@/components/table/TableRow' import { TableContext, useTable } from '@/components/table/use-table.hook' -export type TableProps = Omit & - UseTableProps & { +const __ELEMENT_TYPE__ = 'table' + +type ComponentOwnProps = Omit & + TableVariantProps & { behavior?: 'toggle' | 'replace' - mode?: SelectionMode + mode?: 'none' | 'single' | 'multiple' } -const Table = React.forwardRef((props, ref) => { - const { children, className, behavior, mode, size, sticky, striped, headingless, ...rest } = props - - const context = useTable({ size, sticky, striped, headingless }) - - const getProps = React.useCallback( - () => ({ - ref, - selectionBehavior: behavior, - selectionMode: mode, - className: clsx(context.slots.table(), className), - ...rest, - }), - [behavior, className, context.slots, mode, ref, rest] - ) - - return ( - - {children} - - ) -}) +type ComponentProps = Polymophic.ComponentPropsWithRef + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, behavior, mode, size, sticky, striped, headingless, ...rest } = props -Table.defaultProps = { - behavior: undefined, - mode: undefined, -} + const Element = as ?? Table + + const context = useTable({ size, sticky, striped, headingless }) + + const component = React.useMemo( + () => ({ + selectionBehavior: behavior, + selectionMode: mode, + className: context.slots.table({ className: className?.toString() }), + ...rest, + }), + [behavior, className, context.slots, mode, rest] + ) + + return ( + + + {children} + + + ) + } +) -export default Object.assign(Table, { +export type { ComponentOwnProps as TableProps } +export default Object.assign(Component, { Body: TableBody, Cell: TableCell, Column: TableColumn, diff --git a/packages/react/src/components/table/TableBody.tsx b/packages/react/src/components/table/TableBody.tsx index d822b0d..b105afa 100644 --- a/packages/react/src/components/table/TableBody.tsx +++ b/packages/react/src/components/table/TableBody.tsx @@ -1,28 +1,50 @@ -import type { TableBodyProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { TableBodyProps } from 'react-aria-components' import React from 'react' -import { TableBody as Component } from 'react-aria-components' +import { TableBody } from 'react-aria-components' -import { useTableContext } from '@/components/table/use-table.hook' +import { useTableContext } from './use-table.hook' -export type TableBodyProps = ComponentProps +const __ELEMENT_TYPE__ = 'tbody' -const TableBody: (props: TableBodyProps) => React.ReactNode = (() => - React.forwardRef((props, ref: React.ForwardedRef) => { - const { children, className, ...rest } = props +type ComponentOwnProps = TableBodyProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, ...rest } = props + + const Element = as ?? TableBody const { slots } = useTableContext() - const getProps = React.useCallback( + const component = React.useMemo>( () => ({ - ref, - className: slots.tbody(), + className: slots.tbody({ className: className?.toString() }), ...rest, }), - [ref, rest, slots] + [className, rest, slots] ) - return {children} - }))() + return ( + + {children} + + ) + } +) -export default TableBody +export type { ComponentOwnProps as TableBodyProps } +export default Component diff --git a/packages/react/src/components/table/TableCell.tsx b/packages/react/src/components/table/TableCell.tsx index ef53f51..83d5597 100644 --- a/packages/react/src/components/table/TableCell.tsx +++ b/packages/react/src/components/table/TableCell.tsx @@ -1,27 +1,44 @@ -import type { CellProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { CellProps } from 'react-aria-components' import React from 'react' -import { Cell as Component } from 'react-aria-components' +import { Cell } from 'react-aria-components' import { useTableContext } from '@/components/table/use-table.hook' -export type TableCellProps = ComponentProps +const __ELEMENT_TYPE__ = 'td' -const TableCell = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = CellProps - const { slots } = useTableContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.td(), - ...rest, - }), - [ref, rest, slots] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return {children} -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props -export default TableCell + const Element = as ?? Cell + + const { slots } = useTableContext() + + const component = React.useMemo( + () => ({ + className: slots.td({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as TableCellProps } +export default Component diff --git a/packages/react/src/components/table/TableColumn.tsx b/packages/react/src/components/table/TableColumn.tsx index bfc039d..e883097 100644 --- a/packages/react/src/components/table/TableColumn.tsx +++ b/packages/react/src/components/table/TableColumn.tsx @@ -1,28 +1,44 @@ -import type { ColumnProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { ColumnProps } from 'react-aria-components' -import clsx from 'clsx' import React from 'react' -import { Column as Component } from 'react-aria-components' +import { Column } from 'react-aria-components' import { useTableContext } from '@/components/table/use-table.hook' -export type TableColumnProps = ComponentProps +const __ELEMENT_TYPE__ = 'th' -const TableColumn = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = ColumnProps - const { slots } = useTableContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: clsx(slots.th(), className), - ...rest, - }), - [className, ref, rest, slots] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return {children} -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props -export default TableColumn + const Element = as ?? Column + + const { slots } = useTableContext() + + const component = React.useMemo( + () => ({ + className: slots.th({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as TableColumnProps } +export default Component diff --git a/packages/react/src/components/table/TableHead.tsx b/packages/react/src/components/table/TableHead.tsx index af5f582..4bc388d 100644 --- a/packages/react/src/components/table/TableHead.tsx +++ b/packages/react/src/components/table/TableHead.tsx @@ -1,33 +1,47 @@ -import type { UseTableProps } from '@/components/table/use-table.hook' -import type { TableHeaderProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { TableHeaderProps } from 'react-aria-components' import React from 'react' -import { Collection, TableHeader as Component, useTableOptions } from 'react-aria-components' +import { Checkbox, Collection, TableHeader, useTableOptions } from 'react-aria-components' -import Checkbox from '@/components/checkbox/Checkbox' import TableColumn from '@/components/table/TableColumn' import { useTableContext } from '@/components/table/use-table.hook' -export type TableHeadProps = ComponentProps & Pick +const __ELEMENT_TYPE__ = 'thead' -const TableHead: (props: TableHeadProps) => React.ReactNode = (() => - React.forwardRef((props, ref: React.ForwardedRef) => { - const { children, className, columns, size, ...rest } = props +type ComponentOwnProps = TableHeaderProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, columns, ...rest } = props + + const Element = as ?? TableHeader const { slots } = useTableContext() const { selectionBehavior, selectionMode, allowsDragging } = useTableOptions() - const getProps = React.useCallback( + const component = React.useMemo>( () => ({ - ref, - className: slots.thead({ className, size }), + className: slots.thead({ className: className?.toString() }), ...rest, }), - [className, ref, rest, size, slots] + [className, rest, slots] ) return ( - + {allowsDragging && } {selectionBehavior === 'toggle' && ( @@ -35,8 +49,10 @@ const TableHead: (props: TableHeadProps) => React.ReactNode )} {children} - + ) - }))() + } +) -export default TableHead +export type { ComponentOwnProps as TableHeadProps } +export default Component diff --git a/packages/react/src/components/table/TableRow.tsx b/packages/react/src/components/table/TableRow.tsx index 1975e83..6803616 100644 --- a/packages/react/src/components/table/TableRow.tsx +++ b/packages/react/src/components/table/TableRow.tsx @@ -1,33 +1,47 @@ -import type { RowProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { RowProps } from 'react-aria-components' import React from 'react' -import { Collection, Row as Component, useTableOptions } from 'react-aria-components' +import { Button, Checkbox, Collection, Row, useTableOptions } from 'react-aria-components' -import Button from '@/components/button/Button' -import Checkbox from '@/components/checkbox/Checkbox' import TableCell from '@/components/table/TableCell' import { useTableContext } from '@/components/table/use-table.hook' -export type TableRowProps = ComponentProps +const __ELEMENT_TYPE__ = 'tr' -const TableRow: (props: TableRowProps) => React.ReactNode = (() => - React.forwardRef((props, ref: React.ForwardedRef) => { - const { children, className, columns, ...rest } = props +type ComponentOwnProps = RowProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, columns, ...rest } = props + + const Element = as ?? Row const { slots } = useTableContext() const { selectionBehavior, allowsDragging } = useTableOptions() - const getProps = React.useCallback( + const component = React.useMemo>( () => ({ - ref, - className: slots.tr(), + className: slots.tr({ className: className?.toString() }), ...rest, }), - [ref, rest, slots] + [className, rest, slots] ) return ( - + {allowsDragging && ( @@ -41,8 +55,10 @@ const TableRow: (props: TableRowProps) => React.ReactNode = )} {children} - + ) - }))() + } +) -export default TableRow +export type { ComponentOwnProps as TableRowProps } +export default Component diff --git a/packages/react/src/components/table/index.ts b/packages/react/src/components/table/index.ts index a090dff..5521e8b 100644 --- a/packages/react/src/components/table/index.ts +++ b/packages/react/src/components/table/index.ts @@ -1,3 +1,8 @@ export type { TableProps } from '@/components/table/Table' +export type { TableBodyProps } from '@/components/table/TableBody' +export type { TableCellProps } from '@/components/table/TableCell' +export type { TableColumnProps } from '@/components/table/TableColumn' +export type { TableHeadProps } from '@/components/table/TableHead' +export type { TableRowProps } from '@/components/table/TableRow' export { default as Table } from '@/components/table/Table' diff --git a/packages/react/src/components/table/use-table.hook.ts b/packages/react/src/components/table/use-table.hook.ts index 819c6ab..70149f3 100644 --- a/packages/react/src/components/table/use-table.hook.ts +++ b/packages/react/src/components/table/use-table.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseTableProps = TableVariantProps +type UseTableProps = TableVariantProps -export type UseTableReturn = ReturnType +type UseTableReturn = ReturnType export const useTable = (props: UseTableProps) => { const { size, sticky, striped, headingless } = props From a5bd00303477b753749d6a8f4587a41582f35646 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:33:58 +1000 Subject: [PATCH 08/22] feat(typography): add polymorphic component support --- .../typography/TypographyHeading.tsx | 76 ++++++++++--------- .../typography/TypographyParagraph.tsx | 50 +++++++----- .../components/typography/TypographyText.tsx | 48 +++++++----- .../typography/use-heading-level.hook.ts | 4 +- 4 files changed, 107 insertions(+), 71 deletions(-) diff --git a/packages/react/src/components/typography/TypographyHeading.tsx b/packages/react/src/components/typography/TypographyHeading.tsx index 5ea4bfe..97653a6 100644 --- a/packages/react/src/components/typography/TypographyHeading.tsx +++ b/packages/react/src/components/typography/TypographyHeading.tsx @@ -1,52 +1,60 @@ import type { HeadingLevel } from '@/components/typography/use-heading-level.hook' -import type { ComponentWithoutAs } from '@/utilities/types' -import type { HeadingProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { HeadingProps } from 'react-aria-components' import { heading } from '@giantnodes/theme' import React from 'react' -import { Heading as Component } from 'react-aria-components' +import { Heading } from 'react-aria-components' import { HeadingLevelContext } from '@/components/typography/use-heading-level.hook' -export type TypographyHeadingProps = ComponentWithoutAs<'h1'> & - ComponentProps & { - as?: HeadingLevel - } +const __ELEMENT_TYPE__ = 'h1' -const TypographyHeading = React.forwardRef((props, ref) => { - const { children, className, as, ...rest } = props +type ComponentOwnProps = HeadingProps & { + level?: HeadingLevel +} - const context = React.useContext(HeadingLevelContext) +type ComponentProps = Polymophic.ComponentPropsWithRef - if (context === undefined) { - throw new Error(' component must be a descendant of component.') - } +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - if (context.level > 6) { - throw new Error(` cannot be nested ${context.level} times. The maximum is 6 levels.`) - } +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - const level = React.useMemo(() => context.level || 1, [context.level]) + const Element = as ?? Heading - const slots = React.useMemo(() => heading({ level }), [level]) + const context = React.useContext(HeadingLevelContext) - const getProps = React.useCallback( - () => ({ - ref, - level, - className: slots.heading({ className, level: as || level }), - ...rest, - }), - [ref, level, slots, className, as, rest] - ) + if (context === undefined) { + throw new Error(' component must be a descendant of component.') + } - return {children} -}) + if (context.level > 6) { + throw new Error(` cannot be nested ${context.level} times. The maximum is 6 levels.`) + } -TypographyHeading.displayName = 'Typography.Heading' + const level = React.useMemo(() => rest.level ?? context.level ?? 1, [context.level, rest.level]) -TypographyHeading.defaultProps = { - as: undefined, -} + const slots = React.useMemo(() => heading({ level }), [level]) + + const component = React.useMemo( + () => ({ + className: slots.heading({ className, level }), + ...rest, + }), + [className, level, rest, slots] + ) + + return ( + + {children} + + ) + } +) -export default TypographyHeading +export type { ComponentOwnProps as TypographyHeadingProps } +export default Component diff --git a/packages/react/src/components/typography/TypographyParagraph.tsx b/packages/react/src/components/typography/TypographyParagraph.tsx index feba200..52319b2 100644 --- a/packages/react/src/components/typography/TypographyParagraph.tsx +++ b/packages/react/src/components/typography/TypographyParagraph.tsx @@ -1,28 +1,44 @@ -import type { ComponentWithoutAs } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import type { TypographyVariantProps } from '@giantnodes/theme' +import type { TextProps } from 'react-aria-components' import { typography } from '@giantnodes/theme' import React from 'react' +import { Text } from 'react-aria-components' -export type TypographyParagraphProps = ComponentWithoutAs<'p'> & TypographyVariantProps +const __ELEMENT_TYPE__ = 'p' -const TypographyParagraph = React.forwardRef((props, ref) => { - const { children, className, variant, ...rest } = props +type ComponentOwnProps = TextProps & TypographyVariantProps - const slots = React.useMemo(() => typography({ variant }), [variant]) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.paragraph({ className }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return

{children}

-}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, variant, ...rest } = props -TypographyParagraph.displayName = 'Typography.Paragraph' + const Element = as ?? Text -export default TypographyParagraph + const slots = React.useMemo(() => typography({ variant }), [variant]) + + const component = React.useMemo( + () => ({ + className: slots.paragraph({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as TypographyParagraphProps } +export default Component diff --git a/packages/react/src/components/typography/TypographyText.tsx b/packages/react/src/components/typography/TypographyText.tsx index 5092eb5..ba6264f 100644 --- a/packages/react/src/components/typography/TypographyText.tsx +++ b/packages/react/src/components/typography/TypographyText.tsx @@ -1,30 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import type { TypographyVariantProps } from '@giantnodes/theme' import { typography } from '@giantnodes/theme' import React from 'react' -export type TypographyTextProps = Component<'span'> & TypographyVariantProps +const __ELEMENT_TYPE__ = 'span' -const TypographyText = React.forwardRef((props, ref) => { - const { as, children, className, variant, ...rest } = props +type ComponentOwnProps = TypographyVariantProps - const Component = as || 'span' +type ComponentProps = Polymophic.ComponentPropsWithRef - const slots = React.useMemo(() => typography({ variant }), [variant]) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: slots.paragraph({ className }), - ...rest, - }), - [ref, slots, className, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, variant, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -TypographyText.displayName = 'Typography.Text' + const slots = React.useMemo(() => typography({ variant }), [variant]) -export default TypographyText + const component = React.useMemo>( + () => ({ + className: slots.paragraph({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as TypographyTextProps } +export default Component diff --git a/packages/react/src/components/typography/use-heading-level.hook.ts b/packages/react/src/components/typography/use-heading-level.hook.ts index a2a60ef..9982975 100644 --- a/packages/react/src/components/typography/use-heading-level.hook.ts +++ b/packages/react/src/components/typography/use-heading-level.hook.ts @@ -4,11 +4,11 @@ import { createContext } from '@/utilities/context' export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6 -export type UseHeadingLevelProps = { +type UseHeadingLevelProps = { level: HeadingLevel } -export type UseHeadingLevelReturn = ReturnType +type UseHeadingLevelReturn = ReturnType export const useHeadingLevel = ({ level }: UseHeadingLevelProps) => { const calculated = React.useMemo(() => { From 63ca4ed665104411c523ef485cb05170079aeedb Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:34:23 +1000 Subject: [PATCH 09/22] feat(navigation): add polymorphic component support --- .../src/components/navigation/Navigation.tsx | 61 +++++++++++-------- .../components/navigation/NavigationBrand.tsx | 51 ++++++++++------ .../navigation/NavigationContent.tsx | 51 ++++++++++------ .../navigation/NavigationDivider.tsx | 51 ++++++++++------ .../components/navigation/NavigationItem.tsx | 56 +++++++++-------- .../components/navigation/NavigationLink.tsx | 57 ++++++++++------- .../navigation/NavigationPortal.tsx | 52 +++++++++------- .../navigation/NavigationSegment.tsx | 56 +++++++++-------- .../components/navigation/NavigationTitle.tsx | 52 +++++++++------- .../navigation/NavigationTrigger.tsx | 58 ++++++++++-------- .../navigation/use-navigation.hook.ts | 4 +- 11 files changed, 321 insertions(+), 228 deletions(-) diff --git a/packages/react/src/components/navigation/Navigation.tsx b/packages/react/src/components/navigation/Navigation.tsx index 36cfd36..721d3b3 100644 --- a/packages/react/src/components/navigation/Navigation.tsx +++ b/packages/react/src/components/navigation/Navigation.tsx @@ -1,5 +1,5 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { NavigationVariantProps } from '@giantnodes/theme' import React from 'react' @@ -14,37 +14,44 @@ import NavigationTitle from '@/components/navigation/NavigationTitle' import NavigationTrigger from '@/components/navigation/NavigationTrigger' import { NavigationContext, useNavigation } from '@/components/navigation/use-navigation.hook' -export type NavigationProps = Component<'nav'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'nav' -const Navigation = React.forwardRef((props, ref) => { - const { as, children, className, position, size, orientation, variant, ...rest } = props +type ComponentOwnProps = NavigationVariantProps - const Component = as || 'nav' - const context = useNavigation({ position, size, orientation, variant }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.base({ - class: className, +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, position, size, orientation, variant, ...rest } = props + + const Element = as ?? __ELEMENT_TYPE__ + + const context = useNavigation({ position, size, orientation, variant }) + + const component = React.useMemo>( + () => ({ + className: context.slots.base({ className }), + ...rest, }), - ...rest, - }), - [ref, context.slots, className, rest] - ) - - return ( - - -
{children}
-
-
- ) -}) + [className, context.slots, rest] + ) -Navigation.displayName = 'Navigation' + return ( + + +
{children}
+
+
+ ) + } +) -export default Object.assign(Navigation, { +export type { ComponentOwnProps as NavigationProps } +export default Object.assign(Component, { Brand: NavigationBrand, Content: NavigationContent, Divider: NavigationDivider, diff --git a/packages/react/src/components/navigation/NavigationBrand.tsx b/packages/react/src/components/navigation/NavigationBrand.tsx index 22ecb7e..b86d928 100644 --- a/packages/react/src/components/navigation/NavigationBrand.tsx +++ b/packages/react/src/components/navigation/NavigationBrand.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationBrandProps = Component<'div'> +const __ELEMENT_TYPE__ = 'div' -const NavigationBrand = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'div' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.brand({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationBrand.displayName = 'Navigation.Brand' + const { slots } = useNavigationContext() -export default NavigationBrand + const component = React.useMemo>( + () => ({ + className: slots.brand({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationBrandProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationContent.tsx b/packages/react/src/components/navigation/NavigationContent.tsx index 21b7af5..77157a4 100644 --- a/packages/react/src/components/navigation/NavigationContent.tsx +++ b/packages/react/src/components/navigation/NavigationContent.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationContentProps = Component<'div'> +const __ELEMENT_TYPE__ = 'div' -const NavigationContent = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'div' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.content({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationContent.displayName = 'Navigation.Content' + const { slots } = useNavigationContext() -export default NavigationContent + const component = React.useMemo>( + () => ({ + className: slots.content({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationContentProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationDivider.tsx b/packages/react/src/components/navigation/NavigationDivider.tsx index 135f847..8dc3851 100644 --- a/packages/react/src/components/navigation/NavigationDivider.tsx +++ b/packages/react/src/components/navigation/NavigationDivider.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationDividerProps = Component<'hr'> +const __ELEMENT_TYPE__ = 'hr' -const NavigationDivider = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'hr' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.divider({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationDivider.displayName = 'Navigation.Divider' + const { slots } = useNavigationContext() -export default NavigationDivider + const component = React.useMemo>( + () => ({ + className: slots.divider({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationDividerProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationItem.tsx b/packages/react/src/components/navigation/NavigationItem.tsx index c85aaf1..4fd745a 100644 --- a/packages/react/src/components/navigation/NavigationItem.tsx +++ b/packages/react/src/components/navigation/NavigationItem.tsx @@ -1,36 +1,42 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationItemProps = Component<'li'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'li' -const NavigationItem = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'li' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.item({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationItem.displayName = 'Navigation.Item' + const { slots } = useNavigationContext() -export default NavigationItem + const component = React.useMemo>( + () => ({ + className: slots.item({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationItemProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationLink.tsx b/packages/react/src/components/navigation/NavigationLink.tsx index d1a973a..98de6f5 100644 --- a/packages/react/src/components/navigation/NavigationLink.tsx +++ b/packages/react/src/components/navigation/NavigationLink.tsx @@ -1,33 +1,46 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { LinkProps } from 'react-aria-components' import React from 'react' +import { Link } from 'react-aria-components' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationLinkProps = Component<'a'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'a' -const NavigationLink = React.forwardRef((props, ref) => { - const { as, children, className, isSelected, ...rest } = props +type ComponentOwnProps = LinkProps & { + isSelected?: boolean +} - const Component = as || 'a' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.link({ - class: className, - isSelected, - }), - ...rest, - }), - [ref, slots, className, isSelected, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, isSelected, ...rest } = props - return {children} -}) + const Element = as ?? Link -NavigationLink.displayName = 'Navigation.Link' + const { slots } = useNavigationContext() -export default NavigationLink + const component = React.useMemo( + () => ({ + className: slots.link({ className: className?.toString(), isSelected }), + ...rest, + }), + [className, isSelected, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationLinkProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationPortal.tsx b/packages/react/src/components/navigation/NavigationPortal.tsx index bf2fc3d..e7153bd 100644 --- a/packages/react/src/components/navigation/NavigationPortal.tsx +++ b/packages/react/src/components/navigation/NavigationPortal.tsx @@ -1,32 +1,42 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationPortalProps = Component<'div'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'div' -const NavigationPortal = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'div' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.viewport({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationPortal.displayName = 'Navigation.Portal' + const { slots } = useNavigationContext() -export default NavigationPortal + const component = React.useMemo>( + () => ({ + className: slots.viewport({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationPortalProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationSegment.tsx b/packages/react/src/components/navigation/NavigationSegment.tsx index d0b52ba..06a3009 100644 --- a/packages/react/src/components/navigation/NavigationSegment.tsx +++ b/packages/react/src/components/navigation/NavigationSegment.tsx @@ -1,36 +1,42 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationSegmentProps = Component<'ul'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'ul' -const NavigationSegment = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'ul' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.segment({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationSegment.displayName = 'Navigation.Segment' + const { slots } = useNavigationContext() -export default NavigationSegment + const component = React.useMemo>( + () => ({ + className: slots.segment({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationSegmentProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationTitle.tsx b/packages/react/src/components/navigation/NavigationTitle.tsx index dfa702a..9c01da2 100644 --- a/packages/react/src/components/navigation/NavigationTitle.tsx +++ b/packages/react/src/components/navigation/NavigationTitle.tsx @@ -1,32 +1,42 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationTitleProps = Component<'span'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'span' -const NavigationTitle = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'span' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.title({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -NavigationTitle.displayName = 'Navigation.Title' + const { slots } = useNavigationContext() -export default NavigationTitle + const component = React.useMemo>( + () => ({ + className: slots.title({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationTitleProps } +export default Component diff --git a/packages/react/src/components/navigation/NavigationTrigger.tsx b/packages/react/src/components/navigation/NavigationTrigger.tsx index 3fbd09c..ed52813 100644 --- a/packages/react/src/components/navigation/NavigationTrigger.tsx +++ b/packages/react/src/components/navigation/NavigationTrigger.tsx @@ -1,36 +1,44 @@ -import type { UseNavigationProps } from '@/components/navigation/use-navigation.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { ButtonProps } from 'react-aria-components' import React from 'react' +import { Button } from 'react-aria-components' import { useNavigationContext } from '@/components/navigation/use-navigation.hook' -export type NavigationTriggerProps = Component<'button'> & UseNavigationProps +const __ELEMENT_TYPE__ = 'button' -const NavigationTrigger = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = ButtonProps - const Component = as || 'button' - const { slots } = useNavigationContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.trigger({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? Button -NavigationTrigger.displayName = 'Navigation.Trigger' + const { slots } = useNavigationContext() -export default NavigationTrigger + const component = React.useMemo( + () => ({ + className: slots.title({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as NavigationTriggerProps } +export default Component diff --git a/packages/react/src/components/navigation/use-navigation.hook.ts b/packages/react/src/components/navigation/use-navigation.hook.ts index 1bbfb09..ee85f3d 100644 --- a/packages/react/src/components/navigation/use-navigation.hook.ts +++ b/packages/react/src/components/navigation/use-navigation.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseNavigationProps = NavigationVariantProps +type UseNavigationProps = NavigationVariantProps -export type UseNavigationReturn = ReturnType +type UseNavigationReturn = ReturnType export const useNavigation = (props: UseNavigationProps) => { const { position, size, orientation, variant } = props From 2f7a2802c53e19332d7c7a871d5305e239b00f23 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:34:41 +1000 Subject: [PATCH 10/22] feat(switch): add polymorphic component support --- .../react/src/components/switch/Switch.tsx | 72 ++++++++++--------- .../src/components/switch/use-switch.hook.ts | 26 ------- 2 files changed, 39 insertions(+), 59 deletions(-) delete mode 100644 packages/react/src/components/switch/use-switch.hook.ts diff --git a/packages/react/src/components/switch/Switch.tsx b/packages/react/src/components/switch/Switch.tsx index 8dd435d..d083fdc 100644 --- a/packages/react/src/components/switch/Switch.tsx +++ b/packages/react/src/components/switch/Switch.tsx @@ -1,46 +1,52 @@ -import type { UseSwitchProps } from '@/components/switch/use-switch.hook' -import type { ComponentWithoutAs } from '@/utilities/types' -import type { SwitchProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { ToggleVariantProps } from '@giantnodes/theme' +import type { SwitchProps } from 'react-aria-components' +import { toggle } from '@giantnodes/theme' import React from 'react' -import { Switch as Component } from 'react-aria-components' +import { Switch } from 'react-aria-components' import { useFormGroupContext } from '@/components/form/use-form-group.hook' -import { SwitchContext, useSwitch } from '@/components/switch/use-switch.hook' -export type SwitchProps = ComponentWithoutAs<'label'> & ComponentProps & UseSwitchProps +const __ELEMENT_TYPE__ = 'label' -const Switch = React.forwardRef((props, ref) => { - const { children, className, size, color, ...rest } = props +type ComponentOwnProps = SwitchProps & ToggleVariantProps - const group = useFormGroupContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const context = useSwitch({ size, color }) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.label({ className }), - name: group?.name, - ...group?.fieldProps, - ...rest, - }), - [className, context.slots, group?.fieldProps, group?.name, ref, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, color, size, ...rest } = props - return ( - - -
- -
+ const Element = as ?? Switch + + const group = useFormGroupContext() - {children} -
-
- ) -}) + const slots = React.useMemo(() => toggle({ color, size }), [color, size]) -Switch.displayName = 'Switch' + const component = React.useMemo( + () => ({ + className: slots.label({ className: className?.toString() }), + name: group?.name, + ...group?.fieldProps, + ...rest, + }), + [className, group?.fieldProps, group?.name, rest, slots] + ) + + return ( + ) ?? ref}> +
+ +
+
+ ) + } +) -export default Switch +export type { ComponentOwnProps as SwitchProps } +export default Component diff --git a/packages/react/src/components/switch/use-switch.hook.ts b/packages/react/src/components/switch/use-switch.hook.ts deleted file mode 100644 index 9e58ee2..0000000 --- a/packages/react/src/components/switch/use-switch.hook.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ToggleVariantProps } from '@giantnodes/theme' - -import { toggle } from '@giantnodes/theme' -import React from 'react' - -import { createContext } from '@/utilities/context' - -export type UseSwitchProps = ToggleVariantProps - -export type UseSwitchReturn = ReturnType - -export const useSwitch = (props: UseSwitchProps) => { - const { color, size } = props - - const slots = React.useMemo(() => toggle({ color, size }), [color, size]) - - return { - slots, - } -} - -export const [SwitchContext, useSwitchContext] = createContext({ - name: 'SwitchContext', - strict: true, - errorMessage: 'useSwitchContext: `context` is undefined. Seems you forgot to wrap component within ', -}) From d1dcc22c9c0498228ac2df39acaefa950d54ae51 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:35:00 +1000 Subject: [PATCH 11/22] feat(select): add polymorphic component support --- .../react/src/components/select/Select.tsx | 48 +++++++++++------ .../src/components/select/SelectOption.tsx | 48 +++++++++++------ .../src/components/select/SelectValue.tsx | 52 +++++++++++++++---- .../src/components/select/use-select.hook.ts | 4 +- 4 files changed, 109 insertions(+), 43 deletions(-) diff --git a/packages/react/src/components/select/Select.tsx b/packages/react/src/components/select/Select.tsx index 6bfd351..c0afdd8 100644 --- a/packages/react/src/components/select/Select.tsx +++ b/packages/react/src/components/select/Select.tsx @@ -1,5 +1,6 @@ -import type { UseSelectProps } from '@/components/select/use-select.hook' +import type * as Polymophic from '@/utilities/polymorphic' import type { Override } from '@/utilities/types' +import type { SelectVariantProps } from '@giantnodes/theme' import type { ButtonProps, ListBoxProps, PopoverProps, SelectProps } from 'react-aria-components' import React from 'react' @@ -10,6 +11,8 @@ import SelectOption from '@/components/select/SelectOption' import SelectValue from '@/components/select/SelectValue' import { SelectContext, useSelect } from '@/components/select/use-select.hook' +const __ELEMENT_TYPE__ = 'select' + type OveriddenSelectProps = Override< SelectProps, { @@ -24,15 +27,28 @@ type OveriddenListBoxProps = Override< } > -type ComponentProps = UseSelectProps & - OveriddenSelectProps & - OveriddenListBoxProps & { +type ComponentOwnProps = SelectVariantProps & + OveriddenSelectProps & + OveriddenListBoxProps & { placement?: 'top' | 'bottom' } -const Component: (props: ComponentProps) => React.ReactNode = (() => - React.forwardRef((props, ref: React.ForwardedRef) => { +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { const { + as, children, className, items, @@ -46,10 +62,12 @@ const Component: (props: ComponentProps) => React.ReactNode ...rest } = props + const Element = as ?? Select + const group = useFormGroupContext() const context = useSelect({ - ref: group.ref, + ref: group.ref as React.RefObject, name: group?.name, behavior, mode, @@ -58,9 +76,8 @@ const Component: (props: ComponentProps) => React.ReactNode onChange: group?.onChange ?? onChange, }) - const select = React.useMemo>( + const select = React.useMemo>( () => ({ - ref: group?.ref ?? ref, name: group?.name, placeholder, onChange: group?.onChange, @@ -77,9 +94,7 @@ const Component: (props: ComponentProps) => React.ReactNode group?.name, group?.onBlur, group?.onChange, - group?.ref, placeholder, - ref, rest, ] ) @@ -99,7 +114,7 @@ const Component: (props: ComponentProps) => React.ReactNode [context.slots, placement] ) - const listbox = React.useMemo>( + const listbox = React.useMemo>( () => ({ items: items ?? undefined, selectionMode: mode, @@ -111,7 +126,7 @@ const Component: (props: ComponentProps) => React.ReactNode return ( - + ) - }))() + } +) -export { ComponentProps as SelectProps } +export type { ComponentOwnProps as SelectProps } export default Object.assign(Component, { Option: SelectOption, }) diff --git a/packages/react/src/components/select/SelectOption.tsx b/packages/react/src/components/select/SelectOption.tsx index e39eb11..3358c99 100644 --- a/packages/react/src/components/select/SelectOption.tsx +++ b/packages/react/src/components/select/SelectOption.tsx @@ -1,28 +1,44 @@ +import type * as Polymophic from '@/utilities/polymorphic' import type { ListBoxItemProps } from 'react-aria-components' import React from 'react' import { ListBoxItem } from 'react-aria-components' -import { useSelectContext } from '@/components/select/use-select.hook' +import { useSelectContext } from './use-select.hook' -type ComponentProps = ListBoxItemProps +const __ELEMENT_TYPE__ = 'option' -const Component = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = ListBoxItemProps - const { slots } = useSelectContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const item = React.useMemo( - () => ({ - ref, - className: slots.option(), - ...rest, - }), - [ref, rest, slots] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return {children} -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props -export { ComponentProps as SelectOptionProps } + const Element = as ?? ListBoxItem + + const { slots } = useSelectContext() + + const component = React.useMemo( + () => ({ + className: slots.option({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as SelectOptionProps } export default Component diff --git a/packages/react/src/components/select/SelectValue.tsx b/packages/react/src/components/select/SelectValue.tsx index 0e359cf..c1ce554 100644 --- a/packages/react/src/components/select/SelectValue.tsx +++ b/packages/react/src/components/select/SelectValue.tsx @@ -1,19 +1,53 @@ +import type * as Polymophic from '@/utilities/polymorphic' +import type { SelectValueProps } from 'react-aria-components' + import React from 'react' import { SelectStateContext, SelectValue } from 'react-aria-components' import { useSelectContext } from '@/components/select/use-select.hook' -const Component: React.FC = () => { - const { selectedItem } = React.useContext(SelectStateContext) - const { slots } = useSelectContext() +const __ELEMENT_TYPE__ = 'span' + +type ComponentOwnProps = SelectValueProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, ...rest } = props + + const Element = as ?? SelectValue + + const { slots } = useSelectContext() + const { selectedItem } = React.useContext(SelectStateContext) + + const component = React.useMemo>( + () => ({ + className: slots.placeholder({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) - const render = React.useMemo(() => { - if (selectedItem?.textValue) return selectedItem.textValue + const render = React.useMemo(() => { + if (selectedItem?.textValue) return selectedItem.textValue - return - }, [selectedItem?.textValue, slots]) + return + }, [Element, component, ref, selectedItem.textValue]) - return render -} + return render + } +) +export type { ComponentOwnProps as SelectValueProps } export default Component diff --git a/packages/react/src/components/select/use-select.hook.ts b/packages/react/src/components/select/use-select.hook.ts index bff7b3d..1c905e8 100644 --- a/packages/react/src/components/select/use-select.hook.ts +++ b/packages/react/src/components/select/use-select.hook.ts @@ -7,7 +7,7 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseSelectProps = SelectVariantProps & +type UseSelectProps = SelectVariantProps & Pick, 'onSelectionChange'> & { ref?: React.RefObject name?: string @@ -16,7 +16,7 @@ export type UseSelectProps = SelectVariantProps & onChange?: ChangeHandler } -export type UseSelectReturn = ReturnType +type UseSelectReturn = ReturnType export const useSelect = (props: UseSelectProps) => { const { ref, name, status, variant, onChange, onSelectionChange } = props From 43cb22fd515500371f358f8a79a1385f9d402912 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:35:14 +1000 Subject: [PATCH 12/22] feat(progress): add polymorphic component support --- .../src/components/progress/Progress.tsx | 60 ++++++++++------- .../src/components/progress/ProgressBar.tsx | 65 +++++++++++-------- .../components/progress/use-progress.hook.ts | 4 +- 3 files changed, 75 insertions(+), 54 deletions(-) diff --git a/packages/react/src/components/progress/Progress.tsx b/packages/react/src/components/progress/Progress.tsx index d9af892..022f848 100644 --- a/packages/react/src/components/progress/Progress.tsx +++ b/packages/react/src/components/progress/Progress.tsx @@ -1,38 +1,48 @@ -import type { UseProgressProps } from '@/components/progress/use-progress.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { ProgressVariantProps } from '@giantnodes/theme' import React from 'react' import ProgressBar from '@/components/progress/ProgressBar' import { ProgressContext, useProgress } from '@/components/progress/use-progress.hook' -export type ProgressProps = Component<'div'> & UseProgressProps +const __ELEMENT_TYPE__ = 'div' -const Progress = React.forwardRef((props, ref) => { - const { children, className, radius, size, ...rest } = props +type ComponentOwnProps = ProgressVariantProps - const context = useProgress({ radius, size }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.base({ - class: className, - }), - ...rest, - }), - [context.slots, ref, className, rest] - ) - - return ( - -
{children}
-
- ) -}) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, radius, size, ...rest } = props -Progress.displayName = 'Progress' + const Element = as ?? __ELEMENT_TYPE__ -export default Object.assign(Progress, { + const context = useProgress({ radius, size }) + + const component = React.useMemo>( + () => ({ + className: context.slots.base({ className }), + ...rest, + }), + [className, context.slots, rest] + ) + + return ( + + + {children} + + + ) + } +) + +export type { ComponentOwnProps as ProgressProps } +export default Object.assign(Component, { Bar: ProgressBar, }) diff --git a/packages/react/src/components/progress/ProgressBar.tsx b/packages/react/src/components/progress/ProgressBar.tsx index f070eac..4f04ab2 100644 --- a/packages/react/src/components/progress/ProgressBar.tsx +++ b/packages/react/src/components/progress/ProgressBar.tsx @@ -1,39 +1,50 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useProgressContext } from '@/components/progress/use-progress.hook' -export type ProgressBarProps = Omit, 'children'> & { +const __ELEMENT_TYPE__ = 'span' + +type ComponentOwnProps = { color: string width: number } -const ProgressBar = React.forwardRef((props, ref) => { - const { as, className, style, color, width, ...rest } = props +type ComponentProps = Polymophic.ComponentPropsWithRef + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, style, color, width, ...rest } = props + + const Element = as ?? __ELEMENT_TYPE__ - const Component = as || 'span' - const { slots } = useProgressContext() + const context = useProgressContext() - const getProps = React.useCallback( - () => ({ - ref, - className: slots.bar({ - class: className, + const component = React.useMemo>( + () => ({ + className: context.slots.bar({ className }), + style: { + ...style, + backgroundColor: color, + width: `${width}%`, + }, + ...rest, }), - style: { - ...style, - backgroundColor: color, - width: `${width}%`, - }, - ...rest, - }), - [ref, slots, className, style, color, width, rest] - ) - - return -}) - -ProgressBar.displayName = 'Progress.Bar' - -export default ProgressBar + [className, color, context.slots, rest, style, width] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as ProgressBarProps } +export default Component diff --git a/packages/react/src/components/progress/use-progress.hook.ts b/packages/react/src/components/progress/use-progress.hook.ts index e021133..81b3f15 100644 --- a/packages/react/src/components/progress/use-progress.hook.ts +++ b/packages/react/src/components/progress/use-progress.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseProgressProps = ProgressVariantProps +type UseProgressProps = ProgressVariantProps -export type UseProgressReturn = ReturnType +type UseProgressReturn = ReturnType export const useProgress = (props: UseProgressProps) => { const { radius, size } = props From a4a8e1ac916940a45f7b6c845affaca3ddab6c90 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:35:38 +1000 Subject: [PATCH 13/22] feat(menu): add polymorphic component support --- packages/react/src/components/menu/Menu.tsx | 57 +++++++++++-------- .../react/src/components/menu/MenuItem.tsx | 55 +++++++++++------- .../react/src/components/menu/MenuList.tsx | 50 +++++++++++----- .../react/src/components/menu/MenuPopover.tsx | 53 ++++++++++------- .../src/components/menu/use-menu.hook.ts | 4 +- 5 files changed, 140 insertions(+), 79 deletions(-) diff --git a/packages/react/src/components/menu/Menu.tsx b/packages/react/src/components/menu/Menu.tsx index 994f4cd..eae272e 100644 --- a/packages/react/src/components/menu/Menu.tsx +++ b/packages/react/src/components/menu/Menu.tsx @@ -1,40 +1,51 @@ -import type { UseMenuProps } from '@/components/menu/use-menu.hook' -import type { ComponentWithoutAs } from '@/utilities/types' -import type { MenuTriggerProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { MenuVariantProps } from '@giantnodes/theme' +import type { MenuTriggerProps } from 'react-aria-components' import React from 'react' -import { MenuTrigger as Component } from 'react-aria-components' +import { MenuTrigger } from 'react-aria-components' import MenuItem from '@/components/menu/MenuItem' import MenuList from '@/components/menu/MenuList' import MenuPopover from '@/components/menu/MenuPopover' import { MenuContext, useMenu } from '@/components/menu/use-menu.hook' -export type MenuProps = ComponentWithoutAs<'div'> & ComponentProps & UseMenuProps +const __ELEMENT_TYPE__ = 'div' -const Menu = React.forwardRef((props, ref) => { - const { children, className, size, status, variant, ...rest } = props +type ComponentOwnProps = MenuTriggerProps & MenuVariantProps - const context = useMenu({ size, status, variant }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - ...rest, - }), - [ref, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return ( - - {children} - - ) -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps) => { + const { as, children, className, size, status, variant, ...rest } = props + + const Element = as ?? MenuTrigger + + const context = useMenu({ size, status, variant }) + + const component = React.useMemo( + () => ({ + className: context.slots.menu({ className }), + ...rest, + }), + [className, context.slots, rest] + ) -Menu.displayName = 'Menu' + return ( + + {children} + + ) + } +) -export default Object.assign(Menu, { +export type { ComponentOwnProps as MenuProps } +export default Object.assign(Component, { Popover: MenuPopover, List: MenuList, Item: MenuItem, diff --git a/packages/react/src/components/menu/MenuItem.tsx b/packages/react/src/components/menu/MenuItem.tsx index 81af9e8..b9aad0d 100644 --- a/packages/react/src/components/menu/MenuItem.tsx +++ b/packages/react/src/components/menu/MenuItem.tsx @@ -1,31 +1,44 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { MenuItemProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { MenuItemProps } from 'react-aria-components' import React from 'react' -import { MenuItem as Component } from 'react-aria-components' +import { MenuItem } from 'react-aria-components' import { useMenuContext } from '@/components/menu/use-menu.hook' -export type MenuPopoverItemProps = ComponentWithoutAs<'div'> & ComponentProps +const __ELEMENT_TYPE__ = 'div' -const MenuItem = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props - const { slots } = useMenuContext() +type ComponentOwnProps = MenuItemProps - const getProps = React.useCallback( - () => ({ - ref, - className: slots.item({ - class: className, - }), - ...rest, - }), - [className, ref, rest, slots] - ) +type ComponentProps = Polymophic.ComponentPropsWithRef + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return {children} -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props -MenuItem.displayName = 'Menu.Item' + const Element = as ?? MenuItem -export default MenuItem + const { slots } = useMenuContext() + + const component = React.useMemo( + () => ({ + className: slots.item({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as MenuItemProps } +export default Component diff --git a/packages/react/src/components/menu/MenuList.tsx b/packages/react/src/components/menu/MenuList.tsx index 3205ee5..b6806bc 100644 --- a/packages/react/src/components/menu/MenuList.tsx +++ b/packages/react/src/components/menu/MenuList.tsx @@ -1,28 +1,50 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { MenuProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { MenuProps } from 'react-aria-components' import React from 'react' -import { Menu as Component } from 'react-aria-components' +import { Menu } from 'react-aria-components' import { useMenuContext } from '@/components/menu/use-menu.hook' -export type MenuPopoverProps = ComponentWithoutAs<'div'> & ComponentProps +const __ELEMENT_TYPE__ = 'div' + +type ComponentOwnProps = MenuProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, ...rest } = props + + const Element = as ?? Menu -const MenuList: (props: MenuPopoverProps) => React.ReactNode = (() => - React.forwardRef((props, ref: React.ForwardedRef) => { - const { children, className, ...rest } = props const { slots } = useMenuContext() - const getMenuProps = React.useCallback( + const component = React.useMemo>( () => ({ - ref, - className: slots.list(), + className: slots.list({ className: className?.toString() }), ...rest, }), - [ref, rest, slots] + [className, rest, slots] ) - return {children} - }))() + return ( + + {children} + + ) + } +) -export default MenuList +export type { ComponentOwnProps as MenuListProps } +export default Component diff --git a/packages/react/src/components/menu/MenuPopover.tsx b/packages/react/src/components/menu/MenuPopover.tsx index 9912fba..17c2c60 100644 --- a/packages/react/src/components/menu/MenuPopover.tsx +++ b/packages/react/src/components/menu/MenuPopover.tsx @@ -1,29 +1,44 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { PopoverProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { PopoverProps } from 'react-aria-components' import React from 'react' -import { Popover as Component } from 'react-aria-components' +import { Popover } from 'react-aria-components' import { useMenuContext } from '@/components/menu/use-menu.hook' -export type MenuPopoverProps = ComponentWithoutAs<'div'> & ComponentProps +const __ELEMENT_TYPE__ = 'div' -const MenuPopover = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props - const { slots } = useMenuContext() +type ComponentOwnProps = PopoverProps - const getPopoverProps = React.useCallback( - () => ({ - ref, - className: slots.popover({ - class: className, +type ComponentProps = Polymophic.ComponentPropsWithRef + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props + + const Element = as ?? Popover + + const { slots } = useMenuContext() + + const component = React.useMemo( + () => ({ + className: slots.popover({ className: className?.toString() }), + ...rest, }), - ...rest, - }), - [className, ref, rest, slots] - ) + [className, rest, slots] + ) - return {children} -}) + return ( + + {children} + + ) + } +) -export default MenuPopover +export type { ComponentOwnProps as MenuPopoverProps } +export default Component diff --git a/packages/react/src/components/menu/use-menu.hook.ts b/packages/react/src/components/menu/use-menu.hook.ts index e1feb56..b5b09bd 100644 --- a/packages/react/src/components/menu/use-menu.hook.ts +++ b/packages/react/src/components/menu/use-menu.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseMenuProps = MenuVariantProps +type UseMenuProps = MenuVariantProps -export type UseMenuReturn = ReturnType +type UseMenuReturn = ReturnType export const useMenu = (props: UseMenuProps) => { const { size, status, variant } = props From 75046bb5664cf08fa4d89a5d0d23af18beec1596 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:37:05 +1000 Subject: [PATCH 14/22] feat(input): add polymorphic component support --- packages/react/src/components/input/Input.tsx | 57 ++++++++++------- .../react/src/components/input/InputAddon.tsx | 51 +++++++++------ .../src/components/input/InputControl.tsx | 62 ++++++++++++------- packages/react/src/components/input/index.ts | 1 - .../src/components/input/use-input.hook.tsx | 4 +- 5 files changed, 105 insertions(+), 70 deletions(-) diff --git a/packages/react/src/components/input/Input.tsx b/packages/react/src/components/input/Input.tsx index f6faccc..ae59303 100644 --- a/packages/react/src/components/input/Input.tsx +++ b/packages/react/src/components/input/Input.tsx @@ -1,5 +1,5 @@ -import type { UseInputProps } from '@/components/input/use-input.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { InputVariantProps } from '@giantnodes/theme' import React from 'react' @@ -7,33 +7,44 @@ import InputAddon from '@/components/input/InputAddon' import InputControl from '@/components/input/InputControl' import { InputContext, useInput } from '@/components/input/use-input.hook' -export type InputProps = Component<'div'> & UseInputProps +const __ELEMENT_TYPE__ = 'div' -const Input = React.forwardRef((props, ref) => { - const { as, children, className, status, size, variant, transparent, ...rest } = props +type ComponentOwnProps = InputVariantProps - const Component = as || 'div' - const context = useInput({ status, size, variant, transparent }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.input({ className }), - ...rest, - }), - [ref, className, context.slots, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return ( - - {children} - - ) -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, status, size, variant, transparent, ...rest } = props + + const Element = as ?? __ELEMENT_TYPE__ + + const context = useInput({ status, size, variant, transparent }) + + const component = React.useMemo>( + () => ({ + className: context.slots.input({ className }), + ...rest, + }), + [className, context.slots, rest] + ) -Input.displayName = 'Input' + return ( + + + {children} + + + ) + } +) -export default Object.assign(Input, { +export type { ComponentOwnProps as InputProps } +export default Object.assign(Component, { Addon: InputAddon, Control: InputControl, }) diff --git a/packages/react/src/components/input/InputAddon.tsx b/packages/react/src/components/input/InputAddon.tsx index 131c9de..8e97d7d 100644 --- a/packages/react/src/components/input/InputAddon.tsx +++ b/packages/react/src/components/input/InputAddon.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useInputContext } from '@/components/input/use-input.hook' -export type InputAddonProps = Component<'div'> +const __ELEMENT_TYPE__ = 'span' -const InputAddon = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'div' - const { slots } = useInputContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.addon({ - class: className, - }), - ...rest, - }), - [className, slots, ref, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -InputAddon.displayName = 'Input.Addon' + const { slots } = useInputContext() -export default InputAddon + const component = React.useMemo>( + () => ({ + className: slots.addon({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as InputAddonProps } +export default Component diff --git a/packages/react/src/components/input/InputControl.tsx b/packages/react/src/components/input/InputControl.tsx index 9b50f4c..5a706ff 100644 --- a/packages/react/src/components/input/InputControl.tsx +++ b/packages/react/src/components/input/InputControl.tsx @@ -1,36 +1,50 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { InputProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { InputProps } from 'react-aria-components' import React from 'react' -import { Input as Component } from 'react-aria-components' +import { Input } from 'react-aria-components' import { useFormGroupContext } from '@/components/form/use-form-group.hook' import { useInputContext } from '@/components/input/use-input.hook' -export type InputControlProps = ComponentWithoutAs<'input'> & ComponentProps +const __ELEMENT_TYPE__ = 'input' -const InputControl = React.forwardRef((props, ref) => { - const { className, ...rest } = props +type ComponentOwnProps = InputProps - const { slots } = useInputContext() - const group = useFormGroupContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref: group?.ref ?? ref, - name: group?.name, - onChange: group?.onChange, - onBlur: group?.onBlur, - className: slots.control({ className }), - ...group?.fieldProps, - ...rest, - }), - [group?.ref, group?.name, group?.onChange, group?.onBlur, group?.fieldProps, ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props -InputControl.displayName = 'Input.Control' + const Element = as ?? Input -export default InputControl + const { slots } = useInputContext() + const group = useFormGroupContext() + + const component = React.useMemo( + () => ({ + name: group?.name, + onChange: group?.onChange, + onBlur: group?.onBlur, + className: slots.control({ className: className?.toString() }), + ...group?.fieldProps, + ...rest, + }), + [className, group?.fieldProps, group?.name, group?.onBlur, group?.onChange, rest, slots] + ) + + return ( + ) ?? ref}> + {children} + + ) + } +) + +export type { ComponentOwnProps as InputControlProps } +export default Component diff --git a/packages/react/src/components/input/index.ts b/packages/react/src/components/input/index.ts index 2fb64cd..b7dbdb3 100644 --- a/packages/react/src/components/input/index.ts +++ b/packages/react/src/components/input/index.ts @@ -3,4 +3,3 @@ export type { InputAddonProps } from '@/components/input/InputAddon' export type { InputControlProps } from '@/components/input/InputControl' export { default as Input } from '@/components/input/Input' -export { default as InputControl } from '@/components/input/InputControl' diff --git a/packages/react/src/components/input/use-input.hook.tsx b/packages/react/src/components/input/use-input.hook.tsx index b7b4de6..952f29d 100644 --- a/packages/react/src/components/input/use-input.hook.tsx +++ b/packages/react/src/components/input/use-input.hook.tsx @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseInputProps = InputVariantProps +type UseInputProps = InputVariantProps -export type UseInputReturn = ReturnType +type UseInputReturn = ReturnType export const useInput = (props: UseInputProps) => { const { status, size, transparent, variant } = props From 2e5167a0fda6df133b1658eb366eee2383285240 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:37:29 +1000 Subject: [PATCH 15/22] feat(form): add polymorphic component support --- packages/react/src/components/form/Form.tsx | 52 ++++++--- .../react/src/components/form/FormCaption.tsx | 53 +++++---- .../src/components/form/FormFeedback.tsx | 61 +++++----- .../react/src/components/form/FormGroup.tsx | 107 ++++++++---------- .../react/src/components/form/FormLabel.tsx | 56 +++++---- .../components/form/use-form-group.hook.tsx | 6 +- 6 files changed, 185 insertions(+), 150 deletions(-) diff --git a/packages/react/src/components/form/Form.tsx b/packages/react/src/components/form/Form.tsx index db46e81..4a722f4 100644 --- a/packages/react/src/components/form/Form.tsx +++ b/packages/react/src/components/form/Form.tsx @@ -1,33 +1,51 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { FormProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { FormProps } from 'react-aria-components' +import { form } from '@giantnodes/theme' import React from 'react' -import { Form as Component } from 'react-aria-components' +import { Form } from 'react-aria-components' import FormCaption from '@/components/form/FormCaption' import FormFeedback from '@/components/form/FormFeedback' import FormGroup from '@/components/form/FormGroup' import FormLabel from '@/components/form/FormLabel' -export type FormProps = ComponentWithoutAs<'form'> & ComponentProps +const __ELEMENT_TYPE__ = 'form' -const Form = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = FormProps - const getProps = React.useCallback( - () => ({ - ref, - ...rest, - }), - [ref, rest] - ) +type ComponentProps = Polymophic.ComponentPropsWithRef - return {children} -}) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, status, ...rest } = props + + const Element = as ?? Form + + const slots = React.useMemo(() => form({ status }), [status]) + + const component = React.useMemo( + () => ({ + className: slots.form({ className }), + ...rest, + }), + [className, rest, slots] + ) -Form.displayName = 'Form' + return ( + + {children} + + ) + } +) -export default Object.assign(Form, { +export type { ComponentOwnProps as FormProps } +export default Object.assign(Component, { Caption: FormCaption, Feedback: FormFeedback, Group: FormGroup, diff --git a/packages/react/src/components/form/FormCaption.tsx b/packages/react/src/components/form/FormCaption.tsx index c0d2dce..5f37e57 100644 --- a/packages/react/src/components/form/FormCaption.tsx +++ b/packages/react/src/components/form/FormCaption.tsx @@ -1,33 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useFormGroupContext } from '@/components/form/use-form-group.hook' -export type FormCaptionProps = Component<'span'> +const __ELEMENT_TYPE__ = 'span' -const FormCaption = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props +type ComponentOwnProps = {} - const Component = as || 'span' - const { slots } = useFormGroupContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.caption({ className }), - ...rest, - }), - [ref, className, slots, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return ( - - {children} - - ) -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props -FormCaption.displayName = 'Form.Caption' + const Element = as ?? __ELEMENT_TYPE__ -export default FormCaption + const { slots } = useFormGroupContext() + + const component = React.useMemo>( + () => ({ + className: slots.caption({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as FormCaptionProps } +export default Component diff --git a/packages/react/src/components/form/FormFeedback.tsx b/packages/react/src/components/form/FormFeedback.tsx index e2b2ca0..6bae9af 100644 --- a/packages/react/src/components/form/FormFeedback.tsx +++ b/packages/react/src/components/form/FormFeedback.tsx @@ -1,41 +1,50 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import clsx from 'clsx' import React from 'react' import { useFormGroupContext } from '@/components/form/use-form-group.hook' -export type FeedbackType = 'success' | 'warning' | 'error' +const __ELEMENT_TYPE__ = 'span' -export type FormFeedbackProps = Component<'span'> & { +type FeedbackType = 'success' | 'warning' | 'error' + +type ComponentOwnProps = { type: FeedbackType } -const FormFeedback = React.forwardRef((props, ref) => { - const { as, children, className, type, ...rest } = props +type ComponentProps = Polymophic.ComponentPropsWithRef - const Component = as || 'span' - const { slots, status, feedback } = useFormGroupContext() +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: slots.feedback({ - class: clsx(className, { hidden: type !== feedback }), - status, - }), - ...rest, - }), - [ref, slots, className, type, feedback, status, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, type, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? __ELEMENT_TYPE__ -FormFeedback.displayName = 'Form.Feedback' + const { slots, status, feedback } = useFormGroupContext() -export default FormFeedback + const component = React.useMemo>( + () => ({ + className: slots.feedback({ + class: clsx(className, { hidden: type !== feedback }), + status, + }), + ...rest, + }), + [className, feedback, rest, slots, status, type] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as FormFeedbackProps, FeedbackType } +export default Component diff --git a/packages/react/src/components/form/FormGroup.tsx b/packages/react/src/components/form/FormGroup.tsx index 9efe1bf..2123a93 100644 --- a/packages/react/src/components/form/FormGroup.tsx +++ b/packages/react/src/components/form/FormGroup.tsx @@ -1,74 +1,59 @@ -import type { UseFormGroupProps } from '@/components/form/use-form-group.hook' -import type { ComponentWithoutAs } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useField } from 'react-aria' -import { FormGroupContext, useFormGroup } from '@/components/form/use-form-group.hook' -import { useDomRef } from '@/hooks/use-dom-ref' +import { FormGroupContext, useFormGroup } from './use-form-group.hook' -export type FormGroupProps = ComponentWithoutAs<'input'> & - Omit & { - name: string - success?: boolean - warning?: boolean - error?: boolean - } - -const FormGroup = React.forwardRef((props, ref) => { - const { name, children, className, success, warning, error, onChange, onBlur, ...rest } = props +const __ELEMENT_TYPE__ = 'span' - const dom = useDomRef(ref) - - const { labelProps, fieldProps } = useField({ ...props, label: name }) - const context = useFormGroup({ ref: dom, name, labelProps, fieldProps, onChange, onBlur }) +type ComponentOwnProps = { + name: string + success?: boolean + warning?: boolean + error?: boolean +} - const getProps = React.useCallback( - () => ({ - className: context.slots.group({ - class: className, - }), - ...rest, - }), - [className, context.slots, rest] - ) +type ComponentProps = Polymophic.ComponentPropsWithRef - React.useEffect(() => { - if (success) context.setFeedback('success') - if (warning) context.setFeedback('warning') - if (error) context.setFeedback('error') - else context.setFeedback(null) - }, [context, success, warning, error]) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return ( - -
- {React.Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return child - } +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, name, success, warning, error, onChange, onBlur, ...rest } = props - return React.cloneElement(child, { - status: context.status, - ...child.props, - }) - })} -
-
- ) -}) + const Element = as ?? __ELEMENT_TYPE__ -FormGroup.displayName = 'Form.Group' + const { labelProps, fieldProps } = useField({ ...props, label: name }) + const context = useFormGroup({ ref, name, labelProps, fieldProps, onChange, onBlur }) -FormGroup.defaultProps = { - error: undefined, - warning: undefined, - success: undefined, -} + const component = React.useMemo>( + () => ({ + 'data-error': error ?? undefined, + 'data-success': success ?? undefined, + 'data-warning': warning ?? undefined, + className: context.slots.group({ className }), + ...rest, + }), + [className, context.slots, error, rest, success, warning] + ) + + React.useEffect(() => { + if (success) context.setFeedback('success') + if (warning) context.setFeedback('warning') + if (error) context.setFeedback('error') + else context.setFeedback(null) + }, [context, success, warning, error]) + + return ( + + {children} + + ) + } +) -export default FormGroup +export type { ComponentOwnProps as FormGroupProps } +export default Component diff --git a/packages/react/src/components/form/FormLabel.tsx b/packages/react/src/components/form/FormLabel.tsx index 0ae8247..46aa885 100644 --- a/packages/react/src/components/form/FormLabel.tsx +++ b/packages/react/src/components/form/FormLabel.tsx @@ -1,31 +1,45 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { LabelProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { LabelProps } from 'react-aria-components' import React from 'react' -import { Label as Component } from 'react-aria-components' +import { Label } from 'react-aria-components' -import { useFormGroupContext } from '@/components/form/use-form-group.hook' +import { useFormGroupContext } from './use-form-group.hook' -export type FormLabelProps = ComponentWithoutAs<'label'> & ComponentProps +const __ELEMENT_TYPE__ = 'label' -const FormLabel = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = LabelProps - const { slots, labelProps } = useFormGroupContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.label({ className }), - ...labelProps, - ...rest, - }), - [ref, className, labelProps, slots, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return {children} -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, color, radius, size, variant, ...rest } = props -FormLabel.displayName = 'Form.Label' + const Element = as ?? Label -export default FormLabel + const { slots, labelProps, status } = useFormGroupContext() + + const component = React.useMemo( + () => ({ + className: slots.label({ className, status }), + ...labelProps, + ...rest, + }), + [className, labelProps, rest, slots, status] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as FormLabelProps } +export default Component diff --git a/packages/react/src/components/form/use-form-group.hook.tsx b/packages/react/src/components/form/use-form-group.hook.tsx index ed31533..bdee6c5 100644 --- a/packages/react/src/components/form/use-form-group.hook.tsx +++ b/packages/react/src/components/form/use-form-group.hook.tsx @@ -8,14 +8,14 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseFormGroupProps = Partial & { - ref?: React.RefObject +type UseFormGroupProps = LabelAria & { + ref?: React.RefObject name?: string onChange?: ChangeHandler onBlur?: ChangeHandler } -export type UseFormGroupReturn = ReturnType +type UseFormGroupReturn = ReturnType export const useFormGroup = (props: UseFormGroupProps) => { const { ref, name, fieldProps, labelProps, onChange, onBlur } = props From ff7bc807a0617340f13aa875bf7b6106cdd656db Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:37:36 +1000 Subject: [PATCH 16/22] feat(divider): add polymorphic component support --- .../react/src/components/divider/Divider.tsx | 59 +++++++++++-------- .../components/divider/use-divider.hook.ts | 18 ------ 2 files changed, 35 insertions(+), 42 deletions(-) delete mode 100644 packages/react/src/components/divider/use-divider.hook.ts diff --git a/packages/react/src/components/divider/Divider.tsx b/packages/react/src/components/divider/Divider.tsx index ef848c8..ccd77f5 100644 --- a/packages/react/src/components/divider/Divider.tsx +++ b/packages/react/src/components/divider/Divider.tsx @@ -1,34 +1,45 @@ -import type { UseDividerProps } from '@/components/divider/use-divider.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import { divider } from '@giantnodes/theme' import React from 'react' -import { useDivider } from '@/components/divider/use-divider.hook' +const __ELEMENT_TYPE__ = 'hr' -export type DividerProps = Component<'hr'> & UseDividerProps +type ComponentOwnProps = { + icon: React.ReactNode +} -const Divider = React.forwardRef((props, ref) => { - const { as, children, className, orientation, ...rest } = props +type ComponentProps = Polymophic.ComponentPropsWithRef - const Component = as || 'hr' - const { slots } = useDivider({ orientation }) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: slots.divider({ className }), - ...rest, - }), - [ref, slots, className, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, orientation, ...rest } = props - return ( - - {children} - - ) -}) + const Element = as ?? __ELEMENT_TYPE__ -Divider.displayName = 'Divider' + const slots = React.useMemo(() => divider({ orientation }), [orientation]) -export default Divider + const component = React.useMemo>( + () => ({ + 'aria-orientation': orientation, + 'data-orientation': orientation, + className: slots.divider({ className }), + ...rest, + }), + [className, orientation, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as DividerProps } +export default Component diff --git a/packages/react/src/components/divider/use-divider.hook.ts b/packages/react/src/components/divider/use-divider.hook.ts deleted file mode 100644 index aa32f59..0000000 --- a/packages/react/src/components/divider/use-divider.hook.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { DividerVariantProps } from '@giantnodes/theme' - -import { divider } from '@giantnodes/theme' -import React from 'react' - -export type UseDividerProps = DividerVariantProps - -export type UseDividerReturn = ReturnType - -export const useDivider = (props: UseDividerProps) => { - const { orientation } = props - - const slots = React.useMemo(() => divider({ orientation }), [orientation]) - - return { - slots, - } -} From 9a0c3ad0ba9722eaba663bffb7785115d32bba6d Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:38:01 +1000 Subject: [PATCH 17/22] feat(dialog): add polymorphic component support --- .../src/components/dialog/Dialog.stories.tsx | 2 +- .../react/src/components/dialog/Dialog.tsx | 54 +++++++----- .../src/components/dialog/DialogContent.tsx | 88 +++++++++---------- 3 files changed, 75 insertions(+), 69 deletions(-) diff --git a/packages/react/src/components/dialog/Dialog.stories.tsx b/packages/react/src/components/dialog/Dialog.stories.tsx index dbe880b..6ff012a 100644 --- a/packages/react/src/components/dialog/Dialog.stories.tsx +++ b/packages/react/src/components/dialog/Dialog.stories.tsx @@ -24,7 +24,7 @@ export const Default: StoryFn = (args: DialogProps) => ( Dialog Content - + )} diff --git a/packages/react/src/components/dialog/Dialog.tsx b/packages/react/src/components/dialog/Dialog.tsx index f5805d9..f6015b3 100644 --- a/packages/react/src/components/dialog/Dialog.tsx +++ b/packages/react/src/components/dialog/Dialog.tsx @@ -1,5 +1,5 @@ -import type { UseDialogProps } from '@/components/dialog/use-dialog.hook' -import type { ComponentWithoutAs } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { DialogVariantProps } from '@giantnodes/theme' import type { DialogTriggerProps } from 'react-aria-components' import React from 'react' @@ -8,33 +8,41 @@ import { DialogTrigger } from 'react-aria-components' import DialogContent from '@/components/dialog/DialogContent' import { DialogContext, useDialog } from '@/components/dialog/use-dialog.hook' -type ComponentProps = ComponentWithoutAs<'div'> & Omit & UseDialogProps +const __ELEMENT_TYPE__ = 'div' -const Component = React.forwardRef((props, ref) => { - const { children, className, blur, placement, ...rest } = props +type ComponentOwnProps = Omit & DialogVariantProps - const context = useDialog({ blur, placement }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const trigger = React.useMemo( - () => ({ - ref, - children, - className: context.slots.dialog({ className }), - ...rest, - }), - [children, className, context.slots, ref, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return ( - - {children} - - ) -}) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps) => { + const { as, children, className, blur, placement, ...rest } = props + + const Element = as ?? DialogTrigger + + const context = useDialog({ blur, placement }) + + const component = React.useMemo>( + () => ({ + className: context.slots.dialog({ className }), + ...rest, + }), + [context.slots, className, rest] + ) -Component.displayName = 'Dialog' + return ( + + {children} + + ) + } +) -export { ComponentProps as DialogProps } +export type { ComponentOwnProps as DialogProps } export default Object.assign(Component, { Content: DialogContent, }) diff --git a/packages/react/src/components/dialog/DialogContent.tsx b/packages/react/src/components/dialog/DialogContent.tsx index c8e9a16..58fc313 100644 --- a/packages/react/src/components/dialog/DialogContent.tsx +++ b/packages/react/src/components/dialog/DialogContent.tsx @@ -1,50 +1,48 @@ -import type { DialogProps, ModalOverlayProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { DialogProps } from 'react-aria-components' import React from 'react' import { Dialog, Modal, ModalOverlay } from 'react-aria-components' -import { useDialogContext } from '@/components/dialog/use-dialog.hook' - -type ComponentProps = DialogProps - -const Component = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props - - const { slots } = useDialogContext() - - const overlay = React.useMemo( - () => ({ - className: slots.overlay(), - }), - [slots] - ) - - const modal = React.useMemo( - () => ({ - className: slots.modal(), - }), - [slots] - ) - - const content = React.useMemo( - () => ({ - ref, - className: slots.content({ className }), - ...rest, - }), - [className, slots, ref, rest] - ) - - return ( - - - {children} - - - ) -}) - -Component.displayName = 'Dialog.Content' - -export { ComponentProps as DialogContentProps } +import { useDialogContext } from './use-dialog.hook' + +const __ELEMENT_TYPE__ = 'div' + +type ComponentOwnProps = DialogProps + +type ComponentProps = Polymophic.ComponentPropsWithRef + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props + + const Element = as ?? Dialog + + const { slots } = useDialogContext() + + const component = React.useMemo( + () => ({ + className: slots.content({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + + + {children} + + + + ) + } +) + +export type { ComponentOwnProps as DialogContentProps } export default Component From 9116ab5f3d345bfae7e579bf04022cbe3da4c42e Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:38:31 +1000 Subject: [PATCH 18/22] feat(chip): add polymorphic component support --- packages/react/src/components/chip/Chip.tsx | 54 +++++++++++-------- .../src/components/chip/use-chip.hook.ts | 18 ------- 2 files changed, 32 insertions(+), 40 deletions(-) delete mode 100644 packages/react/src/components/chip/use-chip.hook.ts diff --git a/packages/react/src/components/chip/Chip.tsx b/packages/react/src/components/chip/Chip.tsx index 5ecb5e5..5d03e15 100644 --- a/packages/react/src/components/chip/Chip.tsx +++ b/packages/react/src/components/chip/Chip.tsx @@ -1,32 +1,42 @@ -import type { UseChipProps } from '@/components/chip/use-chip.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { ChipVariantProps } from '@giantnodes/theme' +import { chip } from '@giantnodes/theme' import React from 'react' -import { useChip } from '@/components/chip/use-chip.hook' +const __ELEMENT_TYPE__ = 'span' -export type ChipProps = Component<'span'> & UseChipProps +type ComponentOwnProps = ChipVariantProps -const Chip = React.forwardRef((props, ref) => { - const { as, children, className, color, radius, size, variant, ...rest } = props +type ComponentProps = Polymophic.ComponentPropsWithRef - const Component = as || 'span' - const { slots } = useChip({ color, radius, size, variant }) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - const getProps = React.useCallback( - () => ({ - ref, - className: slots.base({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, color, radius, size, variant, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -Chip.displayName = 'Chip' + const slots = React.useMemo(() => chip({ color, radius, size, variant }), [color, radius, size, variant]) -export default Object.assign(Chip, {}) + const component = React.useMemo>( + () => ({ + className: slots.base({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as ChipProps } +export default Component diff --git a/packages/react/src/components/chip/use-chip.hook.ts b/packages/react/src/components/chip/use-chip.hook.ts deleted file mode 100644 index 72e233d..0000000 --- a/packages/react/src/components/chip/use-chip.hook.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ChipVariantProps } from '@giantnodes/theme' - -import { chip } from '@giantnodes/theme' -import React from 'react' - -export type UseChipProps = ChipVariantProps - -export type UseChipReturn = ReturnType - -export const useChip = (props: UseChipProps) => { - const { color, radius, size, variant } = props - - const slots = React.useMemo(() => chip({ color, radius, size, variant }), [color, radius, size, variant]) - - return { - slots, - } -} From b93eabcd3650b7c6b865a7471b78c2ca0562e05c Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:38:47 +1000 Subject: [PATCH 19/22] feat(checkbox): add polymorphic component support --- .../src/components/checkbox/Checkbox.tsx | 103 ++++++++++-------- .../components/checkbox/use-checkbox.hook.ts | 18 --- 2 files changed, 55 insertions(+), 66 deletions(-) delete mode 100644 packages/react/src/components/checkbox/use-checkbox.hook.ts diff --git a/packages/react/src/components/checkbox/Checkbox.tsx b/packages/react/src/components/checkbox/Checkbox.tsx index 7372d44..3f5a4cf 100644 --- a/packages/react/src/components/checkbox/Checkbox.tsx +++ b/packages/react/src/components/checkbox/Checkbox.tsx @@ -1,50 +1,57 @@ -import type { UseCheckboxProps } from '@/components/checkbox/use-checkbox.hook' -import type { ComponentWithoutAs } from '@/utilities/types' -import type { CheckboxProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { CheckboxVariantProps } from '@giantnodes/theme' +import type { CheckboxProps } from 'react-aria-components' +import { checkbox } from '@giantnodes/theme' import React from 'react' -import { Checkbox as Component } from 'react-aria-components' - -import { useCheckbox } from '@/components/checkbox/use-checkbox.hook' - -export type CheckboxProps = ComponentWithoutAs<'label'> & ComponentProps & UseCheckboxProps - -const Checkbox = React.forwardRef((props, ref) => { - const { children, className, size, ...rest } = props - - const { slots } = useCheckbox({ size }) - - const getProps = React.useCallback( - () => ({ - ref, - className: slots.label({ className }), - ...rest, - }), - [className, ref, rest, slots] - ) - - return ( - - - - {children} - - ) -}) - -Checkbox.displayName = 'Checkbox' - -export default Checkbox +import { Checkbox } from 'react-aria-components' + +const __ELEMENT_TYPE__ = 'label' + +type ComponentOwnProps = CheckboxProps & CheckboxVariantProps + +type ComponentProps = Polymophic.ComponentPropsWithRef + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, size, ...rest } = props + + const Element = as ?? Checkbox + + const slots = React.useMemo(() => checkbox({ size }), [size]) + + const component = React.useMemo( + () => ({ + className: slots.label({ className: className?.toString() }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + + + ) + } +) + +export type { ComponentOwnProps as CheckboxProps } +export default Component diff --git a/packages/react/src/components/checkbox/use-checkbox.hook.ts b/packages/react/src/components/checkbox/use-checkbox.hook.ts deleted file mode 100644 index da3da9a..0000000 --- a/packages/react/src/components/checkbox/use-checkbox.hook.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CheckboxVariantProps } from '@giantnodes/theme' - -import { checkbox } from '@giantnodes/theme' -import React from 'react' - -export type UseCheckboxProps = Omit - -export type UseCheckboxReturn = ReturnType - -export const useCheckbox = (props: UseCheckboxProps) => { - const { size } = props - - const slots = React.useMemo(() => checkbox({ size }), [size]) - - return { - slots, - } -} From 1a0b31bac25431e9eb0ac61f365ea1d8d4bde21c Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:39:09 +1000 Subject: [PATCH 20/22] feat(card): add polymorphic component support --- packages/react/src/components/card/Card.tsx | 61 +++++++++++-------- .../react/src/components/card/CardBody.tsx | 51 ++++++++++------ .../react/src/components/card/CardFooter.tsx | 51 ++++++++++------ .../react/src/components/card/CardHeader.tsx | 51 ++++++++++------ .../src/components/card/use-card.hook.ts | 4 +- 5 files changed, 130 insertions(+), 88 deletions(-) diff --git a/packages/react/src/components/card/Card.tsx b/packages/react/src/components/card/Card.tsx index 0fe283e..6d33dad 100644 --- a/packages/react/src/components/card/Card.tsx +++ b/packages/react/src/components/card/Card.tsx @@ -1,5 +1,5 @@ -import type { UseCardProps } from '@/components/card/use-card.hook' -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' +import type { CardVariantProps } from '@giantnodes/theme' import React from 'react' @@ -8,35 +8,44 @@ import CardFooter from '@/components/card/CardFooter' import CardHeader from '@/components/card/CardHeader' import { CardContext, useCard } from '@/components/card/use-card.hook' -export type CardProps = Component<'div'> & UseCardProps +const __ELEMENT_TYPE__ = 'div' -const Card = React.forwardRef((props, ref) => { - const { as, children, className, transparent, ...rest } = props +type ComponentOwnProps = CardVariantProps - const Component = as || 'div' - const context = useCard({ transparent }) +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: context.slots.base({ - class: className, - }), - ...rest, - }), - [ref, context.slots, className, rest] - ) - - return ( - - {children} - - ) -}) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, transparent, ...rest } = props -Card.displayName = 'Card' + const Element = as ?? __ELEMENT_TYPE__ -export default Object.assign(Card, { + const context = useCard({ transparent }) + + const component = React.useMemo>( + () => ({ + className: context.slots.base({ className }), + ...rest, + }), + [context.slots, className, rest] + ) + + return ( + + + {children} + + + ) + } +) + +export type { ComponentOwnProps as CardProps } +export default Object.assign(Component, { Body: CardBody, Footer: CardFooter, Header: CardHeader, diff --git a/packages/react/src/components/card/CardBody.tsx b/packages/react/src/components/card/CardBody.tsx index a9c5151..e18e8e8 100644 --- a/packages/react/src/components/card/CardBody.tsx +++ b/packages/react/src/components/card/CardBody.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useCardContext } from '@/components/card/use-card.hook' -export type CardBodyProps = Component<'div'> +const __ELEMENT_TYPE__ = 'div' -const CardBody = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useCardContext() +type ComponentOwnProps = {} - const Component = as || 'div' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.body({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -CardBody.displayName = 'Card.Body' + const { slots } = useCardContext() -export default CardBody + const component = React.useMemo>( + () => ({ + className: slots.body({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as CardBodyProps } +export default Component diff --git a/packages/react/src/components/card/CardFooter.tsx b/packages/react/src/components/card/CardFooter.tsx index c6b6e5c..669f8a1 100644 --- a/packages/react/src/components/card/CardFooter.tsx +++ b/packages/react/src/components/card/CardFooter.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useCardContext } from '@/components/card/use-card.hook' -export type CardFooterProps = Component<'div'> +const __ELEMENT_TYPE__ = 'div' -const CardFooter = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useCardContext() +type ComponentOwnProps = {} - const Component = as || 'div' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.footer({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -CardFooter.displayName = 'Card.Footer' + const { slots } = useCardContext() -export default CardFooter + const component = React.useMemo>( + () => ({ + className: slots.footer({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as CardFooterProps } +export default Component diff --git a/packages/react/src/components/card/CardHeader.tsx b/packages/react/src/components/card/CardHeader.tsx index 0036c6b..888f172 100644 --- a/packages/react/src/components/card/CardHeader.tsx +++ b/packages/react/src/components/card/CardHeader.tsx @@ -1,31 +1,42 @@ -import type { Component } from '@/utilities/types' +import type * as Polymophic from '@/utilities/polymorphic' import React from 'react' import { useCardContext } from '@/components/card/use-card.hook' -export type CardHeaderProps = Component<'div'> +const __ELEMENT_TYPE__ = 'div' -const CardHeader = React.forwardRef((props, ref) => { - const { as, children, className, ...rest } = props - const { slots } = useCardContext() +type ComponentOwnProps = {} - const Component = as || 'div' +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.header({ - class: className, - }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - return {children} -}) + const Element = as ?? __ELEMENT_TYPE__ -CardHeader.displayName = 'Card.Header' + const { slots } = useCardContext() -export default CardHeader + const component = React.useMemo>( + () => ({ + className: slots.header({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + ) + } +) + +export type { ComponentOwnProps as CardHeaderProps } +export default Component diff --git a/packages/react/src/components/card/use-card.hook.ts b/packages/react/src/components/card/use-card.hook.ts index 16d4763..e78b80e 100644 --- a/packages/react/src/components/card/use-card.hook.ts +++ b/packages/react/src/components/card/use-card.hook.ts @@ -5,9 +5,9 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseCardProps = CardVariantProps +type UseCardProps = CardVariantProps -export type UseCardReturn = ReturnType +type UseCardReturn = ReturnType export const useCard = (props: UseCardProps) => { const { transparent } = props From 9872df0431f26d6417dc9cbee721738505012365 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:39:50 +1000 Subject: [PATCH 21/22] feat(breadcrumb): add polymorphic component support --- .../src/components/breadcrumb/Breadcrumb.tsx | 47 +++++++++---- .../components/breadcrumb/BreadcrumbItem.tsx | 66 +++++++++++-------- .../breadcrumb/use-breadcrumb.hook.ts | 4 +- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/packages/react/src/components/breadcrumb/Breadcrumb.tsx b/packages/react/src/components/breadcrumb/Breadcrumb.tsx index 5c29adf..046a8d5 100644 --- a/packages/react/src/components/breadcrumb/Breadcrumb.tsx +++ b/packages/react/src/components/breadcrumb/Breadcrumb.tsx @@ -1,37 +1,56 @@ -import type { UseBreadcrumbProps } from '@/components/breadcrumb/use-breadcrumb.hook' -import type { ComponentWithoutAs } from '@/utilities/types' -import type { BreadcrumbsProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { BreadcrumbVariantProps } from '@giantnodes/theme' +import type { BreadcrumbsProps } from 'react-aria-components' import React from 'react' -import { Breadcrumbs as Component } from 'react-aria-components' +import { Breadcrumbs } from 'react-aria-components' import BreadcrumbItem from '@/components/breadcrumb/BreadcrumbItem' import { BreadcrumbContext, useBreadcrumb } from '@/components/breadcrumb/use-breadcrumb.hook' -export type BreadcrumbProps = ComponentWithoutAs<'ol'> & ComponentProps & UseBreadcrumbProps +const __ELEMENT_TYPE__ = 'ol' -const Breadcrumb: (props: BreadcrumbProps) => React.ReactNode = (() => - React.forwardRef((props, ref: React.ForwardedRef) => { - const { children, disabled, className, size, separator, ...rest } = props +type ComponentOwnProps = BreadcrumbsProps & BreadcrumbVariantProps + +type ComponentProps = Polymophic.ComponentPropsWithRef< + T, + ComponentOwnProps +> + +type ComponentType = ( + props: ComponentProps +) => React.ReactNode + +const Component: ComponentType = React.forwardRef( + ( + props: ComponentProps, + ref: Polymophic.Ref + ) => { + const { as, children, className, size, separator, ...rest } = props + + const Element = as ?? Breadcrumbs const context = useBreadcrumb({ size, separator }) - const getProps = React.useCallback( + const component = React.useMemo>( () => ({ - ref, className: context.slots.base({ className }), ...rest, }), - [ref, context.slots, className, rest] + [context.slots, className, rest] ) return ( - {children} + + {children} + ) - }))() + } +) -export default Object.assign(Breadcrumb, { +export type { ComponentOwnProps as BreadcrumbProps } +export default Object.assign(Component, { Item: BreadcrumbItem, }) diff --git a/packages/react/src/components/breadcrumb/BreadcrumbItem.tsx b/packages/react/src/components/breadcrumb/BreadcrumbItem.tsx index 2b48434..eec6c53 100644 --- a/packages/react/src/components/breadcrumb/BreadcrumbItem.tsx +++ b/packages/react/src/components/breadcrumb/BreadcrumbItem.tsx @@ -1,40 +1,50 @@ -import type { ComponentWithoutAs } from '@/utilities/types' -import type { BreadcrumbProps as ComponentProps } from 'react-aria-components' +import type * as Polymophic from '@/utilities/polymorphic' +import type { BreadcrumbProps } from 'react-aria-components' import React from 'react' -import { Breadcrumb as Component } from 'react-aria-components' +import { Breadcrumb } from 'react-aria-components' import { useBreadcrumbContext } from '@/components/breadcrumb/use-breadcrumb.hook' -export type BreadcrumbItemProps = ComponentWithoutAs<'li'> & ComponentProps +const __ELEMENT_TYPE__ = 'span' -const BreadcrumbItem = React.forwardRef((props, ref) => { - const { children, className, ...rest } = props +type ComponentOwnProps = BreadcrumbProps - const { slots, separator } = useBreadcrumbContext() +type ComponentProps = Polymophic.ComponentPropsWithRef - const getProps = React.useCallback( - () => ({ - ref, - className: slots.item({ className }), - ...rest, - }), - [ref, slots, className, rest] - ) +type ComponentType = ( + props: ComponentProps +) => React.ReactNode - return ( - - {children} +const Component: ComponentType = React.forwardRef( + (props: ComponentProps, ref: Polymophic.Ref) => { + const { as, children, className, ...rest } = props - {separator && ( - - )} - - ) -}) + const Element = as ?? Breadcrumb -BreadcrumbItem.displayName = 'Breadcrumb.Item' + const { slots, separator } = useBreadcrumbContext() -export default BreadcrumbItem + const component = React.useMemo>( + () => ({ + className: slots.item({ className }), + ...rest, + }), + [className, rest, slots] + ) + + return ( + + {children} + + {separator && ( + + )} + + ) + } +) + +export type { ComponentOwnProps as BreadcrumbItemProps } +export default Component diff --git a/packages/react/src/components/breadcrumb/use-breadcrumb.hook.ts b/packages/react/src/components/breadcrumb/use-breadcrumb.hook.ts index 063a011..c67d8c2 100644 --- a/packages/react/src/components/breadcrumb/use-breadcrumb.hook.ts +++ b/packages/react/src/components/breadcrumb/use-breadcrumb.hook.ts @@ -5,11 +5,11 @@ import React from 'react' import { createContext } from '@/utilities/context' -export type UseBreadcrumbProps = BreadcrumbVariantProps & { +type UseBreadcrumbProps = BreadcrumbVariantProps & { separator?: React.ReactNode } -export type UseBreadcrumbReturn = ReturnType +type UseBreadcrumbReturn = ReturnType export const useBreadcrumb = (props: UseBreadcrumbProps) => { const { size, separator = '/' } = props From 9f08647d2200964dd45aaa8de1c2d1540cf550e2 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 20 Apr 2024 13:40:27 +1000 Subject: [PATCH 22/22] fix(button): base button props missing --- packages/react/src/components/button/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/button/Button.tsx b/packages/react/src/components/button/Button.tsx index c8153a1..0daa0e2 100644 --- a/packages/react/src/components/button/Button.tsx +++ b/packages/react/src/components/button/Button.tsx @@ -8,7 +8,7 @@ import { Button } from 'react-aria-components' const __ELEMENT_TYPE__ = 'button' -type ComponentOwnProps = ButtonVariantProps +type ComponentOwnProps = ButtonVariantProps & ButtonProps type ComponentProps = Polymophic.ComponentPropsWithRef