diff --git a/.changeset/delete-wizard-cards.md b/.changeset/delete-wizard-cards.md new file mode 100644 index 0000000000..8d0e6aab74 --- /dev/null +++ b/.changeset/delete-wizard-cards.md @@ -0,0 +1,29 @@ +--- +'@leafygreen-ui/delete-wizard': minor +--- + +Creates reusable `RecommendationCard` & `ReviewCard` for Deletion Wizards + +```tsx +mongodb.design} +/>, +``` + +```tsx + + } + description="Completing this action will terminate all clusters" +> + ...
+
+``` diff --git a/.changeset/wizard-exports.md b/.changeset/wizard-exports.md new file mode 100644 index 0000000000..9c9365b1df --- /dev/null +++ b/.changeset/wizard-exports.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Exports `WizardSubComponentProperties` type diff --git a/packages/delete-wizard/package.json b/packages/delete-wizard/package.json index 68016d844d..3575625049 100644 --- a/packages/delete-wizard/package.json +++ b/packages/delete-wizard/package.json @@ -27,16 +27,25 @@ "access": "public" }, "dependencies": { + "@leafygreen-ui/badge": "workspace:^", "@leafygreen-ui/canvas-header": "workspace:^", + "@leafygreen-ui/card": "workspace:^", + "@leafygreen-ui/expandable-card": "workspace:^", "@leafygreen-ui/compound-component": "workspace:^", - "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/form-footer": "workspace:^", "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/lib": "workspace:^", + "@leafygreen-ui/loading-indicator": "workspace:^", + "@leafygreen-ui/skeleton-loader": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", + "@leafygreen-ui/typography": "workspace:^", "@leafygreen-ui/wizard": "workspace:^", "@lg-tools/test-harnesses": "workspace:^" }, + "peerDependencies": { + "@leafygreen-ui/emotion": "workspace:^5.0.0", + "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" + }, "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/delete-wizard", "repository": { "type": "git", @@ -46,7 +55,6 @@ "url": "https://jira.mongodb.org/projects/LG/summary" }, "devDependencies": { - "@faker-js/faker": "^10.1.0", - "@leafygreen-ui/typography": "workspace:^" + "@faker-js/faker": "^10.1.0" } } diff --git a/packages/delete-wizard/src/DeleteWizard.stories.tsx b/packages/delete-wizard/src/DeleteWizard.stories.tsx index ffe471d80f..5e26d1d064 100644 --- a/packages/delete-wizard/src/DeleteWizard.stories.tsx +++ b/packages/delete-wizard/src/DeleteWizard.stories.tsx @@ -53,7 +53,7 @@ export const LiveExample: StoryObj = { window.location.reload(); }; - const handleStepChange = step => { + const handleStepChange = (step: number) => { console.log('[STORYBOOK] step changed to ', step); }; diff --git a/packages/delete-wizard/src/RecommendationCard/RecommendationCard.stories.tsx b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.stories.tsx new file mode 100644 index 0000000000..b781566644 --- /dev/null +++ b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.stories.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { StoryObj } from '@storybook/react'; + +import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; +import { Link } from '@leafygreen-ui/typography'; + +import { RecommendationCard } from './RecommendationCard'; + +export default { + title: 'Templates/DeleteWizard/RecommendationCard', + component: RecommendationCard, + args: { + category: 'Things', + title: 'Do a thing', + description: 'Before deleting, you need to do a thing.', + link: mongodb.design, + }, +}; + +export const LiveExample: StoryObj = { + render: args => , +}; +export const DarkMode: StoryObj = { + args: { + // @ts-expect-error darkMode is not a prop on RecommendationCard + darkMode: true, + }, + render: args => ( + + + + ), +}; diff --git a/packages/delete-wizard/src/RecommendationCard/RecommendationCard.styles.ts b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.styles.ts new file mode 100644 index 0000000000..3aa6706bc8 --- /dev/null +++ b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.styles.ts @@ -0,0 +1,10 @@ +import { css } from '@leafygreen-ui/emotion'; +import { spacing } from '@leafygreen-ui/tokens'; + +export const titleStyles = css` + font-weight: 600; + margin-block: ${spacing[200]}px; +`; +export const descriptionStyles = css` + margin-block: ${spacing[200]}px; +`; diff --git a/packages/delete-wizard/src/RecommendationCard/RecommendationCard.tsx b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.tsx new file mode 100644 index 0000000000..6c3fbbfa04 --- /dev/null +++ b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { Badge } from '@leafygreen-ui/badge'; +import { Card } from '@leafygreen-ui/card'; +import { BaseFontSize } from '@leafygreen-ui/tokens'; +import { Body, Description } from '@leafygreen-ui/typography'; + +import { descriptionStyles, titleStyles } from './RecommendationCard.styles'; +import type { RecommendationCardProps } from './RecommendationCard.types'; + +export const RecommendationCard = ({ + category, + title, + description, + link, +}: RecommendationCardProps) => { + return ( + + {category} + + {title} + + {description} + {link} + + ); +}; diff --git a/packages/delete-wizard/src/RecommendationCard/RecommendationCard.types.ts b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.types.ts new file mode 100644 index 0000000000..d244e1430e --- /dev/null +++ b/packages/delete-wizard/src/RecommendationCard/RecommendationCard.types.ts @@ -0,0 +1,8 @@ +import { ReactNode } from 'react'; + +export interface RecommendationCardProps { + category: string; + title: string; + description: string; + link: ReactNode; +} diff --git a/packages/delete-wizard/src/RecommendationCard/index.ts b/packages/delete-wizard/src/RecommendationCard/index.ts new file mode 100644 index 0000000000..284db29149 --- /dev/null +++ b/packages/delete-wizard/src/RecommendationCard/index.ts @@ -0,0 +1,2 @@ +export { RecommendationCard } from './RecommendationCard'; +export { type RecommendationCardProps } from './RecommendationCard.types'; diff --git a/packages/delete-wizard/src/ReviewCard/ReviewCard.stories.tsx b/packages/delete-wizard/src/ReviewCard/ReviewCard.stories.tsx new file mode 100644 index 0000000000..191650e13e --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/ReviewCard.stories.tsx @@ -0,0 +1,171 @@ +import React from 'react'; +import { faker } from '@faker-js/faker'; +import { StoryMetaType } from '@lg-tools/storybook-utils'; +import { StoryObj } from '@storybook/react'; + +import { css } from '@leafygreen-ui/emotion'; +import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; + +import { defaultLoadingDescription } from './constants'; +import { ReviewCard } from './ReviewCard'; +import { ReviewState } from './ReviewCard.types'; +import { ReviewCardTitleWithCountEmphasis } from './utils'; + +faker.seed(0); + +export default { + title: 'Templates/DeleteWizard/ReviewCard', + component: ReviewCard, + parameters: { + default: 'LiveExample', + }, + argTypes: { + title: { + control: 'text', + }, + loadingTitle: { + control: 'text', + }, + state: { + control: 'select', + options: Object.values(ReviewState), + }, + }, + args: { + state: ReviewState.Review, + title: ( + + ), + description: 'Completing this action will terminate all clusters', + errorTitle: 'Error loading clusters', + errorDescription: 'Could not fetch clusters. Please try again later', + loadingTitle: 'Loading cluster data', + loadingDescription: defaultLoadingDescription, + completedTitle: 'No clusters detected', + completedDescription: 'Required action complete', + children: faker.lorem.paragraphs(4), + }, +} satisfies StoryMetaType; + +export const LiveExample: StoryObj = { + render: ({ children, ...args }) => ( + +
+ {children} +
+
+ ), +}; + +export const Review: StoryObj = { + parameters: { + controls: { + exclude: [ + 'state', + 'errorTitle', + 'errorDescription', + 'loadingTitle', + 'loadingDescription', + 'completedTitle', + 'completedDescription', + ], + }, + }, + args: { + state: ReviewState.Review, + }, + render: LiveExample.render, +}; + +export const ReviewDarkMode: StoryObj = { + parameters: { + controls: { + exclude: [ + 'state', + 'errorTitle', + 'errorDescription', + 'loadingTitle', + 'loadingDescription', + 'completedTitle', + 'completedDescription', + ], + }, + }, + args: { + darkMode: true, + state: ReviewState.Review, + }, + render: args => ( + + + + ), +}; + +export const Loading: StoryObj = { + parameters: { + controls: { + exclude: [ + 'state', + 'title', + 'description', + 'errorTitle', + 'errorDescription', + 'completedTitle', + 'completedDescription', + ], + }, + }, + args: { + state: ReviewState.Loading, + }, + render: LiveExample.render, +}; + +export const Complete: StoryObj = { + parameters: { + controls: { + exclude: [ + 'state', + 'title', + 'description', + 'errorTitle', + 'errorDescription', + 'loadingTitle', + 'loadingDescription', + ], + }, + }, + args: { + state: ReviewState.Complete, + }, + render: LiveExample.render, +}; + +export const Error: StoryObj = { + parameters: { + controls: { + exclude: [ + 'state', + 'title', + 'description', + 'completedTitle', + 'completedDescription', + 'loadingTitle', + 'loadingDescription', + ], + }, + }, + args: { + state: ReviewState.Error, + }, + render: LiveExample.render, +}; diff --git a/packages/delete-wizard/src/ReviewCard/ReviewCard.styles.ts b/packages/delete-wizard/src/ReviewCard/ReviewCard.styles.ts new file mode 100644 index 0000000000..2a891bdcf2 --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/ReviewCard.styles.ts @@ -0,0 +1,22 @@ +import { css } from '@leafygreen-ui/emotion'; +import { spacing } from '@leafygreen-ui/tokens'; + +export const reviewCardStyles = css` + width: 100%; + & h6 { + display: block; + } +`; + +export const descriptionBodyStyles = css` + margin-top: 4px; +`; + +export const expandableCardContentStyles = css` + padding: unset; +`; + +export const cardContentWrapperStyles = css` + padding-bottom: ${spacing[400]}px; + overflow: hidden; +`; diff --git a/packages/delete-wizard/src/ReviewCard/ReviewCard.tsx b/packages/delete-wizard/src/ReviewCard/ReviewCard.tsx new file mode 100644 index 0000000000..a5193d8c9e --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/ReviewCard.tsx @@ -0,0 +1,76 @@ +import React from 'react'; + +import { Card } from '@leafygreen-ui/card'; +import { ExpandableCard } from '@leafygreen-ui/expandable-card'; +import { Body, Subtitle } from '@leafygreen-ui/typography'; + +import { + defaultCompleteDescription, + defaultCompleteTitle, + defaultErrorDescription, + defaultErrorTitle, + defaultLoadingDescription, + defaultLoadingTitle, +} from './constants'; +import { + cardContentWrapperStyles, + descriptionBodyStyles, + expandableCardContentStyles, + reviewCardStyles, +} from './ReviewCard.styles'; +import { type ReviewCardProps, ReviewState } from './ReviewCard.types'; +import { ReviewCardDescription, ReviewCardTitleElement } from './utils'; + +export const ReviewCard = ({ + state, + title, + description, + errorTitle = defaultErrorTitle, + errorDescription = defaultErrorDescription, + loadingTitle = defaultLoadingTitle, + loadingDescription = defaultLoadingDescription, + completedTitle = defaultCompleteTitle, + completedDescription = defaultCompleteDescription, + children, + ...rest +}: ReviewCardProps) => { + if (state === ReviewState.Review) { + return ( + +
{children}
+
+ ); + } + + return ( + + + + + {description && ( + + + + )} + + ); +}; diff --git a/packages/delete-wizard/src/ReviewCard/ReviewCard.types.ts b/packages/delete-wizard/src/ReviewCard/ReviewCard.types.ts new file mode 100644 index 0000000000..41f84793d7 --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/ReviewCard.types.ts @@ -0,0 +1,54 @@ +import type { ReactNode } from 'react'; + +import type { ExpandableCardProps } from '@leafygreen-ui/expandable-card'; + +export enum ReviewState { + Loading = 'loading', + Review = 'review', + Error = 'error', + Complete = 'complete', +} +export interface ReviewCardProps extends ExpandableCardProps { + /** + * @required + * Determines which title and description are rendered + */ + state: ReviewState; + + /** + * Title displayed when state === 'error' + * @default 'Error loading data' + */ + errorTitle?: ReactNode; + + /** + * Description displayed when state === 'error' + * @default 'Could not retrieve data' + */ + errorDescription?: ReactNode; + + /** + * Title displayed when state === 'loading' + * @default Skeleton loader + */ + loadingTitle?: ReactNode; + + /** + * Description displayed when state === 'loading' + * @default 'This may take a few moments' + */ + loadingDescription?: string; + + /** + * Title displayed when state === 'complete' + * @default 'None detected' + */ + completedTitle?: ReactNode; + /** + * Description displaced when state === 'complete' + * @default 'Review complete' + */ + completedDescription?: ReactNode; +} +export interface InheritedReviewCardProps + extends Omit {} diff --git a/packages/delete-wizard/src/ReviewCard/constants.tsx b/packages/delete-wizard/src/ReviewCard/constants.tsx new file mode 100644 index 0000000000..36645f6a7e --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/constants.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { css } from '@leafygreen-ui/emotion'; +import { Skeleton } from '@leafygreen-ui/skeleton-loader'; + +export const defaultErrorTitle = 'Error loading data'; +export const defaultErrorDescription = 'Could not retrieve data'; +export const defaultLoadingTitle = ( + +); +export const defaultLoadingDescription = 'This may take a few moments'; +export const defaultCompleteTitle = 'None detected'; +export const defaultCompleteDescription = 'Required action complete'; diff --git a/packages/delete-wizard/src/ReviewCard/index.ts b/packages/delete-wizard/src/ReviewCard/index.ts new file mode 100644 index 0000000000..20bfe6acaf --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/index.ts @@ -0,0 +1,4 @@ +export { ReviewCard } from './ReviewCard'; +export { type ReviewCardProps } from './ReviewCard.types'; +export { ReviewState } from './ReviewCard.types'; +export { ReviewCardTitleWithCountEmphasis } from './utils'; diff --git a/packages/delete-wizard/src/ReviewCard/utils/ReviewCardDescription.tsx b/packages/delete-wizard/src/ReviewCard/utils/ReviewCardDescription.tsx new file mode 100644 index 0000000000..522a361098 --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/utils/ReviewCardDescription.tsx @@ -0,0 +1,74 @@ +import React, { ReactNode } from 'react'; + +import { css } from '@leafygreen-ui/emotion'; +import { Icon } from '@leafygreen-ui/icon'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { Spinner } from '@leafygreen-ui/loading-indicator/spinner'; +import { color, Size, spacing } from '@leafygreen-ui/tokens'; +import { Description } from '@leafygreen-ui/typography'; + +import { type ReviewCardProps, ReviewState } from '../ReviewCard.types'; + +type ReviewCardDescriptionProps = Pick< + ReviewCardProps, + | 'state' + | 'description' + | 'completedDescription' + | 'loadingDescription' + | 'errorDescription' +>; + +export const ReviewCardDescription = ({ + state, + description, + errorDescription, + loadingDescription, + completedDescription, +}: ReviewCardDescriptionProps): ReactNode => { + const { theme } = useDarkMode(); + + switch (state) { + case ReviewState.Error: + return ( +
+ + {errorDescription} +
+ ); + case ReviewState.Loading: + return ( + <> + + {loadingDescription} + + ); + case ReviewState.Complete: + return ( +
+ + {completedDescription} +
+ ); + case ReviewState.Review: + default: + return description; + } +}; diff --git a/packages/delete-wizard/src/ReviewCard/utils/ReviewCardTitleElement.tsx b/packages/delete-wizard/src/ReviewCard/utils/ReviewCardTitleElement.tsx new file mode 100644 index 0000000000..91d9d0d3fc --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/utils/ReviewCardTitleElement.tsx @@ -0,0 +1,26 @@ +import { FunctionComponent } from 'react'; + +import { type ReviewCardProps, ReviewState } from '../ReviewCard.types'; + +export const ReviewCardTitleElement = ({ + state, + title, + errorTitle, + loadingTitle, + completedTitle, +}: Pick< + ReviewCardProps, + 'state' | 'title' | 'completedTitle' | 'loadingTitle' | 'errorTitle' +>): ReturnType => { + switch (state) { + case ReviewState.Error: + return errorTitle; + case ReviewState.Loading: + return loadingTitle; + case ReviewState.Complete: + return completedTitle; + case ReviewState.Review: + default: + return title; + } +}; diff --git a/packages/delete-wizard/src/ReviewCard/utils/ReviewCardTitleWithCountEmphasis.tsx b/packages/delete-wizard/src/ReviewCard/utils/ReviewCardTitleWithCountEmphasis.tsx new file mode 100644 index 0000000000..43e8ac2e73 --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/utils/ReviewCardTitleWithCountEmphasis.tsx @@ -0,0 +1,35 @@ +import React, { PropsWithChildren } from 'react'; + +import { css } from '@leafygreen-ui/emotion'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { color } from '@leafygreen-ui/tokens'; + +const TitleEm = ({ children }: PropsWithChildren<{}>) => { + const { theme } = useDarkMode(); + return ( + + {children} + + ); +}; + +export const ReviewCardTitleWithCountEmphasis = ({ + verb, + count, + resource, +}: { + verb: string; + count: number; + resource: string; +}) => { + return ( + <> + {verb} {count} {resource} + + ); +}; diff --git a/packages/delete-wizard/src/ReviewCard/utils/index.ts b/packages/delete-wizard/src/ReviewCard/utils/index.ts new file mode 100644 index 0000000000..b9c77f8f6d --- /dev/null +++ b/packages/delete-wizard/src/ReviewCard/utils/index.ts @@ -0,0 +1,3 @@ +export { ReviewCardDescription } from './ReviewCardDescription'; +export { ReviewCardTitleElement } from './ReviewCardTitleElement'; +export { ReviewCardTitleWithCountEmphasis } from './ReviewCardTitleWithCountEmphasis'; diff --git a/packages/delete-wizard/src/index.ts b/packages/delete-wizard/src/index.ts index e73d5c48d3..5804c8dff4 100644 --- a/packages/delete-wizard/src/index.ts +++ b/packages/delete-wizard/src/index.ts @@ -4,3 +4,13 @@ export { useDeleteWizardContext, useDeleteWizardStepContext, } from './DeleteWizard'; +export { + RecommendationCard, + type RecommendationCardProps, +} from './RecommendationCard'; +export { + ReviewCard, + type ReviewCardProps, + ReviewCardTitleWithCountEmphasis, + ReviewState, +} from './ReviewCard'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f7f9d7ffd..9d61ebfe0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1671,27 +1671,48 @@ importers: packages/delete-wizard: dependencies: + '@leafygreen-ui/badge': + specifier: workspace:^ + version: link:../badge '@leafygreen-ui/canvas-header': specifier: workspace:^ version: link:../canvas-header + '@leafygreen-ui/card': + specifier: workspace:^ + version: link:../card '@leafygreen-ui/compound-component': specifier: workspace:^ version: link:../compound-component '@leafygreen-ui/emotion': - specifier: workspace:^ + specifier: workspace:^5.0.0 version: link:../emotion + '@leafygreen-ui/expandable-card': + specifier: workspace:^ + version: link:../expandable-card '@leafygreen-ui/form-footer': specifier: workspace:^ version: link:../form-footer '@leafygreen-ui/icon': specifier: workspace:^ version: link:../icon + '@leafygreen-ui/leafygreen-provider': + specifier: workspace:^5.0.0 || ^4.0.0 || ^3.2.0 + version: link:../leafygreen-provider '@leafygreen-ui/lib': specifier: workspace:^ version: link:../lib + '@leafygreen-ui/loading-indicator': + specifier: workspace:^ + version: link:../loading-indicator + '@leafygreen-ui/skeleton-loader': + specifier: workspace:^ + version: link:../skeleton-loader '@leafygreen-ui/tokens': specifier: workspace:^ version: link:../tokens + '@leafygreen-ui/typography': + specifier: workspace:^ + version: link:../typography '@leafygreen-ui/wizard': specifier: workspace:^ version: link:../wizard @@ -1702,9 +1723,6 @@ importers: '@faker-js/faker': specifier: ^10.1.0 version: 10.1.0 - '@leafygreen-ui/typography': - specifier: workspace:^ - version: link:../typography packages/descendants: dependencies: diff --git a/tools/install/src/ALL_PACKAGES.ts b/tools/install/src/ALL_PACKAGES.ts index 64cba25311..ae30386b25 100644 --- a/tools/install/src/ALL_PACKAGES.ts +++ b/tools/install/src/ALL_PACKAGES.ts @@ -21,6 +21,7 @@ export const ALL_PACKAGES = [ '@leafygreen-ui/copyable', '@leafygreen-ui/date-picker', '@leafygreen-ui/date-utils', + '@leafygreen-ui/delete-wizard', '@leafygreen-ui/descendants', '@leafygreen-ui/drawer', '@leafygreen-ui/emotion',