Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react): fix component composition issues #281

Merged
merged 10 commits into from
Oct 11, 2024
52 changes: 43 additions & 9 deletions packages/react/src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
pavinduLakshan marked this conversation as resolved.
Show resolved Hide resolved
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -17,23 +17,57 @@
*/

import MuiAccordion, {AccordionProps as MuiAccordionProps} from '@mui/material/Accordion';
import type {OverridableComponent} from '@mui/material/OverridableComponent';
import clsx from 'clsx';
import {FC, ReactElement} from 'react';
import {forwardRef} from 'react';
import type {ElementType, ReactElement, Ref} from 'react';
import type {WithWrapperProps} from '../../models/component';
import composeComponentDisplayName from '../../utils/compose-component-display-name';
import type {PaperTypeMap} from '../Paper';
import './accordion.scss';

export type AccordionProps = MuiAccordionProps;
export type AccordionProps<C extends ElementType = ElementType> = {
/**
* The component used for the root node. Either a string to use a HTML element or a component.
*/
component?: C;
} & Omit<MuiAccordionProps, 'component'>;

const COMPONENT_NAME: string = 'Accordion';

const Accordion: FC<AccordionProps> & WithWrapperProps = (props: AccordionProps): ReactElement => {
const {className, ...rest} = props;

const classes: string = clsx('oxygen-accordion', className);
/**
* The Accordion component lets users show and hide sections of related content on a page.
*
* Demos:
*
* - [Accordion (Oxygen UI)] (https://wso2.github.io/oxygen-ui/react/?path=/docs/surfaces-accordion)
* - [Accordion (MUI)](https://mui.com/material-ui/react-accordion/)
*
* API:
*
* - [Accordion API](https://mui.com/material-ui/api/accordion/)
* - inherits [Paper API](https://mui.com/material-ui/api/paper/)
*
* @remarks
* - ✔️ Props of the [Paper](https://mui.com/material-ui/api/paper/) component are also available.
* - ✅ `component` prop is supported.
* - ✅ The `ref` is forwarded to the root element.
*
* @template C - The type of the component.
* @param props - The props for the Accordion component.
* @param ref - The ref to be forwarded to the MuiAccordion component.
* @returns The rendered Accordion component.
*/
const Accordion: OverridableComponent<PaperTypeMap<AccordionProps>> & WithWrapperProps = forwardRef(
<C extends ElementType = ElementType>(
{className, ...rest}: AccordionProps<C>,
ref: Ref<HTMLDivElement>,
): ReactElement => {
const classes: string = clsx('oxygen-accordion', className);

return <MuiAccordion className={classes} {...rest} />;
};
return <MuiAccordion ref={ref} className={classes} {...rest} />;
},
) as OverridableComponent<PaperTypeMap<AccordionProps>> & WithWrapperProps;

Accordion.displayName = composeComponentDisplayName(COMPONENT_NAME);
Accordion.muiName = COMPONENT_NAME;
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/Accordion/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
*/

export {default} from './Accordion';
export type {AccordionProps} from './Accordion';
export * from './Accordion';
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,48 @@
* under the License.
*/

import MuiAccordionDetails, {AccordionDetailsProps as MuiAccordionDetailsProps} from '@mui/material/AccordionDetails';
import MuiAccordionDetails from '@mui/material/AccordionDetails';
import type {AccordionDetailsProps as MuiAccordionDetailsProps} from '@mui/material/AccordionDetails';
import clsx from 'clsx';
import {forwardRef, ForwardRefExoticComponent, ReactElement, MutableRefObject} from 'react';
import {forwardRef} from 'react';
import type {ForwardRefExoticComponent, ReactElement, Ref} from 'react';
import type {WithWrapperProps} from '../../models/component';
import composeComponentDisplayName from '../../utils/compose-component-display-name';

export type AccordionDetailsProps = MuiAccordionDetailsProps;

const COMPONENT_NAME: string = 'AccordionDetails';

/**
* The Accordion Details component is the wrapper for the Accordion content.
*
* Demos:
*
* - [Accordion (Oxygen UI)] (https://wso2.github.io/oxygen-ui/react/?path=/docs/surfaces-accordion)
* - [Accordion (MUI)](https://mui.com/material-ui/react-accordion/)
*
* API:
*
* - [AccordionDetails API](https://mui.com/material-ui/api/accordion-details/)
*
* @remarks
* - ✔️ Props of the native component are also available.
* - ❌ `component` prop is not supported.
* - ✅ The `ref` is forwarded to the root element.
*
* @param props - The props for the AccordionDetails component.
* @param ref - The ref to be forwarded to the MuiAccordionDetails component.
* @returns The rendered AccordionDetails component.
*/
const AccordionDetails: ForwardRefExoticComponent<AccordionDetailsProps> & WithWrapperProps = forwardRef(
(props: AccordionDetailsProps, ref: MutableRefObject<HTMLDivElement>): ReactElement => {
const {className, ...rest} = props;

({className, ...rest}: AccordionDetailsProps, ref: Ref<HTMLDivElement>): ReactElement => {
const classes: string = clsx('oxygen-accordion-details', className);

return <MuiAccordionDetails className={classes} {...rest} ref={ref} />;
return <MuiAccordionDetails ref={ref} className={classes} {...rest} />;
},
) as ForwardRefExoticComponent<AccordionDetailsProps> & WithWrapperProps;

AccordionDetails.displayName = composeComponentDisplayName(COMPONENT_NAME);
AccordionDetails.muiName = COMPONENT_NAME;
AccordionDetails.defaultProps = {};

export default AccordionDetails;
2 changes: 1 addition & 1 deletion packages/react/src/components/AccordionDetails/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
*/

export {default} from './AccordionDetails';
export type {AccordionDetailsProps} from './AccordionDetails';
export * from './AccordionDetails';
63 changes: 51 additions & 12 deletions packages/react/src/components/AccordionSummary/AccordionSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,67 @@
* under the License.
*/

import MuiAccordionSummary, {AccordionSummaryProps as MuiAccordionSummaryProps} from '@mui/material/AccordionSummary';
import MuiAccordionSummary from '@mui/material/AccordionSummary';
import type {
AccordionSummaryProps as MuiAccordionSummaryProps,
AccordionSummaryTypeMap,
} from '@mui/material/AccordionSummary';
import type {OverridableComponent} from '@mui/material/OverridableComponent';
import clsx from 'clsx';
import {forwardRef, ForwardRefExoticComponent, ReactElement, MutableRefObject} from 'react';
import {forwardRef} from 'react';
import type {ReactElement, ElementType, Ref} from 'react';
import type {WithWrapperProps} from '../../models/component';
import composeComponentDisplayName from '../../utils/compose-component-display-name';

export type AccordionSummaryProps = MuiAccordionSummaryProps;
export type AccordionSummaryProps<
C extends ElementType = ElementType,
D extends ElementType = AccordionSummaryTypeMap['defaultComponent'],
P = {},
> = {
/**
* The component used for the root node. Either a string to use a HTML element or a component.
*/
component?: C;
} & Omit<MuiAccordionSummaryProps<D, P>, 'component'>;

const COMPONENT_NAME: string = 'AccordionSummary';

const AccordionSummary: ForwardRefExoticComponent<AccordionSummaryProps> & WithWrapperProps = forwardRef(
(props: AccordionSummaryProps, ref: MutableRefObject<HTMLDivElement>): ReactElement => {
const {className, ...rest} = props;

const classes: string = clsx('oxygen-accordion-summary', className);
/**
* The Accordion Summary component is the wrapper for the Accordion header, which expands or collapses the content when clicked.
*
* Demos:
*
* - [Accordion (Oxygen UI)] (https://wso2.github.io/oxygen-ui/react/?path=/docs/surfaces-accordion)
* - [Accordion (MUI)](https://mui.com/material-ui/react-accordion/)
*
* API:
*
* - [AccordionSummary API](https://mui.com/material-ui/api/accordion-summary/)
* - inherits [ButtonBase API](https://mui.com/material-ui/api/button-base/)
*
* @remarks
* - ✔️ Props of the [ButtonBase](https://mui.com/material-ui/api/button-base/) component are also available.
* - ✅ `component` prop is supported.
* - ✅ The `ref` is forwarded to the root element.
*
* @template C - The type of the component.
* @param props - The props for the AccordionSummary component.
* @param ref - The ref to be forwarded to the MuiAccordionSummary component.
* @returns The rendered AccordionSummary component.
*/
const AccordionSummary: OverridableComponent<AccordionSummaryTypeMap<AccordionSummaryProps>> & WithWrapperProps =
forwardRef(
<C extends ElementType = ElementType>(
{className, ...rest}: AccordionSummaryProps<C>,
ref: Ref<HTMLDivElement>,
): ReactElement => {
const classes: string = clsx('oxygen-accordion-summary', className);

return <MuiAccordionSummary className={classes} {...rest} ref={ref} />;
},
) as ForwardRefExoticComponent<AccordionSummaryProps> & WithWrapperProps;
return <MuiAccordionSummary ref={ref} className={classes} {...rest} />;
},
) as OverridableComponent<AccordionSummaryTypeMap<AccordionSummaryProps>> & WithWrapperProps;

AccordionSummary.displayName = composeComponentDisplayName(COMPONENT_NAME);
AccordionSummary.muiName = COMPONENT_NAME;
AccordionSummary.defaultProps = {};

export default AccordionSummary;
4 changes: 3 additions & 1 deletion packages/react/src/components/AccordionSummary/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@
*/

export {default} from './AccordionSummary';
export type {AccordionSummaryProps} from './AccordionSummary';
export * from './AccordionSummary';

export type {AccordionSummaryTypeMap} from '@mui/material/AccordionSummary';
125 changes: 80 additions & 45 deletions packages/react/src/components/AccountOverview/AccountOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,29 @@
* under the License.
*/

import type {OverridableComponent} from '@mui/material/OverridableComponent';
import clsx from 'clsx';
import {FC, ReactElement, ReactNode} from 'react';
import {forwardRef} from 'react';
import type {ElementType, ReactElement, ReactNode, Ref} from 'react';
import type {WithWrapperProps} from '../../models/component';
import composeComponentDisplayName from '../../utils/compose-component-display-name';
import Box from '../Box';
import Card, {CardProps} from '../Card';
import CardHeader, {CardHeaderProps} from '../CardHeader';
import Carousel, {CarouselStep} from '../Carousel';
import Card from '../Card';
import type {CardProps, CardTypeMap} from '../Card';
import CardHeader from '../CardHeader';
import type {CardHeaderProps} from '../CardHeader';
import Carousel from '../Carousel';
import type {CarouselStep} from '../Carousel';
import CircularProgressAvatar from '../CircularProgressAvatar';
import Divider from '../Divider';
import {UserTemplate} from '../UserDropdownMenu';
import type {UserTemplate} from '../UserDropdownMenu';
import './account-overview.scss';

export interface AccountOverviewProps extends Omit<CardProps, 'title'> {
export type AccountOverviewProps<
C extends ElementType = ElementType,
D extends ElementType = CardTypeMap['defaultComponent'],
P = {},
> = Omit<CardProps<C, D, P>, 'title'> & {
/**
* Account completion steps.
*/
Expand Down Expand Up @@ -60,51 +69,77 @@ export interface AccountOverviewProps extends Omit<CardProps, 'title'> {
* Logged user information.
*/
user: UserTemplate;
}
};

export type AccountCompletionSteps = CarouselStep;

const COMPONENT_NAME: string = 'AccountOverview';

const AccountOverview: FC<AccountOverviewProps> & WithWrapperProps = (props: AccountOverviewProps): ReactElement => {
const {
className,
title,
subheader,
accountCompletionStepsTitle,
accountCompletionSteps,
accountProgress,
user,
cardHeaderProps,
...rest
} = props;

const classes: string = clsx('oxygen-account-overview', className);
/**
* The Account Overview component lets you display the progress of the user's account.
* It includes the user's profile picture, name, email, account progress and account completion steps.
*
* Demos:
*
* - [Account Overview (Oxygen UI)] (https://wso2.github.io/oxygen-ui/react/?path=/docs/patterns-account-overview)
*
* API:
*
* - inherits [Card API](https://mui.com/material-ui/api/card/)
*
* @remarks
* - ✨ This is a custom component that is not available in the Material-UI library.
* - ✔️ Props of the [Paper](https://mui.com/material-ui/api/card/) component are also available.
* - ✅ `component` prop is supported.
* - ✅ The `ref` is forwarded to the root element.
*
* @template C - The type of the component.
* @param props - The props for the AccountOverview component.
* @param ref - The ref to be forwarded to the Card component.
* @returns The rendered AccountOverview component.
*/
const AccountOverview: OverridableComponent<CardTypeMap<AccountOverviewProps>> & WithWrapperProps = forwardRef(
<C extends ElementType = ElementType>(
{
className,
title,
subheader,
accountCompletionStepsTitle,
accountCompletionSteps,
accountProgress,
user,
cardHeaderProps,
...rest
}: AccountOverviewProps<C>,
ref: Ref<HTMLDivElement>,
): ReactElement => {
const classes: string = clsx('oxygen-account-overview', className);

return (
<Card className={classes} elevation={0} variant="outlined" {...rest}>
<CardHeader
avatar={
<CircularProgressAvatar
color={accountProgress < 100 ? 'warning' : 'success'}
progress={accountProgress}
avatarOptions={{alt: "User's avatar", src: user?.image}}
badgeOptions={{badgeContent: `${accountProgress}%`, color: accountProgress < 100 ? 'warning' : 'success'}}
/>
}
title={title}
subheader={subheader}
{...cardHeaderProps}
/>
{accountCompletionSteps && (
<Box className="oxygen-account-completion-steps-box">
<Divider />
<Carousel title={accountCompletionStepsTitle} steps={accountCompletionSteps} />
</Box>
)}
</Card>
);
};
return (
<Card ref={ref} className={classes} elevation={0} variant="outlined" {...rest}>
<CardHeader
avatar={
<CircularProgressAvatar
color={accountProgress < 100 ? 'warning' : 'success'}
progress={accountProgress}
avatarOptions={{alt: "User's avatar", src: user?.image}}
badgeOptions={{badgeContent: `${accountProgress}%`, color: accountProgress < 100 ? 'warning' : 'success'}}
/>
}
title={title}
subheader={subheader}
{...cardHeaderProps}
/>
{accountCompletionSteps && (
<Box className="oxygen-account-completion-steps-box">
<Divider />
<Carousel title={accountCompletionStepsTitle} steps={accountCompletionSteps} />
</Box>
)}
</Card>
);
},
) as OverridableComponent<CardTypeMap<AccountOverviewProps>> & WithWrapperProps;

AccountOverview.displayName = composeComponentDisplayName(COMPONENT_NAME);
AccountOverview.muiName = COMPONENT_NAME;
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/AccountOverview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
*/

export {default} from './AccountOverview';
export type {AccountOverviewProps} from './AccountOverview';
export * from './AccountOverview';
Loading
Loading