From dae23a2e5c6e9126d461413ff6a2f84e721444b6 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Wed, 22 Oct 2025 17:28:29 -0400 Subject: [PATCH 01/51] rm step wrapper --- .changeset/wizard-patch-0.md | 5 +++++ packages/wizard/src/Wizard/Wizard.styles.ts | 5 ----- packages/wizard/src/Wizard/Wizard.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 .changeset/wizard-patch-0.md diff --git a/.changeset/wizard-patch-0.md b/.changeset/wizard-patch-0.md new file mode 100644 index 0000000000..6f7f76d3f7 --- /dev/null +++ b/.changeset/wizard-patch-0.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Removes wrapper div around step children diff --git a/packages/wizard/src/Wizard/Wizard.styles.ts b/packages/wizard/src/Wizard/Wizard.styles.ts index c6ca33aaee..a20d2f2a15 100644 --- a/packages/wizard/src/Wizard/Wizard.styles.ts +++ b/packages/wizard/src/Wizard/Wizard.styles.ts @@ -8,8 +8,3 @@ export const wizardContainerStyles = css` flex-direction: column; gap: ${spacing[600]}px; `; - -export const stepContentStyles = css` - flex: 1; - min-height: 0; /* Allow content to shrink */ -`; diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index ad5ed5d165..60c0e6efcd 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -13,7 +13,7 @@ import { WizardProvider } from '../WizardContext/WizardContext'; import { WizardFooter } from '../WizardFooter'; import { WizardStep } from '../WizardStep'; -import { stepContentStyles, wizardContainerStyles } from './Wizard.styles'; +import { wizardContainerStyles } from './Wizard.styles'; import { WizardProps } from './Wizard.types'; export const Wizard = CompoundComponent( @@ -71,7 +71,7 @@ export const Wizard = CompoundComponent( return (
-
{currentStep}
+ {currentStep} {footerChild}
From 9c6c5887e7e45f1e6728d12285ae1bc2e2c80eeb Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Wed, 22 Oct 2025 17:57:45 -0400 Subject: [PATCH 02/51] rm descendants dep --- .changeset/wizard-patch-1.md | 5 ++++ packages/wizard/package.json | 6 ++--- packages/wizard/src/Wizard/Wizard.tsx | 23 +++++++------------ packages/wizard/src/Wizard/Wizard.types.ts | 10 -------- packages/wizard/src/Wizard/index.ts | 2 +- .../src/WizardContext/WizardContext.tsx | 4 +--- .../wizard/src/WizardFooter/WizardFooter.tsx | 5 ++-- 7 files changed, 19 insertions(+), 36 deletions(-) create mode 100644 .changeset/wizard-patch-1.md diff --git a/.changeset/wizard-patch-1.md b/.changeset/wizard-patch-1.md new file mode 100644 index 0000000000..90935fc790 --- /dev/null +++ b/.changeset/wizard-patch-1.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Removes descendants dep. Clamp activeStep between 0 & step count \ No newline at end of file diff --git a/packages/wizard/package.json b/packages/wizard/package.json index be08f12182..2ab7b30147 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -1,7 +1,6 @@ - { "name": "@leafygreen-ui/wizard", - "version": "0.0.1", + "version": "0.1.0-local.1", "description": "LeafyGreen UI Kit Wizard", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -30,7 +29,6 @@ "dependencies": { "@leafygreen-ui/button": "workspace:^", "@leafygreen-ui/compound-component": "workspace:^", - "@leafygreen-ui/descendants": "workspace:^", "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/form-footer": "workspace:^", "@leafygreen-ui/hooks": "workspace:^", @@ -40,7 +38,7 @@ "@leafygreen-ui/typography": "workspace:^", "@lg-tools/test-harnesses": "workspace:^" }, - "devDependencies" : { + "devDependencies": { "@leafygreen-ui/icon": "workspace:^", "@faker-js/faker": "^8.0.0" }, diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index 60c0e6efcd..a96c678243 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -5,7 +5,6 @@ import { findChild, findChildren, } from '@leafygreen-ui/compound-component'; -import { Direction } from '@leafygreen-ui/descendants'; import { useControlled } from '@leafygreen-ui/hooks'; import { WizardSubComponentProperties } from '../constants'; @@ -48,21 +47,15 @@ export const Wizard = CompoundComponent( } const updateStep = useCallback( - (direction: Direction) => { - const getNextStep = (curr: number) => { - switch (direction) { - case Direction.Next: - return Math.min(curr + 1, stepChildren.length - 1); - case Direction.Prev: - return Math.max(curr - 1, 0); - } - }; - - // TODO pass getNextStep into setter as callback https://jira.mongodb.org/browse/LG-5607 - const nextStep = getNextStep(activeStep); - setActiveStep(nextStep); + (step: number) => { + // Clamp the step value between 0 and stepChildren.length - 1 + const clampedStep = Math.max( + 0, + Math.min(step, stepChildren.length - 1), + ); + setActiveStep(clampedStep); }, - [activeStep, setActiveStep, stepChildren.length], + [setActiveStep, stepChildren.length], ); // Get the current step to render diff --git a/packages/wizard/src/Wizard/Wizard.types.ts b/packages/wizard/src/Wizard/Wizard.types.ts index 7fc1a3901a..e87d22b8c3 100644 --- a/packages/wizard/src/Wizard/Wizard.types.ts +++ b/packages/wizard/src/Wizard/Wizard.types.ts @@ -1,8 +1,5 @@ import { ComponentPropsWithRef, ReactNode } from 'react'; -import { WizardFooter } from '../WizardFooter'; -import { WizardStep } from '../WizardStep'; - export interface WizardProps extends ComponentPropsWithRef<'div'> { /** * The current active step index (0-based). If provided, the component operates in controlled mode. @@ -19,10 +16,3 @@ export interface WizardProps extends ComponentPropsWithRef<'div'> { */ children: ReactNode; } - -export interface WizardComponent { - (props: WizardProps): JSX.Element; - Step: typeof WizardStep; - Footer: typeof WizardFooter; - displayName: string; -} diff --git a/packages/wizard/src/Wizard/index.ts b/packages/wizard/src/Wizard/index.ts index a6d6cd5342..639f0423de 100644 --- a/packages/wizard/src/Wizard/index.ts +++ b/packages/wizard/src/Wizard/index.ts @@ -1,2 +1,2 @@ export { Wizard } from './Wizard'; -export { type WizardComponent, type WizardProps } from './Wizard.types'; +export { type WizardProps } from './Wizard.types'; diff --git a/packages/wizard/src/WizardContext/WizardContext.tsx b/packages/wizard/src/WizardContext/WizardContext.tsx index c3d50b9d3f..ba4bf230d7 100644 --- a/packages/wizard/src/WizardContext/WizardContext.tsx +++ b/packages/wizard/src/WizardContext/WizardContext.tsx @@ -1,11 +1,9 @@ import React, { createContext, PropsWithChildren, useContext } from 'react'; -import { Direction } from '@leafygreen-ui/descendants'; - export interface WizardContextData { isWizardContext: boolean; activeStep: number; - updateStep: (direction: Direction) => void; + updateStep: (step: number) => void; } export const WizardContext = createContext({ diff --git a/packages/wizard/src/WizardFooter/WizardFooter.tsx b/packages/wizard/src/WizardFooter/WizardFooter.tsx index 2a46c9201a..011bf41d2b 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.tsx +++ b/packages/wizard/src/WizardFooter/WizardFooter.tsx @@ -1,7 +1,6 @@ import React, { MouseEventHandler } from 'react'; import { CompoundSubComponent } from '@leafygreen-ui/compound-component'; -import { Direction } from '@leafygreen-ui/descendants'; import { FormFooter } from '@leafygreen-ui/form-footer'; import { consoleOnce } from '@leafygreen-ui/lib'; @@ -21,14 +20,14 @@ export const WizardFooter = CompoundSubComponent( const { isWizardContext, activeStep, updateStep } = useWizardContext(); const handleBackButtonClick: MouseEventHandler = e => { - updateStep(Direction.Prev); + updateStep(activeStep - 1); backButtonProps?.onClick?.(e); }; const handlePrimaryButtonClick: MouseEventHandler< HTMLButtonElement > = e => { - updateStep(Direction.Next); + updateStep(activeStep + 1); primaryButtonProps.onClick?.(e); }; From 79774a061ea0d4322cf0eaf1111660d6e52725ec Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Wed, 22 Oct 2025 18:07:30 -0400 Subject: [PATCH 03/51] export WizardProvider --- .changeset/wizard-patch-2.md | 5 +++++ packages/wizard/src/index.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/wizard-patch-2.md diff --git a/.changeset/wizard-patch-2.md b/.changeset/wizard-patch-2.md new file mode 100644 index 0000000000..4a924c356f --- /dev/null +++ b/.changeset/wizard-patch-2.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Exports WizardProvider diff --git a/packages/wizard/src/index.ts b/packages/wizard/src/index.ts index 1d5270af64..ad4bdf22da 100644 --- a/packages/wizard/src/index.ts +++ b/packages/wizard/src/index.ts @@ -3,6 +3,7 @@ export { useWizardContext, WizardContext, type WizardContextData, + WizardProvider, } from './WizardContext'; export { type WizardFooterProps } from './WizardFooter'; export { type WizardStepProps } from './WizardStep'; From c2f40f974cf42caca872a296cbd40674a160b03b Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Tue, 4 Nov 2025 14:01:53 -0500 Subject: [PATCH 04/51] delete-wizard-demo private endpoints useFetchRequiredActionTableData renam ReqAct cards composable basic table stream processing card federated db card applications card clusters card wizard step context Delete requiredActionsConfig.tsx re-enable wizard add useRequiredActionAcknowledgements mv required action. add skeleton Update ModelApiKeysCard.tsx --- .npmrc | 7 +- packages/descendants/package.json | 2 +- packages/toast/package.json | 2 +- packages/wizard/package.json | 15 +- packages/wizard/src/HELPERS/BasicTable.tsx | 52 +++ .../src/HELPERS/TableHeaderWithSubtitle.tsx | 35 ++ packages/wizard/src/Wizard/Wizard.tsx | 21 +- packages/wizard/src/Wizard/Wizard.types.ts | 4 +- .../src/WizardContext/WizardContext.tsx | 7 + .../wizard/src/WizardFooter/WizardFooter.tsx | 3 + packages/wizard/src/WizardStep/WizardStep.tsx | 42 ++- .../wizard/src/WizardStep/WizardStep.types.ts | 13 +- .../src/WizardStep/WizardStepContext.tsx | 15 + packages/wizard/src/WizardStep/index.ts | 1 + .../wizard/src/demo/DeleteWizard.stories.tsx | 78 ++++ .../src/demo/DeleteWizardStepContent.types.ts | 1 + .../wizard/src/demo/RecommendedActionCard.tsx | 44 +++ .../wizard/src/demo/RecommendedActions.tsx | 69 ++++ .../RequiredActionCards/ApplicationsCard.tsx | 73 ++++ .../demo/RequiredActionCards/ClustersCard.tsx | 234 ++++++++++++ .../RequiredActionCards/FederatedDbCard.tsx | 141 +++++++ .../RequiredActionCards/ModelApiKeysCard.tsx | 105 ++++++ .../PrivateEndpointsCard.tsx | 348 ++++++++++++++++++ .../RequiredActionCard.tsx | 97 +++++ .../StreamProcessingCard.tsx | 110 ++++++ .../src/demo/RequiredActionCards/index.ts | 6 + packages/wizard/src/demo/RequiredActions.tsx | 96 +++++ packages/wizard/src/demo/constants.ts | 3 + .../hooks/useFetchRequiredActionTableData.ts | 41 +++ .../useRequiredActionAcknowledgements.ts | 44 +++ .../wizard/src/demo/recommendedActionData.tsx | 67 ++++ packages/wizard/src/index.ts | 2 +- packages/wizard/tsconfig.json | 31 +- pnpm-lock.yaml | 67 +++- 34 files changed, 1819 insertions(+), 57 deletions(-) create mode 100644 packages/wizard/src/HELPERS/BasicTable.tsx create mode 100644 packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx create mode 100644 packages/wizard/src/WizardStep/WizardStepContext.tsx create mode 100644 packages/wizard/src/demo/DeleteWizard.stories.tsx create mode 100644 packages/wizard/src/demo/DeleteWizardStepContent.types.ts create mode 100644 packages/wizard/src/demo/RecommendedActionCard.tsx create mode 100644 packages/wizard/src/demo/RecommendedActions.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx create mode 100644 packages/wizard/src/demo/RequiredActionCards/index.ts create mode 100644 packages/wizard/src/demo/RequiredActions.tsx create mode 100644 packages/wizard/src/demo/constants.ts create mode 100644 packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts create mode 100644 packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts create mode 100644 packages/wizard/src/demo/recommendedActionData.tsx diff --git a/.npmrc b/.npmrc index d805c4429b..6b1b4eaaef 100644 --- a/.npmrc +++ b/.npmrc @@ -8,4 +8,9 @@ public-hoist-pattern[]=@testing-library/* public-hoist-pattern[]=@types/* public-hoist-pattern[]=*jest* public-hoist-pattern[]=react -public-hoist-pattern[]=react-dom \ No newline at end of file +public-hoist-pattern[]=react-dom +# @leafygreen-ui:registry=http://localhost:4873 +# @lg-chat:registry=http://localhost:4873 +# @lg-charts:registry=http://localhost:4873 +# @lg-tools:registry=http://localhost:4873 +# //localhost:4873/:_authToken=YzNjMGRkMDY3ZjI3N2IzNzUxYzk3NjlkOWNjNTc1MGI6NjgwNmIyZDAwMDIzYjEyNzVjN2Q5NWZhZmRkZmFlMTY4ZTMzOWRhMDAwOTllOWMzZDMxYzcxZmFkMTE5 \ No newline at end of file diff --git a/packages/descendants/package.json b/packages/descendants/package.json index b360383fac..2f6ce5d968 100644 --- a/packages/descendants/package.json +++ b/packages/descendants/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@faker-js/faker": "^8.0.0", - "@storybook/react": "8.6.14", + "@storybook/react": "latest", "@leafygreen-ui/button": "workspace:^", "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/popover": "workspace:^", diff --git a/packages/toast/package.json b/packages/toast/package.json index f2a7440ff8..2597770900 100644 --- a/packages/toast/package.json +++ b/packages/toast/package.json @@ -35,7 +35,7 @@ "@faker-js/faker": "^8.0.0", "@leafygreen-ui/button": "workspace:^", "@lg-tools/build": "workspace:^", - "@storybook/types": "^8.5.3", + "@storybook/types": "latest", "react-test-renderer": "^18.2.0" }, "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/toast", diff --git a/packages/wizard/package.json b/packages/wizard/package.json index 2ab7b30147..068d337352 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -36,11 +36,22 @@ "@leafygreen-ui/polymorphic": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", "@leafygreen-ui/typography": "workspace:^", - "@lg-tools/test-harnesses": "workspace:^" + "@lg-tools/test-harnesses": "workspace:^", + "date-fns": "^2.30.0" }, "devDependencies": { + "@faker-js/faker": "^8.0.0", + "@leafygreen-ui/badge": "workspace:^", + "@leafygreen-ui/button": "workspace:^", + "@leafygreen-ui/card": "workspace:^", + "@leafygreen-ui/checkbox": "workspace:^", + "@leafygreen-ui/expandable-card": "workspace:^", + "@leafygreen-ui/form-footer": "workspace:^", "@leafygreen-ui/icon": "workspace:^", - "@faker-js/faker": "^8.0.0" + "@leafygreen-ui/loading-indicator": "workspace:^", + "@leafygreen-ui/segmented-control": "workspace:^", + "@leafygreen-ui/skeleton-loader": "workspace:^", + "@leafygreen-ui/typography": "workspace:^" }, "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/wizard", "repository": { diff --git a/packages/wizard/src/HELPERS/BasicTable.tsx b/packages/wizard/src/HELPERS/BasicTable.tsx new file mode 100644 index 0000000000..74c7c2cfe4 --- /dev/null +++ b/packages/wizard/src/HELPERS/BasicTable.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import { + Cell, + flexRender, + HeaderCell, + HeaderRow, + LeafyGreenTable, + LGRowData, + Row, + Table, + TableBody, + TableHead, +} from '@leafygreen-ui/table'; + +interface BasicTableProps { + table: LeafyGreenTable; +} + +export const BasicTable = ({ + table, +}: BasicTableProps) => { + return ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+ ); +}; diff --git a/packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx b/packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx new file mode 100644 index 0000000000..1039a2169b --- /dev/null +++ b/packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import { css } from '@leafygreen-ui/emotion'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { color } from '@leafygreen-ui/tokens'; + +export const TableHeaderWithSubtitle = ({ + title, + subtitle, +}: { + title: string; + subtitle: string; +}) => { + const { theme } = useDarkMode(); + + return ( +
+
+ {title} +
+
+ {subtitle} +
+
+ ); +}; diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index a96c678243..a4f5a0fff2 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -2,7 +2,6 @@ import React, { useCallback } from 'react'; import { CompoundComponent, - findChild, findChildren, } from '@leafygreen-ui/compound-component'; import { useControlled } from '@leafygreen-ui/hooks'; @@ -12,24 +11,14 @@ import { WizardProvider } from '../WizardContext/WizardContext'; import { WizardFooter } from '../WizardFooter'; import { WizardStep } from '../WizardStep'; -import { wizardContainerStyles } from './Wizard.styles'; import { WizardProps } from './Wizard.types'; export const Wizard = CompoundComponent( - ({ - activeStep: activeStepProp, - onStepChange, - children, - ...rest - }: WizardProps) => { + ({ activeStep: activeStepProp, onStepChange, children }: WizardProps) => { const stepChildren = findChildren( children, WizardSubComponentProperties.Step, ); - const footerChild = findChild( - children, - WizardSubComponentProperties.Footer, - ); // Controlled/Uncontrolled activeStep value const { value: activeStep, updateValue: setActiveStep } = @@ -58,15 +47,9 @@ export const Wizard = CompoundComponent( [setActiveStep, stepChildren.length], ); - // Get the current step to render - const currentStep = stepChildren[activeStep] || null; - return ( -
- {currentStep} - {footerChild} -
+ {stepChildren.map((child, i) => (i === activeStep ? child : null))}
); }, diff --git a/packages/wizard/src/Wizard/Wizard.types.ts b/packages/wizard/src/Wizard/Wizard.types.ts index e87d22b8c3..86ac5fd724 100644 --- a/packages/wizard/src/Wizard/Wizard.types.ts +++ b/packages/wizard/src/Wizard/Wizard.types.ts @@ -1,6 +1,6 @@ -import { ComponentPropsWithRef, ReactNode } from 'react'; +import { ReactNode } from 'react'; -export interface WizardProps extends ComponentPropsWithRef<'div'> { +export interface WizardProps { /** * The current active step index (0-based). If provided, the component operates in controlled mode. */ diff --git a/packages/wizard/src/WizardContext/WizardContext.tsx b/packages/wizard/src/WizardContext/WizardContext.tsx index ba4bf230d7..a91f830eed 100644 --- a/packages/wizard/src/WizardContext/WizardContext.tsx +++ b/packages/wizard/src/WizardContext/WizardContext.tsx @@ -3,6 +3,13 @@ import React, { createContext, PropsWithChildren, useContext } from 'react'; export interface WizardContextData { isWizardContext: boolean; activeStep: number; + /** + * Updates the Wizard `activeStep` to the provided step number. + * Note: The Wizard implementation internally handles clamping the step number + * to the available number of steps + * @param step + * @returns + */ updateStep: (step: number) => void; } diff --git a/packages/wizard/src/WizardFooter/WizardFooter.tsx b/packages/wizard/src/WizardFooter/WizardFooter.tsx index 011bf41d2b..8c5c4f7104 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.tsx +++ b/packages/wizard/src/WizardFooter/WizardFooter.tsx @@ -6,6 +6,7 @@ import { consoleOnce } from '@leafygreen-ui/lib'; import { WizardSubComponentProperties } from '../constants'; import { useWizardContext } from '../WizardContext'; +import { useWizardStepContext } from '../WizardStep'; import { WizardFooterProps } from './WizardFooter.types'; @@ -18,6 +19,7 @@ export const WizardFooter = CompoundSubComponent( ...rest }: WizardFooterProps) => { const { isWizardContext, activeStep, updateStep } = useWizardContext(); + const { isAcknowledged } = useWizardStepContext(); const handleBackButtonClick: MouseEventHandler = e => { updateStep(activeStep - 1); @@ -53,6 +55,7 @@ export const WizardFooter = CompoundSubComponent( cancelButtonProps={cancelButtonProps} primaryButtonProps={{ ...primaryButtonProps, + disabled: !isAcknowledged, onClick: handlePrimaryButtonClick, }} /> diff --git a/packages/wizard/src/WizardStep/WizardStep.tsx b/packages/wizard/src/WizardStep/WizardStep.tsx index 4af5eba958..25900687fe 100644 --- a/packages/wizard/src/WizardStep/WizardStep.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.tsx @@ -1,20 +1,26 @@ -import React from 'react'; +import React, { useState } from 'react'; -import { CompoundSubComponent } from '@leafygreen-ui/compound-component'; +import { + CompoundSubComponent, + filterChildren, + findChild, +} from '@leafygreen-ui/compound-component'; import { cx } from '@leafygreen-ui/emotion'; +import { useIdAllocator } from '@leafygreen-ui/hooks'; import { consoleOnce } from '@leafygreen-ui/lib'; -import { Description, H3 } from '@leafygreen-ui/typography'; import { WizardSubComponentProperties } from '../constants'; import { useWizardContext } from '../WizardContext'; -import { TextNode } from './TextNode'; import { stepStyles } from './WizardStep.styles'; import { WizardStepProps } from './WizardStep.types'; +import { WizardStepContext } from './WizardStepContext'; export const WizardStep = CompoundSubComponent( - ({ title, description, children, className, ...rest }: WizardStepProps) => { + ({ children, className, ...rest }: WizardStepProps) => { + const stepId = useIdAllocator({ prefix: 'wizard-step' }); const { isWizardContext } = useWizardContext(); + const [isAcknowledged, setAcknowledged] = useState(false); if (!isWizardContext) { consoleOnce.error( @@ -23,12 +29,28 @@ export const WizardStep = CompoundSubComponent( return null; } + const footerChild = findChild( + children, + WizardSubComponentProperties.Footer, + ); + + const restChildren = filterChildren(children, [ + WizardSubComponentProperties.Footer, + ]); + return ( -
- {title} - {description && {description}} -
{children}
-
+ +
+ {restChildren} + {footerChild} +
+
); }, { diff --git a/packages/wizard/src/WizardStep/WizardStep.types.ts b/packages/wizard/src/WizardStep/WizardStep.types.ts index b0e9e97f70..d594f78aa1 100644 --- a/packages/wizard/src/WizardStep/WizardStep.types.ts +++ b/packages/wizard/src/WizardStep/WizardStep.types.ts @@ -1,17 +1,6 @@ import { ReactNode } from 'react'; -export interface WizardStepProps - extends Omit, 'title'> { - /** - * The title of the step - */ - title: ReactNode; - - /** - * The description of the step - */ - description?: ReactNode; - +export interface WizardStepProps extends React.ComponentProps<'div'> { /** * The content of the step */ diff --git a/packages/wizard/src/WizardStep/WizardStepContext.tsx b/packages/wizard/src/WizardStep/WizardStepContext.tsx new file mode 100644 index 0000000000..fec1807aed --- /dev/null +++ b/packages/wizard/src/WizardStep/WizardStepContext.tsx @@ -0,0 +1,15 @@ +import { createContext, Dispatch, SetStateAction, useContext } from 'react'; + +export interface WizardStepContextData { + stepId: string; + isAcknowledged: boolean; + setAcknowledged: Dispatch>; +} + +export const WizardStepContext = createContext({ + stepId: '', + isAcknowledged: false, + setAcknowledged: () => {}, +}); + +export const useWizardStepContext = () => useContext(WizardStepContext); diff --git a/packages/wizard/src/WizardStep/index.ts b/packages/wizard/src/WizardStep/index.ts index f7e0b02596..6ef4b499f6 100644 --- a/packages/wizard/src/WizardStep/index.ts +++ b/packages/wizard/src/WizardStep/index.ts @@ -1,2 +1,3 @@ export { WizardStep } from './WizardStep'; export { type WizardStepProps } from './WizardStep.types'; +export { useWizardStepContext } from './WizardStepContext'; diff --git a/packages/wizard/src/demo/DeleteWizard.stories.tsx b/packages/wizard/src/demo/DeleteWizard.stories.tsx new file mode 100644 index 0000000000..0630ed55f0 --- /dev/null +++ b/packages/wizard/src/demo/DeleteWizard.stories.tsx @@ -0,0 +1,78 @@ +import React from 'react'; + +import { Variant as ButtonVariant } from '@leafygreen-ui/button'; +import { css } from '@leafygreen-ui/emotion'; +import { Icon } from '@leafygreen-ui/icon'; + +import { Wizard } from '../'; + +import { DELETE_PAGE_MAX_WIDTH } from './constants'; +import { RecommendedActions } from './RecommendedActions'; +import { RequiredActions } from './RequiredActions'; + +const stepStyles = css` + overflow: scroll; +`; + +const footerStyles = css` + bottom: 0; +`; +const footerContentStyles = css` + margin-inline: auto; + max-width: ${DELETE_PAGE_MAX_WIDTH}px; +`; + +export default { + title: 'Components/DeleteWizard', + component: Wizard, +}; + +export const DeleteProjectWizard = () => { + return ( +
+ + + + + + + + + , + }} + /> + + +
+ ); +}; diff --git a/packages/wizard/src/demo/DeleteWizardStepContent.types.ts b/packages/wizard/src/demo/DeleteWizardStepContent.types.ts new file mode 100644 index 0000000000..3dcd23405d --- /dev/null +++ b/packages/wizard/src/demo/DeleteWizardStepContent.types.ts @@ -0,0 +1 @@ +export interface DeleteWizardStepContentProps {} diff --git a/packages/wizard/src/demo/RecommendedActionCard.tsx b/packages/wizard/src/demo/RecommendedActionCard.tsx new file mode 100644 index 0000000000..d76a453f96 --- /dev/null +++ b/packages/wizard/src/demo/RecommendedActionCard.tsx @@ -0,0 +1,44 @@ +import React, { ReactNode } from 'react'; + +import { Badge } from '@leafygreen-ui/badge'; +import { Card } from '@leafygreen-ui/card'; +import { css } from '@leafygreen-ui/emotion'; +import { BaseFontSize, spacing } from '@leafygreen-ui/tokens'; +import { Body, Description } from '@leafygreen-ui/typography'; + +export interface RecommendedActionCardProps { + category: string; + title: string; + description: string; + link: ReactNode; +} + +export const RecommendedActionCard = ({ + category, + title, + description, + link, +}: RecommendedActionCardProps) => { + return ( + + {category} + + {title} + + + {description} + + {link} + + ); +}; diff --git a/packages/wizard/src/demo/RecommendedActions.tsx b/packages/wizard/src/demo/RecommendedActions.tsx new file mode 100644 index 0000000000..415b5f3d7e --- /dev/null +++ b/packages/wizard/src/demo/RecommendedActions.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +import { Checkbox } from '@leafygreen-ui/checkbox'; +import { css } from '@leafygreen-ui/emotion'; +import { spacing } from '@leafygreen-ui/tokens'; +import { Body, Description, H3 } from '@leafygreen-ui/typography'; + +import { useWizardStepContext } from '../'; + +import { RecommendedActionCard } from './RecommendedActionCard'; +import { recommendedActionsConfig } from './recommendedActionData'; + +export const RecommendedActions = () => { + const { isAcknowledged, setAcknowledged } = useWizardStepContext(); + return ( + <> +

Recommended Actions

+ + If you delete a project, the action is irreversible and permanently + deletes all data. We strongly recommend that you complete the following + data governance checks before you proceed. + + + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx b/packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx new file mode 100644 index 0000000000..e04f57bfb9 --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +import { + LGColumnDef, + LGTableDataType, + useLeafyGreenTable, +} from '@leafygreen-ui/table'; +import { Description, Link } from '@leafygreen-ui/typography'; + +import { BasicTable } from '../../HELPERS/BasicTable'; +import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; + +import { + InheritedRequiredActionCardProps, + RequiredActionCard, + TitleEm, +} from './RequiredActionCard'; + +interface ApplicationsTableData { + name: string; +} + +const applicationsColumns: Array> = [ + { accessorKey: 'name', header: 'Applications' }, +]; + +const demoApplicationsData: Array> = [ + { name: 'Application-1' }, + { name: 'Application-2' }, + { name: 'Application-3' }, +]; + +export const ApplicationsCard = ({ + ...props +}: InheritedRequiredActionCardProps) => { + const { isLoading, tableData } = useFetchRequiredActionTableData({ + demoData: demoApplicationsData, + }); + + const table = useLeafyGreenTable({ + data: tableData, + columns: applicationsColumns, + }); + + return ( + + Delete {tableData?.length} applications + + } + description={ + + All applications will be deleted upon project deletion. Review the + applications below or go to the{' '} + e.stopPropagation()} + > + Apps + {' '} + page. + + } + {...props} + > + + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx b/packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx new file mode 100644 index 0000000000..afe03c50da --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx @@ -0,0 +1,234 @@ +import React, { ComponentType } from 'react'; + +import { Badge, Variant as BadgeVariant } from '@leafygreen-ui/badge'; +import { Button } from '@leafygreen-ui/button'; +import { css } from '@leafygreen-ui/emotion'; +import ReplicaSet from '@leafygreen-ui/icon/ReplicaSet'; +import ShardedCluster from '@leafygreen-ui/icon/ShardedCluster'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { TableSkeleton } from '@leafygreen-ui/skeleton-loader'; +import { + LGColumnDef, + LGTableDataType, + useLeafyGreenTable, +} from '@leafygreen-ui/table'; +import { color, spacing } from '@leafygreen-ui/tokens'; +import { Body, Description, Link } from '@leafygreen-ui/typography'; + +import { BasicTable } from '../../HELPERS/BasicTable'; +import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; + +import { + InheritedRequiredActionCardProps, + RequiredActionCard, + TitleEm, +} from './RequiredActionCard'; + +// TODO: This is likely defined somewhere in MMS +type ClusterTier = 'Free' | 'Flex' | 'Dedicated'; +type ClusterType = 'Shard' | 'ReplicaSet'; + +const TierToVariantMap: Record = { + Free: BadgeVariant.LightGray, + Flex: BadgeVariant.Yellow, + Dedicated: BadgeVariant.Blue, +}; + +const TypeToIconMap: Record = { + Shard: ShardedCluster, + ReplicaSet: ReplicaSet, +}; + +interface ClusterTableData { + id: string; + name: string; + tier: ClusterTier; + clusterType: ClusterType; + version: string; + region: string; + backup: boolean; +} + +const clustersColumns: Array> = [ + { + accessorKey: 'name', + header: 'Cluster Name', + minSize: 384, + cell: info => { + const clusterType = info.row.original.clusterType; + const TypeIcon = TypeToIconMap[clusterType]; + const name = info.getValue() as string; + return ( +
+ + <>{name} +
+ ); + }, + }, + { + accessorKey: 'tier', + header: 'Tier', + maxSize: 96, + cell: info => { + const val: ClusterTier = info.getValue() as ClusterTier; + return {val}; + }, + }, + { accessorKey: 'version', header: 'Version', maxSize: 64 }, + { accessorKey: 'region', header: 'Region', minSize: 128 }, + { + accessorKey: 'backup', + header: 'Backup', + maxSize: 64, + cell: info => { + const backupEnabled: boolean = info.getValue() as boolean; + // eslint-disable-next-line react-hooks/rules-of-hooks + const { theme } = useDarkMode(); + return backupEnabled ? ( + + ON + + ) : ( + OFF + ); + }, + }, + { + id: 'downloadBackup', + accessorKey: 'backup', + header: () => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { theme } = useDarkMode(); + return ( + e.stopPropagation()} + className={css` + color: ${color[theme].text.primary.default}; + font-weight: 600; + + &, + &:hover { + // Emulating a LG Inline definition + text-decoration: underline dotted 2px; + text-underline-offset: 0.125em; + } + + & > svg { + color: ${color[theme].text.link.default}; + } + `} + > + Download Backup + + ); + }, + maxSize: 96, + cell: info => { + const backupEnabled: boolean = info.getValue() as boolean; + // TODO: What does this download button do? + return ( + + ); + }, + }, +]; + +const demoClustersData: Array> = [ + { + id: 'abc123', + name: 'Cluster1', + clusterType: 'Shard', + tier: 'Dedicated', + version: '8.0.12', + region: 'AWS / N. Virginia (us-east-1)', + backup: true, + }, + { + id: 'xyz789', + name: 'PUXFest2025', + clusterType: 'ReplicaSet', + tier: 'Free', + version: '8.0.12', + region: 'GCP / Iowa (us-central1)', + backup: false, + }, + { + id: '456lmnop', + name: 'PUX Design Strategy', + clusterType: 'ReplicaSet', + tier: 'Flex', + version: '7.0.22', + region: 'AWS / Oregon (us-west-2)', + backup: false, + }, +]; + +export const ClustersCard = ({ + ...props +}: InheritedRequiredActionCardProps) => { + const { isLoading, tableData } = useFetchRequiredActionTableData({ + demoData: demoClustersData, + }); + + const table = useLeafyGreenTable({ + data: tableData, + columns: clustersColumns, + }); + + return ( + + Terminate {tableData?.length} clusters + + } + description={ + + All clusters will be terminated upon project deletion. Review the + clusters below or go to the{' '} + e.stopPropagation()} + > + Clusters + {' '} + page. + + } + {...props} + > + {isLoading ? ( + ( + <> + {typeof col.header === 'function' + ? col.header({} as any) + : col.header ?? ''} + + ))} + /> + ) : ( + + )} + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx b/packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx new file mode 100644 index 0000000000..cbfa5f24bd --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { format } from 'date-fns'; + +import { LGColumnDef, useLeafyGreenTable } from '@leafygreen-ui/table'; +import { Description, Link } from '@leafygreen-ui/typography'; + +import { BasicTable } from '../../HELPERS/BasicTable'; +import { TableHeaderWithSubtitle } from '../../HELPERS/TableHeaderWithSubtitle'; +import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; + +import { + InheritedRequiredActionCardProps, + RequiredActionCard, + TitleEm, +} from './RequiredActionCard'; + +type FederatedDBType = 'Atlas SQL' | 'Online Archive' | 'Federated Instance'; + +interface FederatedDBTableData { + name: string; + type: FederatedDBType; + region: string; + queriesExecuted: number; + dataProcessed: string; + dataReturned: string; + dateCreated: Date; +} + +const federatedDbColumns: Array> = [ + { accessorKey: 'name', header: 'Federated database instances' }, + { accessorKey: 'type', header: 'Type', minSize: 96 }, + { accessorKey: 'region', header: 'Cloud provider & region', minSize: 196 }, + { + accessorKey: 'queriesExecuted', + header: () => ( + + ), + }, + { + accessorKey: 'dataProcessed', + header: () => ( + + ), + }, + { + accessorKey: 'dataReturned', + header: () => ( + + ), + }, + { + accessorKey: 'dateCreated', + header: 'Date created', + cell: info => { + // TODO: Use consistent, localized Time-in-Atlas format + return format(info.getValue() as Date, 'yyyy-MM-dd HH:MM'); + }, + }, +]; + +const demoFederatedDbData: Array = [ + { + name: 'FederatedDatabaseInstance0', + type: 'Atlas SQL', + region: 'AWS / N. Virginia (us-east-1)', + queriesExecuted: 5, + dataProcessed: '48 KB', + dataReturned: '64 KB', + dateCreated: new Date(), + }, + { + name: 'FederatedDatabaseInstance1', + type: 'Online Archive', + region: 'AWS / N. Virginia (us-east-1)', + queriesExecuted: 28, + dataProcessed: '48 KB', + dataReturned: '64 KB', + dateCreated: new Date(), + }, + { + name: 'FederatedDatabaseInstance2', + type: 'Federated Instance', + region: 'AWS / N. Virginia (us-east-1)', + queriesExecuted: 1, + dataProcessed: '48 KB', + dataReturned: '64 KB', + dateCreated: new Date(), + }, + { + name: 'FederatedDatabaseInstance3', + type: 'Federated Instance', + region: 'AWS / N. Virginia (us-east-1)', + queriesExecuted: 7, + dataProcessed: '48 KB', + dataReturned: '64 KB', + dateCreated: new Date(), + }, +]; + +export const FederatedDbCard = ({ + ...props +}: InheritedRequiredActionCardProps) => { + const { isLoading, tableData } = useFetchRequiredActionTableData({ + demoData: demoFederatedDbData, + }); + + const table = useLeafyGreenTable({ + data: tableData, + columns: federatedDbColumns, + }); + + return ( + + Terminate {tableData?.length} federated database + instances + + } + description={ + + All federated database instances will be terminated upon project + deletion. Review the federated database instances below or go to the{' '} + e.stopPropagation()} + > + Data Federation + {' '} + page. + + } + {...props} + > + + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx b/packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx new file mode 100644 index 0000000000..447642b34c --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { format } from 'date-fns'; + +import { + CellContext, + LGColumnDef, + LGTableDataType, + useLeafyGreenTable, +} from '@leafygreen-ui/table'; +import { Description } from '@leafygreen-ui/typography'; + +import { BasicTable } from '../../HELPERS/BasicTable'; +import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; + +import { + InheritedRequiredActionCardProps, + RequiredActionCard, + TitleEm, +} from './RequiredActionCard'; + +const DateCell = ( + cellData: CellContext, unknown>, +) => { + const val = cellData.getValue() as Date | undefined; + + if (!val) return <>Never; + + return <>{format(val, 'yyyy-MM-dd')}; +}; + +interface ModelAPIKeyTableData { + keyName: string; + createdDate: Date; + lastUsedDate?: Date; + createdBy: string; +} + +const modelApiKeysColumns: Array> = [ + { accessorKey: 'keyName', header: 'Key Name' }, + { accessorKey: 'key', header: 'Secret key', cell: () => <>******** }, + { + accessorKey: 'createdDate', + header: 'Created on (UTC)', + cell: DateCell, + }, + { + accessorKey: 'lastUsedDate', + header: 'Last used (UTC)', + cell: DateCell, + }, + { accessorKey: 'createdBy', header: 'Created by' }, +]; + +const demoModelApiKeysData: Array> = [ + { + keyName: 'voyage-api-key-1', + createdDate: new Date('2019-04-22'), + lastUsedDate: new Date(), + createdBy: 'Mike Waltzer', + }, + { + keyName: 'voyage-api-key-2', + createdDate: new Date('2022-08-29'), + lastUsedDate: new Date(), + createdBy: 'Lauren Fox', + }, + { + keyName: 'voyage-api-key-3', + createdDate: new Date('2021-06-07'), + createdBy: 'Adam Thompson', + }, +]; + +export const ModelApiKeysCard = ({ + ...props +}: InheritedRequiredActionCardProps) => { + const { isLoading, tableData } = useFetchRequiredActionTableData({ + demoData: demoModelApiKeysData, + }); + + const table = useLeafyGreenTable({ + data: tableData, + columns: modelApiKeysColumns, + }); + + return ( + + Delete {tableData?.length} Model API keys + + } + description={ + + All Model API keys will be deleted upon project deletion. + + } + {...props} + > + + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx b/packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx new file mode 100644 index 0000000000..f8003183b8 --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx @@ -0,0 +1,348 @@ +import React, { useState } from 'react'; +import upperFirst from 'lodash/upperFirst'; + +import { css } from '@leafygreen-ui/emotion'; +import Circle from '@leafygreen-ui/icon/Circle'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { Theme } from '@leafygreen-ui/lib'; +import { + SegmentedControl, + SegmentedControlOption, +} from '@leafygreen-ui/segmented-control'; +import { + CellContext, + LGColumnDef, + LGTableDataType, + useLeafyGreenTable, +} from '@leafygreen-ui/table'; +import { color, spacing } from '@leafygreen-ui/tokens'; +import { Description, Link } from '@leafygreen-ui/typography'; + +import { BasicTable } from '../../HELPERS/BasicTable'; +import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; + +import { + InheritedRequiredActionCardProps, + RequiredActionCard, + TitleEm, +} from './RequiredActionCard'; + +const ServiceStatus = { + Available: 'available', +} as const; +type ServiceStatus = (typeof ServiceStatus)[keyof typeof ServiceStatus]; + +const ConfigurationStatus = { + Configured: 'configured', +} as const; +type ConfigurationStatus = + (typeof ConfigurationStatus)[keyof typeof ConfigurationStatus]; + +const statusToColorMap: Record< + ServiceStatus | ConfigurationStatus, + Record +> = { + [ServiceStatus.Available]: { + [Theme.Light]: color.light.icon.success.default, + [Theme.Dark]: color.dark.icon.success.default, + }, + [ConfigurationStatus.Configured]: { + [Theme.Light]: color.light.icon.success.default, + [Theme.Dark]: color.dark.icon.success.default, + }, +}; + +const StatusCell = < + D extends + | DedicatedClusterPrivateEndpointTableData + | FederatedDBInstancePrivateEndpointTableData + | StreamProcessingPrivateEndpointTableData, +>( + cellData: CellContext, unknown>, +) => { + const { theme } = useDarkMode(); + const value = cellData.getValue() as ServiceStatus | ConfigurationStatus; + const dotColor = statusToColorMap[value][theme]; + return ( + <> + + {upperFirst(value)} + + ); +}; + +const PrivateEndpointType = { + Dedicated: 'dedicated', + Federated: 'federated', + StreamProcessing: 'stream-processing', +} as const; +type PrivateEndpointType = + (typeof PrivateEndpointType)[keyof typeof PrivateEndpointType]; + +interface DedicatedClusterPrivateEndpointTableData { + id: string; + provider: string; + region: string; + endpointService: string; + endpointStatus: ServiceStatus; + configurationStatus: ConfigurationStatus; +} + +const dedicatedClusterPrivateEndpointsColumns: Array< + LGColumnDef +> = [ + { accessorKey: 'provider', header: 'Cloud Provider', maxSize: 96 }, + { accessorKey: 'region', header: 'Region', maxSize: 96 }, + { accessorKey: 'endpointService', header: 'Atlas Endpoint Service' }, + { + accessorKey: 'endpointStatus', + header: 'Atlas Endpoint Service Status', + cell: StatusCell, + }, + { accessorKey: 'id', header: 'Endpoint' }, + { + accessorKey: 'configurationStatus', + header: 'Endpoint Status', + maxSize: 112, + cell: StatusCell, + }, +]; + +const demoDedicatedClusterPrivateEndpointsData: Array< + LGTableDataType +> = [ + { + id: 'endpoint-1', + provider: 'AWS', + region: 'us-east-1', + endpointService: 'com.amazonaws.vpce.us-east-1.vpce-svc-054161d3958725abb', + endpointStatus: 'available', + configurationStatus: 'configured', + }, + { + id: 'endpoint-2', + provider: 'GCP', + region: 'us-east-1', + endpointService: 'com.amazonaws.vpce.us-east-1.vpce-svc-054161d3958725abb', + endpointStatus: 'available', + configurationStatus: 'configured', + }, + { + id: 'endpoint-3', + provider: 'AWS', + region: 'us-central1', + endpointService: 'com.amazonaws.vpce.us-east-1.vpce-svc-054161d3958725abb', + endpointStatus: 'available', + configurationStatus: 'configured', + }, +]; + +interface FederatedDBInstancePrivateEndpointTableData { + id: string; + provider: string; + endpointStatus: ServiceStatus; + region: string; + comment?: string; +} + +const federatedDBInstancePrivateEndpointsColumns: Array< + LGColumnDef +> = [ + { accessorKey: 'provider', header: 'Cloud Provider', maxSize: 96 }, + { accessorKey: 'region', header: 'Region', maxSize: 96 }, + { + accessorKey: 'endpointStatus', + header: 'Endpoint Status', + maxSize: 96, + cell: StatusCell, + }, + { accessorKey: 'id', header: 'Endpoint' }, + { + accessorKey: 'comment', + header: 'Comment', + cell: info => { + const val = info.getValue() as string | undefined; + + if (!val || val.length === 0) { + return '—'; + } + + return val; + }, + }, +]; + +const demoFederatedDBInstancePrivateEndpointsData: Array< + LGTableDataType +> = [ + { + id: 'vpce-0b9c5701325cb07e6', + provider: 'AWS', + endpointStatus: 'available', + region: 'us-east-1', + comment: 'Production endpoint', + }, + { + id: 'vpce-1a2b3c4d5e6f7g8h9', + provider: 'AWS', + endpointStatus: 'available', + region: 'us-west-2', + }, +]; + +interface StreamProcessingPrivateEndpointTableData { + provider: string; + vendor: string; + endpoint: string; + endpointStatus: ServiceStatus; + accountId: string; +} + +const streamProcessingPrivateEndpointsColumns: Array< + LGColumnDef +> = [ + { accessorKey: 'provider', header: 'Cloud Provider', maxSize: 96 }, + { accessorKey: 'vendor', header: 'Vendor', maxSize: 96 }, + { accessorKey: 'endpoint', header: 'Endpoint' }, + { + accessorKey: 'endpointStatus', + header: 'Endpoint Status', + maxSize: 112, + cell: StatusCell, + }, + { accessorKey: 'accountId', header: 'Account ID' }, +]; + +const demoStreamProcessingPrivateEndpointsData: Array< + LGTableDataType +> = [ + { + provider: 'AWS', + vendor: 'S3', + endpoint: 'vpce-0a1b2c3d4e5f6g7h8', + endpointStatus: 'available', + accountId: '123456789012', + }, + { + provider: 'AWS', + vendor: 'Confluent', + endpoint: 'vpce-9h8g7f6e5d4c3b2a1', + endpointStatus: 'available', + accountId: '123456789012', + }, +]; + +export const PrivateEndpointsCard = ({ + ...props +}: InheritedRequiredActionCardProps) => { + const [segCtrlValue, setSegCtrlValue] = useState( + PrivateEndpointType.Dedicated, + ); + + const { + isLoading: isDedicatedClustersLoading, + tableData: dedicatedClusterTableData, + } = useFetchRequiredActionTableData({ + demoData: demoDedicatedClusterPrivateEndpointsData, + }); + + const { isLoading: isFederatedDBLoading, tableData: federatedDBTableData } = + useFetchRequiredActionTableData({ + demoData: demoFederatedDBInstancePrivateEndpointsData, + }); + + const { + isLoading: isStreamProcessingLoading, + tableData: streamProcessingTableData, + } = useFetchRequiredActionTableData({ + demoData: demoStreamProcessingPrivateEndpointsData, + }); + + const dedicatedClusterTable = useLeafyGreenTable({ + data: dedicatedClusterTableData, + columns: dedicatedClusterPrivateEndpointsColumns, + }); + + const federatedDBTable = useLeafyGreenTable({ + data: federatedDBTableData, + columns: federatedDBInstancePrivateEndpointsColumns, + }); + + const streamProcessingTable = useLeafyGreenTable({ + data: streamProcessingTableData, + columns: streamProcessingPrivateEndpointsColumns, + }); + + const isLoading = + isDedicatedClustersLoading || + isFederatedDBLoading || + isStreamProcessingLoading; + + const totalEndpoints = + dedicatedClusterTableData?.length + + federatedDBTableData?.length + + streamProcessingTableData?.length; + + return ( + + Remove {totalEndpoints} private endpoint + connections + + } + description={ + + All private endpoint connections will be removed upon project + deletion. Review the private endpoint connections below or go to the{' '} + + Network Access + + + } + {...props} + > + {isLoading ? null : ( + <> + setSegCtrlValue(val as PrivateEndpointType)} + className={css` + margin: ${spacing[200]}px ${spacing[600]}px; + `} + > + + Dedicated Cluster + + + Federated database Instance / Online Archive + + + Atlas stream processing + + + + {segCtrlValue === PrivateEndpointType.Dedicated && ( + + )} + {segCtrlValue === PrivateEndpointType.Federated && ( + + )} + {segCtrlValue === PrivateEndpointType.StreamProcessing && ( + + )} + + )} + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx b/packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx new file mode 100644 index 0000000000..ca7e30731a --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx @@ -0,0 +1,97 @@ +import React, { PropsWithChildren, ReactNode } from 'react'; + +import { css } from '@leafygreen-ui/emotion'; +import { + ExpandableCard, + ExpandableCardProps, +} from '@leafygreen-ui/expandable-card'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { + Size as SpinnerSize, + Spinner, +} from '@leafygreen-ui/loading-indicator/spinner'; +import { Skeleton } from '@leafygreen-ui/skeleton-loader'; +import { color, spacing } from '@leafygreen-ui/tokens'; + +const requiredActionCardStyles = css` + & h6 { + display: block; + } +`; + +const expandableCardContentStyles = css` + padding: unset; +`; + +const cardContentWrapperStyles = css` + padding-bottom: ${spacing[400]}px; + overflow: hidden; +`; + +export interface RequiredActionCardProps extends ExpandableCardProps { + isLoading?: boolean; + loadingTitle?: ReactNode; + loadingDescription?: ReactNode; +} + +export interface InheritedRequiredActionCardProps + extends Omit {} + +export const TitleEm = ({ children }: PropsWithChildren<{}>) => { + const { theme } = useDarkMode(); + return ( + + {children} + + ); +}; + +export const RequiredActionCard = ({ + title, + description, + isLoading, + children, + loadingTitle = ( + + ), + loadingDescription = 'This may take a few moments', + ...rest +}: RequiredActionCardProps) => { + return ( + + + {loadingDescription} + + ) : ( + description + ) + } + className={requiredActionCardStyles} + contentClassName={expandableCardContentStyles} + defaultOpen={false} + {...rest} + > +
{children}
+
+ ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx b/packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx new file mode 100644 index 0000000000..57698b5004 --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx @@ -0,0 +1,110 @@ +import React from 'react'; + +import { LGTableDataType, useLeafyGreenTable } from '@leafygreen-ui/table'; +import { Description, Link } from '@leafygreen-ui/typography'; + +import { BasicTable } from '../../HELPERS/BasicTable'; +import { TableHeaderWithSubtitle } from '../../HELPERS/TableHeaderWithSubtitle'; +import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; + +import { + InheritedRequiredActionCardProps, + RequiredActionCard, + TitleEm, +} from './RequiredActionCard'; + +interface StreamProcessingTableData { + name: string; + region: string; + started: number; + stopped: number; + failed: number; +} + +const streamProcessingColumns = [ + { accessorKey: 'name', header: 'Workspace name' }, + { accessorKey: 'region', header: 'Region', maxSize: 180 }, + { + accessorKey: 'started', + maxSize: 96, + header: () => ( + + ), + }, + { + accessorKey: 'stopped', + maxSize: 96, + header: () => ( + + ), + }, + { + accessorKey: 'failed', + maxSize: 96, + header: () => ( + + ), + }, +]; + +const demoStreamProcessingData: Array< + LGTableDataType +> = [ + { + name: 'Workspace-1', + region: 'us-east-1', + started: 1, + stopped: 2, + failed: 0, + }, + { + name: 'Workspace-2', + region: 'us-west-2', + started: 1, + stopped: 2, + failed: 0, + }, +]; + +export const StreamProcessingCard = ({ + ...props +}: InheritedRequiredActionCardProps) => { + const { isLoading, tableData } = useFetchRequiredActionTableData({ + demoData: demoStreamProcessingData, + }); + + const table = useLeafyGreenTable({ + data: tableData, + columns: streamProcessingColumns, + }); + + return ( + + Terminate {tableData?.length} stream processing + workspaces + + } + description={ + + All Stream Processing workspaces will be terminated upon project + deletion. Review the workspaces below or go to the{' '} + e.stopPropagation()} + > + Stream Processing + {' '} + page. + + } + {...props} + > + + + ); +}; diff --git a/packages/wizard/src/demo/RequiredActionCards/index.ts b/packages/wizard/src/demo/RequiredActionCards/index.ts new file mode 100644 index 0000000000..70d2b2bd5d --- /dev/null +++ b/packages/wizard/src/demo/RequiredActionCards/index.ts @@ -0,0 +1,6 @@ +export { ApplicationsCard } from './ApplicationsCard'; +export { ClustersCard } from './ClustersCard'; +export { FederatedDbCard } from './FederatedDbCard'; +export { ModelApiKeysCard } from './ModelApiKeysCard'; +export { PrivateEndpointsCard } from './PrivateEndpointsCard'; +export { StreamProcessingCard } from './StreamProcessingCard'; diff --git a/packages/wizard/src/demo/RequiredActions.tsx b/packages/wizard/src/demo/RequiredActions.tsx new file mode 100644 index 0000000000..10262be135 --- /dev/null +++ b/packages/wizard/src/demo/RequiredActions.tsx @@ -0,0 +1,96 @@ +import React from 'react'; + +import { Checkbox } from '@leafygreen-ui/checkbox'; +import { css } from '@leafygreen-ui/emotion'; +import { spacing } from '@leafygreen-ui/tokens'; +import { Body, Description, H3, Link } from '@leafygreen-ui/typography'; + +import { useRequiredActionAcknowledgements } from './hooks/useRequiredActionAcknowledgements'; +import { + ApplicationsCard, + ClustersCard, + FederatedDbCard, + ModelApiKeysCard, + PrivateEndpointsCard, + StreamProcessingCard, +} from './RequiredActionCards'; + +const reviewCardsContainerStyles = css` + display: flex; + flex-direction: column; + gap: ${spacing[3]}px; + margin-block: ${spacing[4]}px; +`; + +const checkboxContainerStyles = css` + display: flex; + flex-direction: column; + gap: ${spacing[3]}px; + margin-top: ${spacing[3]}px; +`; + +const acknowledgmentLabelStyles = css` + margin-bottom: ${spacing[3]}px; +`; + +const sectionDividerStyles = css` + border-top: 1px solid #e8edeb; + margin-block: ${spacing[5]}px; +`; + +export const RequiredActions = () => { + const { acknowledgementsState, setAcknowledgementState } = + useRequiredActionAcknowledgements(); + + return ( + <> +

Required Actions

+ + Access to all of the following deployments and associated data will be + permanently lost and cannot be recovered.{' '} + Manage Project Access Docs + + +
+ + + + + + +
+ +
+ + + By deleting the project, you acknowledge this irreversible action: + + +
+ setAcknowledgementState(0, e.target.checked)} + /> + + setAcknowledgementState(1, e.target.checked)} + /> + + setAcknowledgementState(2, e.target.checked)} + /> +
+ + ); +}; diff --git a/packages/wizard/src/demo/constants.ts b/packages/wizard/src/demo/constants.ts new file mode 100644 index 0000000000..ce523186ac --- /dev/null +++ b/packages/wizard/src/demo/constants.ts @@ -0,0 +1,3 @@ +import { breakpoints } from '@leafygreen-ui/tokens'; + +export const DELETE_PAGE_MAX_WIDTH = breakpoints.XLDesktop; diff --git a/packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts b/packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts new file mode 100644 index 0000000000..6627809b2f --- /dev/null +++ b/packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts @@ -0,0 +1,41 @@ +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; + +import { LGRowData, LGTableDataType } from '@leafygreen-ui/table'; + +interface FetchRequiredActionTableDataArgs { + demoData: Array>; + _demoDelay?: number; +} + +interface FetchRequiredActionTableDataReturnType { + tableData: Array>; + setTableData: Dispatch>>>; + isLoading: boolean; +} + +export const useFetchRequiredActionTableData = ({ + demoData, + _demoDelay, +}: FetchRequiredActionTableDataArgs): FetchRequiredActionTableDataReturnType => { + const [isLoading, setLoading] = useState(false); + const [tableData, setTableData] = useState>>([]); + + useEffect(() => { + if (_demoDelay === 0) { + setTableData(demoData); + return; + } + + setLoading(true); + setTimeout(() => { + setLoading(false); + setTableData(demoData); + }, _demoDelay ?? 500 + 5000 * Math.random()); + }, [_demoDelay, demoData]); + + return { + isLoading, + tableData, + setTableData, + }; +}; diff --git a/packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts b/packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts new file mode 100644 index 0000000000..b99d952ab8 --- /dev/null +++ b/packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts @@ -0,0 +1,44 @@ +import { Reducer, useReducer } from 'react'; + +import { useWizardStepContext } from '../../WizardStep'; + +export type RequiredActionsAcknowledgementsState = [boolean, boolean, boolean]; + +export interface RequiredActionsAcknowledgementsAction { + index: 0 | 1 | 2; + value: boolean; +} + +export const useRequiredActionAcknowledgements = () => { + const { isAcknowledged, setAcknowledged } = useWizardStepContext(); + + const [acknowledgementsState, dispatch] = useReducer< + Reducer< + RequiredActionsAcknowledgementsState, + RequiredActionsAcknowledgementsAction + > + >( + (state, action) => { + const newState: RequiredActionsAcknowledgementsState = [...state]; + newState[action.index] = action.value; + + if (newState.every(Boolean)) { + setAcknowledged(true); + } else { + setAcknowledged(false); + } + + return newState; + }, + [false, false, false], + ); + + const setAcknowledgementState = (index: 0 | 1 | 2, value: boolean) => + dispatch({ index, value }); + + return { + acknowledgementsState, + setAcknowledgementState, + isAcknowledged, + }; +}; diff --git a/packages/wizard/src/demo/recommendedActionData.tsx b/packages/wizard/src/demo/recommendedActionData.tsx new file mode 100644 index 0000000000..971f25911c --- /dev/null +++ b/packages/wizard/src/demo/recommendedActionData.tsx @@ -0,0 +1,67 @@ +import React from 'react'; + +import { Link } from '@leafygreen-ui/typography'; + +import { RecommendedActionCardProps } from './RecommendedActionCard'; + +export const recommendedActionsConfig: Array = [ + { + category: 'Data Retention', + title: 'Export most recent Backup Snapshot', + description: + 'To safeguard critical information, provide a recovery option to ensure you have access to your data after you delete your project.', + link: ( + + Learn how to export backup snapshots + + ), + }, + + { + category: 'Compliance', + title: 'Export audit logs', + description: + 'Retain a record of essential data regarding changes and actions within your project for compliance, audits, and future reference.', + link: ( + + Learn how to export audit logs + + ), + }, + { + category: 'Auditing', + title: 'Export Project Activity Feed events', + description: + 'Ensure you have access to key details about project changes, actions, and events for audits, compliance, or future reference.', + link: ( + + {' '} + Learn how to return events + + ), + }, + + { + category: 'Security', + title: 'Disconnect third-party integrations', + description: + 'Remove API keys to secure of your data, prevent unauthorized access, and avoid any unintended interactions with external systems.', + link: ( + + Go to integrations + + ), + }, + + { + category: 'Visualizations', + title: 'Export Charts and Dashboards', + description: + 'Retain critical insights and visualizations from dashboards, data sources, and charts associated with your project.', + link: ( + + Export charts dashboards + + ), + }, +]; diff --git a/packages/wizard/src/index.ts b/packages/wizard/src/index.ts index ad4bdf22da..1c370edbab 100644 --- a/packages/wizard/src/index.ts +++ b/packages/wizard/src/index.ts @@ -6,4 +6,4 @@ export { WizardProvider, } from './WizardContext'; export { type WizardFooterProps } from './WizardFooter'; -export { type WizardStepProps } from './WizardStep'; +export { useWizardStepContext, type WizardStepProps } from './WizardStep'; diff --git a/packages/wizard/tsconfig.json b/packages/wizard/tsconfig.json index 26dc97f771..693201d7c6 100644 --- a/packages/wizard/tsconfig.json +++ b/packages/wizard/tsconfig.json @@ -13,20 +13,47 @@ "path": "../button" }, { - "path": "../compound-component" + "path": "../badge" }, { - "path": "../button" + "path": "../card" + }, + { + "path": "../checkbox" + }, + { + "path": "../compound-component" }, { "path": "../emotion" }, + { + "path": "../expandable-card" + }, { "path": "../form-footer" }, + { + "path": "../icon" + }, { "path": "../lib" }, + { + "path": "../loading-indicator" + }, + { + "path": "../segmented-control" + }, + { + "path": "../skeleton-loader" + }, + { + "path": "../table" + }, + { + "path": "../typography" + }, { "path": "../../tools/test-harnesses" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc3b47cf80..58addc65bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1738,8 +1738,8 @@ importers: specifier: workspace:^ version: link:../../tools/build '@storybook/react': - specifier: 8.6.14 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3) + specifier: latest + version: 10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3) packages/drawer: dependencies: @@ -3597,7 +3597,7 @@ importers: specifier: workspace:^ version: link:../../tools/build '@storybook/types': - specifier: ^8.5.3 + specifier: latest version: 8.6.14(storybook@8.6.14(prettier@2.8.8)) react-test-renderer: specifier: ^18.2.0 @@ -3815,9 +3815,6 @@ importers: '@leafygreen-ui/compound-component': specifier: workspace:^ version: link:../compound-component - '@leafygreen-ui/descendants': - specifier: workspace:^ - version: link:../descendants '@leafygreen-ui/emotion': specifier: workspace:^ version: link:../emotion @@ -3842,13 +3839,37 @@ importers: '@lg-tools/test-harnesses': specifier: workspace:^ version: link:../../tools/test-harnesses + date-fns: + specifier: ^2.30.0 + version: 2.30.0 devDependencies: '@faker-js/faker': specifier: ^8.0.0 version: 8.0.2 + '@leafygreen-ui/badge': + specifier: workspace:^ + version: link:../badge + '@leafygreen-ui/card': + specifier: workspace:^ + version: link:../card + '@leafygreen-ui/checkbox': + specifier: workspace:^ + version: link:../checkbox + '@leafygreen-ui/expandable-card': + specifier: workspace:^ + version: link:../expandable-card '@leafygreen-ui/icon': specifier: workspace:^ version: link:../icon + '@leafygreen-ui/loading-indicator': + specifier: workspace:^ + version: link:../loading-indicator + '@leafygreen-ui/segmented-control': + specifier: workspace:^ + version: link:../segmented-control + '@leafygreen-ui/skeleton-loader': + specifier: workspace:^ + version: link:../skeleton-loader tools/build: dependencies: @@ -6537,6 +6558,13 @@ packages: typescript: ~5.8.0 webpack: '>= 4' + '@storybook/react-dom-shim@10.0.5': + resolution: {integrity: sha512-bC6pSrQWRHUVmDDxH5uYq/tf2oqIEyzGIC4+3mZl67ngOKwOgjaBatKLnHBDeWrn25pA11mymdFt1VOStbNzGQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.0.5 + '@storybook/react-dom-shim@8.6.14': resolution: {integrity: sha512-0hixr3dOy3f3M+HBofp3jtMQMS+sqzjKNgl7Arfuj3fvjmyXOks/yGjDImySR4imPtEllvPZfhiQNlejheaInw==} peerDependencies: @@ -6556,6 +6584,17 @@ packages: typescript: optional: true + '@storybook/react@10.0.5': + resolution: {integrity: sha512-FDajPP8HG4n1w3nql7lxQUNKg5+wEa+qJzAPNk583FRaeH6GuxSHlG1vUARQ1QnAZwwlrZzcmgjVNxIucms/5g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.0.5 + typescript: ~5.8.0 + peerDependenciesMeta: + typescript: + optional: true + '@storybook/react@8.6.14': resolution: {integrity: sha512-BOepx5bBFwl/CPI+F+LnmMmsG1wQYmrX/UQXgUbHQUU9Tj7E2ndTnNbpIuSLc8IrM03ru+DfwSg1Co3cxWtT+g==} engines: {node: '>=18.0.0'} @@ -14926,6 +14965,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@storybook/react-dom-shim@10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + storybook: 8.6.14(prettier@2.8.8) + '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))': dependencies: react: 18.3.1 @@ -14951,6 +14996,16 @@ snapshots: - uglify-js - webpack-cli + '@storybook/react@10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + storybook: 8.6.14(prettier@2.8.8) + optionalDependencies: + typescript: 5.8.3 + '@storybook/react@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3)': dependencies: '@storybook/components': 8.6.14(storybook@8.6.14(prettier@2.8.8)) From 7efcfc585dd9ec918ba5475e87b8d63f2791d342 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Fri, 7 Nov 2025 16:51:56 -0500 Subject: [PATCH 05/51] Update pnpm Update package.json --- packages/descendants/package.json | 2 +- pnpm-lock.yaml | 38 ++----------------------------- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/packages/descendants/package.json b/packages/descendants/package.json index 2f6ce5d968..b360383fac 100644 --- a/packages/descendants/package.json +++ b/packages/descendants/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@faker-js/faker": "^8.0.0", - "@storybook/react": "latest", + "@storybook/react": "8.6.14", "@leafygreen-ui/button": "workspace:^", "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/popover": "workspace:^", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58addc65bd..3584925a18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1738,8 +1738,8 @@ importers: specifier: workspace:^ version: link:../../tools/build '@storybook/react': - specifier: latest - version: 10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3) + specifier: 8.6.14 + version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3) packages/drawer: dependencies: @@ -6558,13 +6558,6 @@ packages: typescript: ~5.8.0 webpack: '>= 4' - '@storybook/react-dom-shim@10.0.5': - resolution: {integrity: sha512-bC6pSrQWRHUVmDDxH5uYq/tf2oqIEyzGIC4+3mZl67ngOKwOgjaBatKLnHBDeWrn25pA11mymdFt1VOStbNzGQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.0.5 - '@storybook/react-dom-shim@8.6.14': resolution: {integrity: sha512-0hixr3dOy3f3M+HBofp3jtMQMS+sqzjKNgl7Arfuj3fvjmyXOks/yGjDImySR4imPtEllvPZfhiQNlejheaInw==} peerDependencies: @@ -6584,17 +6577,6 @@ packages: typescript: optional: true - '@storybook/react@10.0.5': - resolution: {integrity: sha512-FDajPP8HG4n1w3nql7lxQUNKg5+wEa+qJzAPNk583FRaeH6GuxSHlG1vUARQ1QnAZwwlrZzcmgjVNxIucms/5g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.0.5 - typescript: ~5.8.0 - peerDependenciesMeta: - typescript: - optional: true - '@storybook/react@8.6.14': resolution: {integrity: sha512-BOepx5bBFwl/CPI+F+LnmMmsG1wQYmrX/UQXgUbHQUU9Tj7E2ndTnNbpIuSLc8IrM03ru+DfwSg1Co3cxWtT+g==} engines: {node: '>=18.0.0'} @@ -14965,12 +14947,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/react-dom-shim@10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))': - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@2.8.8) - '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))': dependencies: react: 18.3.1 @@ -14996,16 +14972,6 @@ snapshots: - uglify-js - webpack-cli - '@storybook/react@10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3)': - dependencies: - '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8)) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@2.8.8) - optionalDependencies: - typescript: 5.8.3 - '@storybook/react@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@2.8.8))(typescript@5.8.3)': dependencies: '@storybook/components': 8.6.14(storybook@8.6.14(prettier@2.8.8)) From 2b2ad963bc4cc5d9d3ea6c631c003e040377f3f5 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Fri, 14 Nov 2025 11:54:42 -0500 Subject: [PATCH 06/51] fix wizard changes --- .changeset/wizard-patch-3.md | 5 +++++ packages/wizard/src/WizardStep/WizardStep.tsx | 10 +++------- packages/wizard/src/WizardStep/WizardStep.types.ts | 9 +-------- 3 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 .changeset/wizard-patch-3.md diff --git a/.changeset/wizard-patch-3.md b/.changeset/wizard-patch-3.md new file mode 100644 index 0000000000..4a924c356f --- /dev/null +++ b/.changeset/wizard-patch-3.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Exports WizardProvider diff --git a/packages/wizard/src/WizardStep/WizardStep.tsx b/packages/wizard/src/WizardStep/WizardStep.tsx index 25900687fe..f676c9058f 100644 --- a/packages/wizard/src/WizardStep/WizardStep.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.tsx @@ -5,19 +5,17 @@ import { filterChildren, findChild, } from '@leafygreen-ui/compound-component'; -import { cx } from '@leafygreen-ui/emotion'; import { useIdAllocator } from '@leafygreen-ui/hooks'; import { consoleOnce } from '@leafygreen-ui/lib'; import { WizardSubComponentProperties } from '../constants'; import { useWizardContext } from '../WizardContext'; -import { stepStyles } from './WizardStep.styles'; import { WizardStepProps } from './WizardStep.types'; import { WizardStepContext } from './WizardStepContext'; export const WizardStep = CompoundSubComponent( - ({ children, className, ...rest }: WizardStepProps) => { + ({ children }: WizardStepProps) => { const stepId = useIdAllocator({ prefix: 'wizard-step' }); const { isWizardContext } = useWizardContext(); const [isAcknowledged, setAcknowledged] = useState(false); @@ -46,10 +44,8 @@ export const WizardStep = CompoundSubComponent( setAcknowledged, }} > -
- {restChildren} - {footerChild} -
+ {restChildren} + {footerChild} ); }, diff --git a/packages/wizard/src/WizardStep/WizardStep.types.ts b/packages/wizard/src/WizardStep/WizardStep.types.ts index d594f78aa1..1663185e97 100644 --- a/packages/wizard/src/WizardStep/WizardStep.types.ts +++ b/packages/wizard/src/WizardStep/WizardStep.types.ts @@ -1,8 +1 @@ -import { ReactNode } from 'react'; - -export interface WizardStepProps extends React.ComponentProps<'div'> { - /** - * The content of the step - */ - children: ReactNode; -} +export interface WizardStepProps extends React.PropsWithChildren<{}> {} From b3300d4279f8debcae20d4a8a5c4848c74331c3d Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 11:31:13 -0500 Subject: [PATCH 07/51] Adds `requiresAcknowledgement` prop to Wizard.Step --- .changeset/wizard-patch-5.md | 5 +++++ .changeset/wizard-patch-6.md | 5 +++++ packages/wizard/src/Wizard/Wizard.tsx | 11 +++++++++++ packages/wizard/src/Wizard/Wizard.types.ts | 4 +++- packages/wizard/src/WizardFooter/WizardFooter.tsx | 5 +++-- packages/wizard/src/WizardStep/WizardStep.tsx | 3 ++- packages/wizard/src/WizardStep/WizardStep.types.ts | 9 ++++++++- packages/wizard/src/WizardStep/WizardStepContext.tsx | 2 ++ 8 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 .changeset/wizard-patch-5.md create mode 100644 .changeset/wizard-patch-6.md diff --git a/.changeset/wizard-patch-5.md b/.changeset/wizard-patch-5.md new file mode 100644 index 0000000000..053bd3ac44 --- /dev/null +++ b/.changeset/wizard-patch-5.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Exports `WizardSubComponentProperties` \ No newline at end of file diff --git a/.changeset/wizard-patch-6.md b/.changeset/wizard-patch-6.md new file mode 100644 index 0000000000..8b8f8e6398 --- /dev/null +++ b/.changeset/wizard-patch-6.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Adds `requiresAcknowledgement` prop to `Wizard.Step` \ No newline at end of file diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index a4f5a0fff2..8a9c25f377 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -55,7 +55,18 @@ export const Wizard = CompoundComponent( }, { displayName: 'Wizard', + /** + * A single step in the wizard. A Wizard will only render Steps as children + */ Step: WizardStep, + + /** + * The footer of a Step component. + * Render this inside of each Step with the relevant button props for that Step. + * + * Back and Primary buttons trigger onStepChange. + * Automatically renders the "Back" button for all Steps except the first + */ Footer: WizardFooter, }, ); diff --git a/packages/wizard/src/Wizard/Wizard.types.ts b/packages/wizard/src/Wizard/Wizard.types.ts index 86ac5fd724..fba53ea8ad 100644 --- a/packages/wizard/src/Wizard/Wizard.types.ts +++ b/packages/wizard/src/Wizard/Wizard.types.ts @@ -2,7 +2,9 @@ import { ReactNode } from 'react'; export interface WizardProps { /** - * The current active step index (0-based). If provided, the component operates in controlled mode. + * The current active step index (0-based). + * If provided, the component operates in controlled mode, and any interaction will not update internal state. + * Use `onStepChange` to update your external state */ activeStep?: number; diff --git a/packages/wizard/src/WizardFooter/WizardFooter.tsx b/packages/wizard/src/WizardFooter/WizardFooter.tsx index 8c5c4f7104..994502f7d7 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.tsx +++ b/packages/wizard/src/WizardFooter/WizardFooter.tsx @@ -19,7 +19,8 @@ export const WizardFooter = CompoundSubComponent( ...rest }: WizardFooterProps) => { const { isWizardContext, activeStep, updateStep } = useWizardContext(); - const { isAcknowledged } = useWizardStepContext(); + const { isAcknowledged, requiresAcknowledgement } = useWizardStepContext(); + const isPrimaryButtonDisabled = requiresAcknowledgement && !isAcknowledged; const handleBackButtonClick: MouseEventHandler = e => { updateStep(activeStep - 1); @@ -55,7 +56,7 @@ export const WizardFooter = CompoundSubComponent( cancelButtonProps={cancelButtonProps} primaryButtonProps={{ ...primaryButtonProps, - disabled: !isAcknowledged, + disabled: isPrimaryButtonDisabled, onClick: handlePrimaryButtonClick, }} /> diff --git a/packages/wizard/src/WizardStep/WizardStep.tsx b/packages/wizard/src/WizardStep/WizardStep.tsx index f676c9058f..d37f0dc7f6 100644 --- a/packages/wizard/src/WizardStep/WizardStep.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.tsx @@ -15,7 +15,7 @@ import { WizardStepProps } from './WizardStep.types'; import { WizardStepContext } from './WizardStepContext'; export const WizardStep = CompoundSubComponent( - ({ children }: WizardStepProps) => { + ({ children, requiresAcknowledgement = false }: WizardStepProps) => { const stepId = useIdAllocator({ prefix: 'wizard-step' }); const { isWizardContext } = useWizardContext(); const [isAcknowledged, setAcknowledged] = useState(false); @@ -40,6 +40,7 @@ export const WizardStep = CompoundSubComponent( {} +export interface WizardStepProps extends React.PropsWithChildren<{}> { + /** + * Defines whether some action in Step must be taken by the user before enabling the primary action button + * + * @default false + */ + requiresAcknowledgement?: boolean; +} diff --git a/packages/wizard/src/WizardStep/WizardStepContext.tsx b/packages/wizard/src/WizardStep/WizardStepContext.tsx index fec1807aed..aff32ce88d 100644 --- a/packages/wizard/src/WizardStep/WizardStepContext.tsx +++ b/packages/wizard/src/WizardStep/WizardStepContext.tsx @@ -2,12 +2,14 @@ import { createContext, Dispatch, SetStateAction, useContext } from 'react'; export interface WizardStepContextData { stepId: string; + requiresAcknowledgement: boolean; isAcknowledged: boolean; setAcknowledged: Dispatch>; } export const WizardStepContext = createContext({ stepId: '', + requiresAcknowledgement: false, isAcknowledged: false, setAcknowledged: () => {}, }); From c0baca54caa661a01f69661e606be0fdc8b33642 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 12:48:42 -0500 Subject: [PATCH 08/51] Implements `isAcknowledged` state inside provider --- .changeset/wizard-patch-7.md | 5 +++ packages/wizard/src/WizardStep/WizardStep.tsx | 17 ++++------ .../src/WizardStep/WizardStepContext.tsx | 32 ++++++++++++++++++- 3 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 .changeset/wizard-patch-7.md diff --git a/.changeset/wizard-patch-7.md b/.changeset/wizard-patch-7.md new file mode 100644 index 0000000000..57aa375db8 --- /dev/null +++ b/.changeset/wizard-patch-7.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/wizard': patch +--- + +Implements `isAcknowledged` state inside provider \ No newline at end of file diff --git a/packages/wizard/src/WizardStep/WizardStep.tsx b/packages/wizard/src/WizardStep/WizardStep.tsx index d37f0dc7f6..4be39a9c69 100644 --- a/packages/wizard/src/WizardStep/WizardStep.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { CompoundSubComponent, @@ -12,13 +12,12 @@ import { WizardSubComponentProperties } from '../constants'; import { useWizardContext } from '../WizardContext'; import { WizardStepProps } from './WizardStep.types'; -import { WizardStepContext } from './WizardStepContext'; +import { WizardStepProvider } from './WizardStepContext'; export const WizardStep = CompoundSubComponent( ({ children, requiresAcknowledgement = false }: WizardStepProps) => { const stepId = useIdAllocator({ prefix: 'wizard-step' }); const { isWizardContext } = useWizardContext(); - const [isAcknowledged, setAcknowledged] = useState(false); if (!isWizardContext) { consoleOnce.error( @@ -37,17 +36,13 @@ export const WizardStep = CompoundSubComponent( ]); return ( - {restChildren} {footerChild} - + ); }, { diff --git a/packages/wizard/src/WizardStep/WizardStepContext.tsx b/packages/wizard/src/WizardStep/WizardStepContext.tsx index aff32ce88d..b16d516074 100644 --- a/packages/wizard/src/WizardStep/WizardStepContext.tsx +++ b/packages/wizard/src/WizardStep/WizardStepContext.tsx @@ -1,4 +1,11 @@ -import { createContext, Dispatch, SetStateAction, useContext } from 'react'; +import React, { + createContext, + Dispatch, + PropsWithChildren, + SetStateAction, + useContext, + useState, +} from 'react'; export interface WizardStepContextData { stepId: string; @@ -14,4 +21,27 @@ export const WizardStepContext = createContext({ setAcknowledged: () => {}, }); +export const WizardStepProvider = ({ + stepId, + requiresAcknowledgement, + children, +}: PropsWithChildren< + Omit +>) => { + const [isAcknowledged, setAcknowledged] = useState(false); + + return ( + + {children} + + ); +}; + export const useWizardStepContext = () => useContext(WizardStepContext); From e5acf366dd7fe5dd35bb1bb53a2ec688efc711e1 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 15:20:05 -0500 Subject: [PATCH 09/51] Update Wizard.stories.tsx --- packages/wizard/src/Wizard.stories.tsx | 179 +++++++++++++++---------- 1 file changed, 106 insertions(+), 73 deletions(-) diff --git a/packages/wizard/src/Wizard.stories.tsx b/packages/wizard/src/Wizard.stories.tsx index bd0e73875a..95f8095d4d 100644 --- a/packages/wizard/src/Wizard.stories.tsx +++ b/packages/wizard/src/Wizard.stories.tsx @@ -1,16 +1,44 @@ /* eslint-disable no-console */ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { faker } from '@faker-js/faker'; import { StoryMetaType } from '@lg-tools/storybook-utils'; import { StoryObj } from '@storybook/react'; +import { Banner } from '@leafygreen-ui/banner'; import { Card } from '@leafygreen-ui/card'; +import { Checkbox } from '@leafygreen-ui/checkbox'; import { css } from '@leafygreen-ui/emotion'; +import { isDefined } from '@leafygreen-ui/lib'; +import { Description, H3, InlineCode } from '@leafygreen-ui/typography'; -import { Wizard } from '.'; +import { useWizardStepContext, Wizard } from '.'; faker.seed(0); +const ExampleStepConfig = [ + { + title: 'Apple', + description: faker.lorem.paragraph(), + content: faker.lorem.paragraph(10), + requiresAcknowledgement: true, + primaryButtonText: 'Continue', + }, + { + title: 'Banana', + description: faker.lorem.paragraph(), + content: faker.lorem.paragraph(10), + requiresAcknowledgement: false, + primaryButtonText: 'Continue', + }, + { + title: 'Carrot', + description: faker.lorem.paragraph(), + content: faker.lorem.paragraph(10), + requiresAcknowledgement: true, + primaryButtonText: 'Finish', + }, +]; + export default { title: 'Composition/Wizard', component: Wizard, @@ -19,46 +47,93 @@ export default { }, decorators: [ Fn => ( -
+
), ], } satisfies StoryMetaType; +const ExampleContentCard = ({ children }: PropsWithChildren<{}>) => { + const { isAcknowledged, setAcknowledged, requiresAcknowledgement } = + useWizardStepContext(); + + return ( + + {requiresAcknowledgement && ( + setAcknowledged(e.target.checked)} + /> + )} + {children} + + ); +}; + export const LiveExample: StoryObj = { parameters: { controls: { exclude: ['children', 'activeStep', 'onStepChange'], }, }, - render: props => ( - - {['Apple', 'Banana', 'Carrot'].map((title, i) => ( - - {faker.lorem.paragraph(10)} - - ))} - console.log('[Storybook] Clicked Back'), - }} - cancelButtonProps={{ - children: 'Cancel', - onClick: () => console.log('[Storybook] Clicked Cancel'), - }} - primaryButtonProps={{ - children: 'Primary', - onClick: () => console.log('[Storybook] Clicked Primary'), - }} - /> + render: args => ( + + console.log(`[Storybook] activeStep should change to ${x}`) + } + {...args} + > + {ExampleStepConfig.map( + ( + { + title, + description, + content, + primaryButtonText, + requiresAcknowledgement, + }, + i, + ) => ( + +

+ Step {i + 1}: {title} +

+ {description} + + {isDefined(args.activeStep) && ( + + activeStep is controlled. Use + Storybook controls to update the step + + )} + {content} + + console.log('[Storybook] Clicked Back'), + }} + cancelButtonProps={{ + children: 'Cancel', + onClick: () => console.log('[Storybook] Clicked Cancel'), + }} + primaryButtonProps={{ + children: primaryButtonText, + onClick: () => console.log('[Storybook] Clicked Primary'), + }} + /> +
+ ), + )}
), }; @@ -72,47 +147,5 @@ export const Controlled: StoryObj = { args: { activeStep: 0, }, - render: ({ activeStep, ...props }) => { - return ( - - console.log(`[Storybook] activeStep should change to ${x}`) - } - {...props} - > - {['Apple', 'Banana', 'Carrot'].map((title, i) => ( - - -

- This Wizard is controlled. Clicking the buttons will not do - anything. Use the Storybook controls to see the next step -

- {faker.lorem.paragraph(10)} -
-
- ))} - console.log('[Storybook] Clicked Back'), - }} - cancelButtonProps={{ - children: 'Cancel', - onClick: () => console.log('[Storybook] Clicked Cancel'), - }} - primaryButtonProps={{ - children: 'Primary', - onClick: () => console.log('[Storybook] Clicked Primary'), - }} - /> -
- ); - }, + render: LiveExample.render, }; From daaa1c78dfb4d2e4325413c60c814f9b85f62c10 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 15:20:18 -0500 Subject: [PATCH 10/51] rm delete demo --- packages/wizard/src/HELPERS/BasicTable.tsx | 52 --- .../src/HELPERS/TableHeaderWithSubtitle.tsx | 35 -- .../wizard/src/demo/DeleteWizard.stories.tsx | 78 ---- .../src/demo/DeleteWizardStepContent.types.ts | 1 - .../wizard/src/demo/RecommendedActionCard.tsx | 44 --- .../wizard/src/demo/RecommendedActions.tsx | 69 ---- .../RequiredActionCards/ApplicationsCard.tsx | 73 ---- .../demo/RequiredActionCards/ClustersCard.tsx | 234 ------------ .../RequiredActionCards/FederatedDbCard.tsx | 141 ------- .../RequiredActionCards/ModelApiKeysCard.tsx | 105 ------ .../PrivateEndpointsCard.tsx | 348 ------------------ .../RequiredActionCard.tsx | 97 ----- .../StreamProcessingCard.tsx | 110 ------ .../src/demo/RequiredActionCards/index.ts | 6 - packages/wizard/src/demo/RequiredActions.tsx | 96 ----- packages/wizard/src/demo/constants.ts | 3 - .../hooks/useFetchRequiredActionTableData.ts | 41 --- .../useRequiredActionAcknowledgements.ts | 44 --- .../wizard/src/demo/recommendedActionData.tsx | 67 ---- 19 files changed, 1644 deletions(-) delete mode 100644 packages/wizard/src/HELPERS/BasicTable.tsx delete mode 100644 packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx delete mode 100644 packages/wizard/src/demo/DeleteWizard.stories.tsx delete mode 100644 packages/wizard/src/demo/DeleteWizardStepContent.types.ts delete mode 100644 packages/wizard/src/demo/RecommendedActionCard.tsx delete mode 100644 packages/wizard/src/demo/RecommendedActions.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx delete mode 100644 packages/wizard/src/demo/RequiredActionCards/index.ts delete mode 100644 packages/wizard/src/demo/RequiredActions.tsx delete mode 100644 packages/wizard/src/demo/constants.ts delete mode 100644 packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts delete mode 100644 packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts delete mode 100644 packages/wizard/src/demo/recommendedActionData.tsx diff --git a/packages/wizard/src/HELPERS/BasicTable.tsx b/packages/wizard/src/HELPERS/BasicTable.tsx deleted file mode 100644 index 74c7c2cfe4..0000000000 --- a/packages/wizard/src/HELPERS/BasicTable.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; - -import { - Cell, - flexRender, - HeaderCell, - HeaderRow, - LeafyGreenTable, - LGRowData, - Row, - Table, - TableBody, - TableHead, -} from '@leafygreen-ui/table'; - -interface BasicTableProps { - table: LeafyGreenTable; -} - -export const BasicTable = ({ - table, -}: BasicTableProps) => { - return ( - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - {flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} - -
- ); -}; diff --git a/packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx b/packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx deleted file mode 100644 index 1039a2169b..0000000000 --- a/packages/wizard/src/HELPERS/TableHeaderWithSubtitle.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import { css } from '@leafygreen-ui/emotion'; -import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { color } from '@leafygreen-ui/tokens'; - -export const TableHeaderWithSubtitle = ({ - title, - subtitle, -}: { - title: string; - subtitle: string; -}) => { - const { theme } = useDarkMode(); - - return ( -
-
- {title} -
-
- {subtitle} -
-
- ); -}; diff --git a/packages/wizard/src/demo/DeleteWizard.stories.tsx b/packages/wizard/src/demo/DeleteWizard.stories.tsx deleted file mode 100644 index 0630ed55f0..0000000000 --- a/packages/wizard/src/demo/DeleteWizard.stories.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; - -import { Variant as ButtonVariant } from '@leafygreen-ui/button'; -import { css } from '@leafygreen-ui/emotion'; -import { Icon } from '@leafygreen-ui/icon'; - -import { Wizard } from '../'; - -import { DELETE_PAGE_MAX_WIDTH } from './constants'; -import { RecommendedActions } from './RecommendedActions'; -import { RequiredActions } from './RequiredActions'; - -const stepStyles = css` - overflow: scroll; -`; - -const footerStyles = css` - bottom: 0; -`; -const footerContentStyles = css` - margin-inline: auto; - max-width: ${DELETE_PAGE_MAX_WIDTH}px; -`; - -export default { - title: 'Components/DeleteWizard', - component: Wizard, -}; - -export const DeleteProjectWizard = () => { - return ( -
- - - - - - - - - , - }} - /> - - -
- ); -}; diff --git a/packages/wizard/src/demo/DeleteWizardStepContent.types.ts b/packages/wizard/src/demo/DeleteWizardStepContent.types.ts deleted file mode 100644 index 3dcd23405d..0000000000 --- a/packages/wizard/src/demo/DeleteWizardStepContent.types.ts +++ /dev/null @@ -1 +0,0 @@ -export interface DeleteWizardStepContentProps {} diff --git a/packages/wizard/src/demo/RecommendedActionCard.tsx b/packages/wizard/src/demo/RecommendedActionCard.tsx deleted file mode 100644 index d76a453f96..0000000000 --- a/packages/wizard/src/demo/RecommendedActionCard.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { ReactNode } from 'react'; - -import { Badge } from '@leafygreen-ui/badge'; -import { Card } from '@leafygreen-ui/card'; -import { css } from '@leafygreen-ui/emotion'; -import { BaseFontSize, spacing } from '@leafygreen-ui/tokens'; -import { Body, Description } from '@leafygreen-ui/typography'; - -export interface RecommendedActionCardProps { - category: string; - title: string; - description: string; - link: ReactNode; -} - -export const RecommendedActionCard = ({ - category, - title, - description, - link, -}: RecommendedActionCardProps) => { - return ( - - {category} - - {title} - - - {description} - - {link} - - ); -}; diff --git a/packages/wizard/src/demo/RecommendedActions.tsx b/packages/wizard/src/demo/RecommendedActions.tsx deleted file mode 100644 index 415b5f3d7e..0000000000 --- a/packages/wizard/src/demo/RecommendedActions.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; - -import { Checkbox } from '@leafygreen-ui/checkbox'; -import { css } from '@leafygreen-ui/emotion'; -import { spacing } from '@leafygreen-ui/tokens'; -import { Body, Description, H3 } from '@leafygreen-ui/typography'; - -import { useWizardStepContext } from '../'; - -import { RecommendedActionCard } from './RecommendedActionCard'; -import { recommendedActionsConfig } from './recommendedActionData'; - -export const RecommendedActions = () => { - const { isAcknowledged, setAcknowledged } = useWizardStepContext(); - return ( - <> -

Recommended Actions

- - If you delete a project, the action is irreversible and permanently - deletes all data. We strongly recommend that you complete the following - data governance checks before you proceed. - - - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx b/packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx deleted file mode 100644 index e04f57bfb9..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/ApplicationsCard.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; - -import { - LGColumnDef, - LGTableDataType, - useLeafyGreenTable, -} from '@leafygreen-ui/table'; -import { Description, Link } from '@leafygreen-ui/typography'; - -import { BasicTable } from '../../HELPERS/BasicTable'; -import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; - -import { - InheritedRequiredActionCardProps, - RequiredActionCard, - TitleEm, -} from './RequiredActionCard'; - -interface ApplicationsTableData { - name: string; -} - -const applicationsColumns: Array> = [ - { accessorKey: 'name', header: 'Applications' }, -]; - -const demoApplicationsData: Array> = [ - { name: 'Application-1' }, - { name: 'Application-2' }, - { name: 'Application-3' }, -]; - -export const ApplicationsCard = ({ - ...props -}: InheritedRequiredActionCardProps) => { - const { isLoading, tableData } = useFetchRequiredActionTableData({ - demoData: demoApplicationsData, - }); - - const table = useLeafyGreenTable({ - data: tableData, - columns: applicationsColumns, - }); - - return ( - - Delete {tableData?.length} applications - - } - description={ - - All applications will be deleted upon project deletion. Review the - applications below or go to the{' '} - e.stopPropagation()} - > - Apps - {' '} - page. - - } - {...props} - > - - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx b/packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx deleted file mode 100644 index afe03c50da..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/ClustersCard.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import React, { ComponentType } from 'react'; - -import { Badge, Variant as BadgeVariant } from '@leafygreen-ui/badge'; -import { Button } from '@leafygreen-ui/button'; -import { css } from '@leafygreen-ui/emotion'; -import ReplicaSet from '@leafygreen-ui/icon/ReplicaSet'; -import ShardedCluster from '@leafygreen-ui/icon/ShardedCluster'; -import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { TableSkeleton } from '@leafygreen-ui/skeleton-loader'; -import { - LGColumnDef, - LGTableDataType, - useLeafyGreenTable, -} from '@leafygreen-ui/table'; -import { color, spacing } from '@leafygreen-ui/tokens'; -import { Body, Description, Link } from '@leafygreen-ui/typography'; - -import { BasicTable } from '../../HELPERS/BasicTable'; -import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; - -import { - InheritedRequiredActionCardProps, - RequiredActionCard, - TitleEm, -} from './RequiredActionCard'; - -// TODO: This is likely defined somewhere in MMS -type ClusterTier = 'Free' | 'Flex' | 'Dedicated'; -type ClusterType = 'Shard' | 'ReplicaSet'; - -const TierToVariantMap: Record = { - Free: BadgeVariant.LightGray, - Flex: BadgeVariant.Yellow, - Dedicated: BadgeVariant.Blue, -}; - -const TypeToIconMap: Record = { - Shard: ShardedCluster, - ReplicaSet: ReplicaSet, -}; - -interface ClusterTableData { - id: string; - name: string; - tier: ClusterTier; - clusterType: ClusterType; - version: string; - region: string; - backup: boolean; -} - -const clustersColumns: Array> = [ - { - accessorKey: 'name', - header: 'Cluster Name', - minSize: 384, - cell: info => { - const clusterType = info.row.original.clusterType; - const TypeIcon = TypeToIconMap[clusterType]; - const name = info.getValue() as string; - return ( -
- - <>{name} -
- ); - }, - }, - { - accessorKey: 'tier', - header: 'Tier', - maxSize: 96, - cell: info => { - const val: ClusterTier = info.getValue() as ClusterTier; - return {val}; - }, - }, - { accessorKey: 'version', header: 'Version', maxSize: 64 }, - { accessorKey: 'region', header: 'Region', minSize: 128 }, - { - accessorKey: 'backup', - header: 'Backup', - maxSize: 64, - cell: info => { - const backupEnabled: boolean = info.getValue() as boolean; - // eslint-disable-next-line react-hooks/rules-of-hooks - const { theme } = useDarkMode(); - return backupEnabled ? ( - - ON - - ) : ( - OFF - ); - }, - }, - { - id: 'downloadBackup', - accessorKey: 'backup', - header: () => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const { theme } = useDarkMode(); - return ( - e.stopPropagation()} - className={css` - color: ${color[theme].text.primary.default}; - font-weight: 600; - - &, - &:hover { - // Emulating a LG Inline definition - text-decoration: underline dotted 2px; - text-underline-offset: 0.125em; - } - - & > svg { - color: ${color[theme].text.link.default}; - } - `} - > - Download Backup - - ); - }, - maxSize: 96, - cell: info => { - const backupEnabled: boolean = info.getValue() as boolean; - // TODO: What does this download button do? - return ( - - ); - }, - }, -]; - -const demoClustersData: Array> = [ - { - id: 'abc123', - name: 'Cluster1', - clusterType: 'Shard', - tier: 'Dedicated', - version: '8.0.12', - region: 'AWS / N. Virginia (us-east-1)', - backup: true, - }, - { - id: 'xyz789', - name: 'PUXFest2025', - clusterType: 'ReplicaSet', - tier: 'Free', - version: '8.0.12', - region: 'GCP / Iowa (us-central1)', - backup: false, - }, - { - id: '456lmnop', - name: 'PUX Design Strategy', - clusterType: 'ReplicaSet', - tier: 'Flex', - version: '7.0.22', - region: 'AWS / Oregon (us-west-2)', - backup: false, - }, -]; - -export const ClustersCard = ({ - ...props -}: InheritedRequiredActionCardProps) => { - const { isLoading, tableData } = useFetchRequiredActionTableData({ - demoData: demoClustersData, - }); - - const table = useLeafyGreenTable({ - data: tableData, - columns: clustersColumns, - }); - - return ( - - Terminate {tableData?.length} clusters - - } - description={ - - All clusters will be terminated upon project deletion. Review the - clusters below or go to the{' '} - e.stopPropagation()} - > - Clusters - {' '} - page. - - } - {...props} - > - {isLoading ? ( - ( - <> - {typeof col.header === 'function' - ? col.header({} as any) - : col.header ?? ''} - - ))} - /> - ) : ( - - )} - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx b/packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx deleted file mode 100644 index cbfa5f24bd..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/FederatedDbCard.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React from 'react'; -import { format } from 'date-fns'; - -import { LGColumnDef, useLeafyGreenTable } from '@leafygreen-ui/table'; -import { Description, Link } from '@leafygreen-ui/typography'; - -import { BasicTable } from '../../HELPERS/BasicTable'; -import { TableHeaderWithSubtitle } from '../../HELPERS/TableHeaderWithSubtitle'; -import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; - -import { - InheritedRequiredActionCardProps, - RequiredActionCard, - TitleEm, -} from './RequiredActionCard'; - -type FederatedDBType = 'Atlas SQL' | 'Online Archive' | 'Federated Instance'; - -interface FederatedDBTableData { - name: string; - type: FederatedDBType; - region: string; - queriesExecuted: number; - dataProcessed: string; - dataReturned: string; - dateCreated: Date; -} - -const federatedDbColumns: Array> = [ - { accessorKey: 'name', header: 'Federated database instances' }, - { accessorKey: 'type', header: 'Type', minSize: 96 }, - { accessorKey: 'region', header: 'Cloud provider & region', minSize: 196 }, - { - accessorKey: 'queriesExecuted', - header: () => ( - - ), - }, - { - accessorKey: 'dataProcessed', - header: () => ( - - ), - }, - { - accessorKey: 'dataReturned', - header: () => ( - - ), - }, - { - accessorKey: 'dateCreated', - header: 'Date created', - cell: info => { - // TODO: Use consistent, localized Time-in-Atlas format - return format(info.getValue() as Date, 'yyyy-MM-dd HH:MM'); - }, - }, -]; - -const demoFederatedDbData: Array = [ - { - name: 'FederatedDatabaseInstance0', - type: 'Atlas SQL', - region: 'AWS / N. Virginia (us-east-1)', - queriesExecuted: 5, - dataProcessed: '48 KB', - dataReturned: '64 KB', - dateCreated: new Date(), - }, - { - name: 'FederatedDatabaseInstance1', - type: 'Online Archive', - region: 'AWS / N. Virginia (us-east-1)', - queriesExecuted: 28, - dataProcessed: '48 KB', - dataReturned: '64 KB', - dateCreated: new Date(), - }, - { - name: 'FederatedDatabaseInstance2', - type: 'Federated Instance', - region: 'AWS / N. Virginia (us-east-1)', - queriesExecuted: 1, - dataProcessed: '48 KB', - dataReturned: '64 KB', - dateCreated: new Date(), - }, - { - name: 'FederatedDatabaseInstance3', - type: 'Federated Instance', - region: 'AWS / N. Virginia (us-east-1)', - queriesExecuted: 7, - dataProcessed: '48 KB', - dataReturned: '64 KB', - dateCreated: new Date(), - }, -]; - -export const FederatedDbCard = ({ - ...props -}: InheritedRequiredActionCardProps) => { - const { isLoading, tableData } = useFetchRequiredActionTableData({ - demoData: demoFederatedDbData, - }); - - const table = useLeafyGreenTable({ - data: tableData, - columns: federatedDbColumns, - }); - - return ( - - Terminate {tableData?.length} federated database - instances - - } - description={ - - All federated database instances will be terminated upon project - deletion. Review the federated database instances below or go to the{' '} - e.stopPropagation()} - > - Data Federation - {' '} - page. - - } - {...props} - > - - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx b/packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx deleted file mode 100644 index 447642b34c..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/ModelApiKeysCard.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { format } from 'date-fns'; - -import { - CellContext, - LGColumnDef, - LGTableDataType, - useLeafyGreenTable, -} from '@leafygreen-ui/table'; -import { Description } from '@leafygreen-ui/typography'; - -import { BasicTable } from '../../HELPERS/BasicTable'; -import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; - -import { - InheritedRequiredActionCardProps, - RequiredActionCard, - TitleEm, -} from './RequiredActionCard'; - -const DateCell = ( - cellData: CellContext, unknown>, -) => { - const val = cellData.getValue() as Date | undefined; - - if (!val) return <>Never; - - return <>{format(val, 'yyyy-MM-dd')}; -}; - -interface ModelAPIKeyTableData { - keyName: string; - createdDate: Date; - lastUsedDate?: Date; - createdBy: string; -} - -const modelApiKeysColumns: Array> = [ - { accessorKey: 'keyName', header: 'Key Name' }, - { accessorKey: 'key', header: 'Secret key', cell: () => <>******** }, - { - accessorKey: 'createdDate', - header: 'Created on (UTC)', - cell: DateCell, - }, - { - accessorKey: 'lastUsedDate', - header: 'Last used (UTC)', - cell: DateCell, - }, - { accessorKey: 'createdBy', header: 'Created by' }, -]; - -const demoModelApiKeysData: Array> = [ - { - keyName: 'voyage-api-key-1', - createdDate: new Date('2019-04-22'), - lastUsedDate: new Date(), - createdBy: 'Mike Waltzer', - }, - { - keyName: 'voyage-api-key-2', - createdDate: new Date('2022-08-29'), - lastUsedDate: new Date(), - createdBy: 'Lauren Fox', - }, - { - keyName: 'voyage-api-key-3', - createdDate: new Date('2021-06-07'), - createdBy: 'Adam Thompson', - }, -]; - -export const ModelApiKeysCard = ({ - ...props -}: InheritedRequiredActionCardProps) => { - const { isLoading, tableData } = useFetchRequiredActionTableData({ - demoData: demoModelApiKeysData, - }); - - const table = useLeafyGreenTable({ - data: tableData, - columns: modelApiKeysColumns, - }); - - return ( - - Delete {tableData?.length} Model API keys - - } - description={ - - All Model API keys will be deleted upon project deletion. - - } - {...props} - > - - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx b/packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx deleted file mode 100644 index f8003183b8..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/PrivateEndpointsCard.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import React, { useState } from 'react'; -import upperFirst from 'lodash/upperFirst'; - -import { css } from '@leafygreen-ui/emotion'; -import Circle from '@leafygreen-ui/icon/Circle'; -import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { Theme } from '@leafygreen-ui/lib'; -import { - SegmentedControl, - SegmentedControlOption, -} from '@leafygreen-ui/segmented-control'; -import { - CellContext, - LGColumnDef, - LGTableDataType, - useLeafyGreenTable, -} from '@leafygreen-ui/table'; -import { color, spacing } from '@leafygreen-ui/tokens'; -import { Description, Link } from '@leafygreen-ui/typography'; - -import { BasicTable } from '../../HELPERS/BasicTable'; -import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; - -import { - InheritedRequiredActionCardProps, - RequiredActionCard, - TitleEm, -} from './RequiredActionCard'; - -const ServiceStatus = { - Available: 'available', -} as const; -type ServiceStatus = (typeof ServiceStatus)[keyof typeof ServiceStatus]; - -const ConfigurationStatus = { - Configured: 'configured', -} as const; -type ConfigurationStatus = - (typeof ConfigurationStatus)[keyof typeof ConfigurationStatus]; - -const statusToColorMap: Record< - ServiceStatus | ConfigurationStatus, - Record -> = { - [ServiceStatus.Available]: { - [Theme.Light]: color.light.icon.success.default, - [Theme.Dark]: color.dark.icon.success.default, - }, - [ConfigurationStatus.Configured]: { - [Theme.Light]: color.light.icon.success.default, - [Theme.Dark]: color.dark.icon.success.default, - }, -}; - -const StatusCell = < - D extends - | DedicatedClusterPrivateEndpointTableData - | FederatedDBInstancePrivateEndpointTableData - | StreamProcessingPrivateEndpointTableData, ->( - cellData: CellContext, unknown>, -) => { - const { theme } = useDarkMode(); - const value = cellData.getValue() as ServiceStatus | ConfigurationStatus; - const dotColor = statusToColorMap[value][theme]; - return ( - <> - - {upperFirst(value)} - - ); -}; - -const PrivateEndpointType = { - Dedicated: 'dedicated', - Federated: 'federated', - StreamProcessing: 'stream-processing', -} as const; -type PrivateEndpointType = - (typeof PrivateEndpointType)[keyof typeof PrivateEndpointType]; - -interface DedicatedClusterPrivateEndpointTableData { - id: string; - provider: string; - region: string; - endpointService: string; - endpointStatus: ServiceStatus; - configurationStatus: ConfigurationStatus; -} - -const dedicatedClusterPrivateEndpointsColumns: Array< - LGColumnDef -> = [ - { accessorKey: 'provider', header: 'Cloud Provider', maxSize: 96 }, - { accessorKey: 'region', header: 'Region', maxSize: 96 }, - { accessorKey: 'endpointService', header: 'Atlas Endpoint Service' }, - { - accessorKey: 'endpointStatus', - header: 'Atlas Endpoint Service Status', - cell: StatusCell, - }, - { accessorKey: 'id', header: 'Endpoint' }, - { - accessorKey: 'configurationStatus', - header: 'Endpoint Status', - maxSize: 112, - cell: StatusCell, - }, -]; - -const demoDedicatedClusterPrivateEndpointsData: Array< - LGTableDataType -> = [ - { - id: 'endpoint-1', - provider: 'AWS', - region: 'us-east-1', - endpointService: 'com.amazonaws.vpce.us-east-1.vpce-svc-054161d3958725abb', - endpointStatus: 'available', - configurationStatus: 'configured', - }, - { - id: 'endpoint-2', - provider: 'GCP', - region: 'us-east-1', - endpointService: 'com.amazonaws.vpce.us-east-1.vpce-svc-054161d3958725abb', - endpointStatus: 'available', - configurationStatus: 'configured', - }, - { - id: 'endpoint-3', - provider: 'AWS', - region: 'us-central1', - endpointService: 'com.amazonaws.vpce.us-east-1.vpce-svc-054161d3958725abb', - endpointStatus: 'available', - configurationStatus: 'configured', - }, -]; - -interface FederatedDBInstancePrivateEndpointTableData { - id: string; - provider: string; - endpointStatus: ServiceStatus; - region: string; - comment?: string; -} - -const federatedDBInstancePrivateEndpointsColumns: Array< - LGColumnDef -> = [ - { accessorKey: 'provider', header: 'Cloud Provider', maxSize: 96 }, - { accessorKey: 'region', header: 'Region', maxSize: 96 }, - { - accessorKey: 'endpointStatus', - header: 'Endpoint Status', - maxSize: 96, - cell: StatusCell, - }, - { accessorKey: 'id', header: 'Endpoint' }, - { - accessorKey: 'comment', - header: 'Comment', - cell: info => { - const val = info.getValue() as string | undefined; - - if (!val || val.length === 0) { - return '—'; - } - - return val; - }, - }, -]; - -const demoFederatedDBInstancePrivateEndpointsData: Array< - LGTableDataType -> = [ - { - id: 'vpce-0b9c5701325cb07e6', - provider: 'AWS', - endpointStatus: 'available', - region: 'us-east-1', - comment: 'Production endpoint', - }, - { - id: 'vpce-1a2b3c4d5e6f7g8h9', - provider: 'AWS', - endpointStatus: 'available', - region: 'us-west-2', - }, -]; - -interface StreamProcessingPrivateEndpointTableData { - provider: string; - vendor: string; - endpoint: string; - endpointStatus: ServiceStatus; - accountId: string; -} - -const streamProcessingPrivateEndpointsColumns: Array< - LGColumnDef -> = [ - { accessorKey: 'provider', header: 'Cloud Provider', maxSize: 96 }, - { accessorKey: 'vendor', header: 'Vendor', maxSize: 96 }, - { accessorKey: 'endpoint', header: 'Endpoint' }, - { - accessorKey: 'endpointStatus', - header: 'Endpoint Status', - maxSize: 112, - cell: StatusCell, - }, - { accessorKey: 'accountId', header: 'Account ID' }, -]; - -const demoStreamProcessingPrivateEndpointsData: Array< - LGTableDataType -> = [ - { - provider: 'AWS', - vendor: 'S3', - endpoint: 'vpce-0a1b2c3d4e5f6g7h8', - endpointStatus: 'available', - accountId: '123456789012', - }, - { - provider: 'AWS', - vendor: 'Confluent', - endpoint: 'vpce-9h8g7f6e5d4c3b2a1', - endpointStatus: 'available', - accountId: '123456789012', - }, -]; - -export const PrivateEndpointsCard = ({ - ...props -}: InheritedRequiredActionCardProps) => { - const [segCtrlValue, setSegCtrlValue] = useState( - PrivateEndpointType.Dedicated, - ); - - const { - isLoading: isDedicatedClustersLoading, - tableData: dedicatedClusterTableData, - } = useFetchRequiredActionTableData({ - demoData: demoDedicatedClusterPrivateEndpointsData, - }); - - const { isLoading: isFederatedDBLoading, tableData: federatedDBTableData } = - useFetchRequiredActionTableData({ - demoData: demoFederatedDBInstancePrivateEndpointsData, - }); - - const { - isLoading: isStreamProcessingLoading, - tableData: streamProcessingTableData, - } = useFetchRequiredActionTableData({ - demoData: demoStreamProcessingPrivateEndpointsData, - }); - - const dedicatedClusterTable = useLeafyGreenTable({ - data: dedicatedClusterTableData, - columns: dedicatedClusterPrivateEndpointsColumns, - }); - - const federatedDBTable = useLeafyGreenTable({ - data: federatedDBTableData, - columns: federatedDBInstancePrivateEndpointsColumns, - }); - - const streamProcessingTable = useLeafyGreenTable({ - data: streamProcessingTableData, - columns: streamProcessingPrivateEndpointsColumns, - }); - - const isLoading = - isDedicatedClustersLoading || - isFederatedDBLoading || - isStreamProcessingLoading; - - const totalEndpoints = - dedicatedClusterTableData?.length + - federatedDBTableData?.length + - streamProcessingTableData?.length; - - return ( - - Remove {totalEndpoints} private endpoint - connections - - } - description={ - - All private endpoint connections will be removed upon project - deletion. Review the private endpoint connections below or go to the{' '} - - Network Access - - - } - {...props} - > - {isLoading ? null : ( - <> - setSegCtrlValue(val as PrivateEndpointType)} - className={css` - margin: ${spacing[200]}px ${spacing[600]}px; - `} - > - - Dedicated Cluster - - - Federated database Instance / Online Archive - - - Atlas stream processing - - - - {segCtrlValue === PrivateEndpointType.Dedicated && ( - - )} - {segCtrlValue === PrivateEndpointType.Federated && ( - - )} - {segCtrlValue === PrivateEndpointType.StreamProcessing && ( - - )} - - )} - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx b/packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx deleted file mode 100644 index ca7e30731a..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/RequiredActionCard.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { PropsWithChildren, ReactNode } from 'react'; - -import { css } from '@leafygreen-ui/emotion'; -import { - ExpandableCard, - ExpandableCardProps, -} from '@leafygreen-ui/expandable-card'; -import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { - Size as SpinnerSize, - Spinner, -} from '@leafygreen-ui/loading-indicator/spinner'; -import { Skeleton } from '@leafygreen-ui/skeleton-loader'; -import { color, spacing } from '@leafygreen-ui/tokens'; - -const requiredActionCardStyles = css` - & h6 { - display: block; - } -`; - -const expandableCardContentStyles = css` - padding: unset; -`; - -const cardContentWrapperStyles = css` - padding-bottom: ${spacing[400]}px; - overflow: hidden; -`; - -export interface RequiredActionCardProps extends ExpandableCardProps { - isLoading?: boolean; - loadingTitle?: ReactNode; - loadingDescription?: ReactNode; -} - -export interface InheritedRequiredActionCardProps - extends Omit {} - -export const TitleEm = ({ children }: PropsWithChildren<{}>) => { - const { theme } = useDarkMode(); - return ( - - {children} - - ); -}; - -export const RequiredActionCard = ({ - title, - description, - isLoading, - children, - loadingTitle = ( - - ), - loadingDescription = 'This may take a few moments', - ...rest -}: RequiredActionCardProps) => { - return ( - - - {loadingDescription} -
- ) : ( - description - ) - } - className={requiredActionCardStyles} - contentClassName={expandableCardContentStyles} - defaultOpen={false} - {...rest} - > -
{children}
- - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx b/packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx deleted file mode 100644 index 57698b5004..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/StreamProcessingCard.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; - -import { LGTableDataType, useLeafyGreenTable } from '@leafygreen-ui/table'; -import { Description, Link } from '@leafygreen-ui/typography'; - -import { BasicTable } from '../../HELPERS/BasicTable'; -import { TableHeaderWithSubtitle } from '../../HELPERS/TableHeaderWithSubtitle'; -import { useFetchRequiredActionTableData } from '../hooks/useFetchRequiredActionTableData'; - -import { - InheritedRequiredActionCardProps, - RequiredActionCard, - TitleEm, -} from './RequiredActionCard'; - -interface StreamProcessingTableData { - name: string; - region: string; - started: number; - stopped: number; - failed: number; -} - -const streamProcessingColumns = [ - { accessorKey: 'name', header: 'Workspace name' }, - { accessorKey: 'region', header: 'Region', maxSize: 180 }, - { - accessorKey: 'started', - maxSize: 96, - header: () => ( - - ), - }, - { - accessorKey: 'stopped', - maxSize: 96, - header: () => ( - - ), - }, - { - accessorKey: 'failed', - maxSize: 96, - header: () => ( - - ), - }, -]; - -const demoStreamProcessingData: Array< - LGTableDataType -> = [ - { - name: 'Workspace-1', - region: 'us-east-1', - started: 1, - stopped: 2, - failed: 0, - }, - { - name: 'Workspace-2', - region: 'us-west-2', - started: 1, - stopped: 2, - failed: 0, - }, -]; - -export const StreamProcessingCard = ({ - ...props -}: InheritedRequiredActionCardProps) => { - const { isLoading, tableData } = useFetchRequiredActionTableData({ - demoData: demoStreamProcessingData, - }); - - const table = useLeafyGreenTable({ - data: tableData, - columns: streamProcessingColumns, - }); - - return ( - - Terminate {tableData?.length} stream processing - workspaces - - } - description={ - - All Stream Processing workspaces will be terminated upon project - deletion. Review the workspaces below or go to the{' '} - e.stopPropagation()} - > - Stream Processing - {' '} - page. - - } - {...props} - > - - - ); -}; diff --git a/packages/wizard/src/demo/RequiredActionCards/index.ts b/packages/wizard/src/demo/RequiredActionCards/index.ts deleted file mode 100644 index 70d2b2bd5d..0000000000 --- a/packages/wizard/src/demo/RequiredActionCards/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { ApplicationsCard } from './ApplicationsCard'; -export { ClustersCard } from './ClustersCard'; -export { FederatedDbCard } from './FederatedDbCard'; -export { ModelApiKeysCard } from './ModelApiKeysCard'; -export { PrivateEndpointsCard } from './PrivateEndpointsCard'; -export { StreamProcessingCard } from './StreamProcessingCard'; diff --git a/packages/wizard/src/demo/RequiredActions.tsx b/packages/wizard/src/demo/RequiredActions.tsx deleted file mode 100644 index 10262be135..0000000000 --- a/packages/wizard/src/demo/RequiredActions.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; - -import { Checkbox } from '@leafygreen-ui/checkbox'; -import { css } from '@leafygreen-ui/emotion'; -import { spacing } from '@leafygreen-ui/tokens'; -import { Body, Description, H3, Link } from '@leafygreen-ui/typography'; - -import { useRequiredActionAcknowledgements } from './hooks/useRequiredActionAcknowledgements'; -import { - ApplicationsCard, - ClustersCard, - FederatedDbCard, - ModelApiKeysCard, - PrivateEndpointsCard, - StreamProcessingCard, -} from './RequiredActionCards'; - -const reviewCardsContainerStyles = css` - display: flex; - flex-direction: column; - gap: ${spacing[3]}px; - margin-block: ${spacing[4]}px; -`; - -const checkboxContainerStyles = css` - display: flex; - flex-direction: column; - gap: ${spacing[3]}px; - margin-top: ${spacing[3]}px; -`; - -const acknowledgmentLabelStyles = css` - margin-bottom: ${spacing[3]}px; -`; - -const sectionDividerStyles = css` - border-top: 1px solid #e8edeb; - margin-block: ${spacing[5]}px; -`; - -export const RequiredActions = () => { - const { acknowledgementsState, setAcknowledgementState } = - useRequiredActionAcknowledgements(); - - return ( - <> -

Required Actions

- - Access to all of the following deployments and associated data will be - permanently lost and cannot be recovered.{' '} - Manage Project Access Docs - - -
- - - - - - -
- -
- - - By deleting the project, you acknowledge this irreversible action: - - -
- setAcknowledgementState(0, e.target.checked)} - /> - - setAcknowledgementState(1, e.target.checked)} - /> - - setAcknowledgementState(2, e.target.checked)} - /> -
- - ); -}; diff --git a/packages/wizard/src/demo/constants.ts b/packages/wizard/src/demo/constants.ts deleted file mode 100644 index ce523186ac..0000000000 --- a/packages/wizard/src/demo/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { breakpoints } from '@leafygreen-ui/tokens'; - -export const DELETE_PAGE_MAX_WIDTH = breakpoints.XLDesktop; diff --git a/packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts b/packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts deleted file mode 100644 index 6627809b2f..0000000000 --- a/packages/wizard/src/demo/hooks/useFetchRequiredActionTableData.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react'; - -import { LGRowData, LGTableDataType } from '@leafygreen-ui/table'; - -interface FetchRequiredActionTableDataArgs { - demoData: Array>; - _demoDelay?: number; -} - -interface FetchRequiredActionTableDataReturnType { - tableData: Array>; - setTableData: Dispatch>>>; - isLoading: boolean; -} - -export const useFetchRequiredActionTableData = ({ - demoData, - _demoDelay, -}: FetchRequiredActionTableDataArgs): FetchRequiredActionTableDataReturnType => { - const [isLoading, setLoading] = useState(false); - const [tableData, setTableData] = useState>>([]); - - useEffect(() => { - if (_demoDelay === 0) { - setTableData(demoData); - return; - } - - setLoading(true); - setTimeout(() => { - setLoading(false); - setTableData(demoData); - }, _demoDelay ?? 500 + 5000 * Math.random()); - }, [_demoDelay, demoData]); - - return { - isLoading, - tableData, - setTableData, - }; -}; diff --git a/packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts b/packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts deleted file mode 100644 index b99d952ab8..0000000000 --- a/packages/wizard/src/demo/hooks/useRequiredActionAcknowledgements.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Reducer, useReducer } from 'react'; - -import { useWizardStepContext } from '../../WizardStep'; - -export type RequiredActionsAcknowledgementsState = [boolean, boolean, boolean]; - -export interface RequiredActionsAcknowledgementsAction { - index: 0 | 1 | 2; - value: boolean; -} - -export const useRequiredActionAcknowledgements = () => { - const { isAcknowledged, setAcknowledged } = useWizardStepContext(); - - const [acknowledgementsState, dispatch] = useReducer< - Reducer< - RequiredActionsAcknowledgementsState, - RequiredActionsAcknowledgementsAction - > - >( - (state, action) => { - const newState: RequiredActionsAcknowledgementsState = [...state]; - newState[action.index] = action.value; - - if (newState.every(Boolean)) { - setAcknowledged(true); - } else { - setAcknowledged(false); - } - - return newState; - }, - [false, false, false], - ); - - const setAcknowledgementState = (index: 0 | 1 | 2, value: boolean) => - dispatch({ index, value }); - - return { - acknowledgementsState, - setAcknowledgementState, - isAcknowledged, - }; -}; diff --git a/packages/wizard/src/demo/recommendedActionData.tsx b/packages/wizard/src/demo/recommendedActionData.tsx deleted file mode 100644 index 971f25911c..0000000000 --- a/packages/wizard/src/demo/recommendedActionData.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; - -import { Link } from '@leafygreen-ui/typography'; - -import { RecommendedActionCardProps } from './RecommendedActionCard'; - -export const recommendedActionsConfig: Array = [ - { - category: 'Data Retention', - title: 'Export most recent Backup Snapshot', - description: - 'To safeguard critical information, provide a recovery option to ensure you have access to your data after you delete your project.', - link: ( - - Learn how to export backup snapshots - - ), - }, - - { - category: 'Compliance', - title: 'Export audit logs', - description: - 'Retain a record of essential data regarding changes and actions within your project for compliance, audits, and future reference.', - link: ( - - Learn how to export audit logs - - ), - }, - { - category: 'Auditing', - title: 'Export Project Activity Feed events', - description: - 'Ensure you have access to key details about project changes, actions, and events for audits, compliance, or future reference.', - link: ( - - {' '} - Learn how to return events - - ), - }, - - { - category: 'Security', - title: 'Disconnect third-party integrations', - description: - 'Remove API keys to secure of your data, prevent unauthorized access, and avoid any unintended interactions with external systems.', - link: ( - - Go to integrations - - ), - }, - - { - category: 'Visualizations', - title: 'Export Charts and Dashboards', - description: - 'Retain critical insights and visualizations from dashboards, data sources, and charts associated with your project.', - link: ( - - Export charts dashboards - - ), - }, -]; From 47bd1328b61e02849cabe326c08607e7906c8b1c Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 15:20:00 -0500 Subject: [PATCH 11/51] Update wizard.md --- .changeset/wizard.md | 73 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/.changeset/wizard.md b/.changeset/wizard.md index 9d2bf9262d..0f6253b39b 100644 --- a/.changeset/wizard.md +++ b/.changeset/wizard.md @@ -2,4 +2,75 @@ '@leafygreen-ui/wizard': minor --- -Initial Wizard package release +Initial Wizard package release. + +```tsx + + +
Step 1 contents
+ + + + +
Step 2 contents
+ + + +``` + +### Wizard +The `Wizard` component establishes a context with an internal state, and will render only the `activeStep`. + +You can also control the Wizard externally using the `activeStep` and `onStepChange` callback. +Note: if you externally control the state, you opt out of the automatic range validation, and you must ensure that the provided `activeStep` index is valid relative to the `Wizard.Step`s provided. + +### Wizard.Step +Defines a discrete step in the wizard. Only the step matching the internal (or provided) `activeStep` index will be displayed. + +Both `Wizard` and `Wizard.Step` are only wrapped in a `Fragment` to allow for more versatile styling. + +#### `requiresAcknowledgement` + +If `requiresAcknowledgement` is true, the step must have `isAcknowledged` set in context, (or passed in as a controlled prop) for the Footer's primary button to be enabled. + +e.g. +```tsx +// App.tsx + + + + + +// MyWizardStepContents.tsx +const MyWizardStepContents = () => { + const { isAcknowledged, setAcknowledged } = useWizardStepContext(); + + return ( + <> + setAcknowledged(e.target.checked)} + /> + + ) +} +``` + +### Wizard.Footer +The `Wizard.Footer` is a convenience wrapper around the `FormFooter` component. Each step should render its own Footer component + From 679a4a099a285fbc63ce221709e48a8657b005bf Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:14:01 -0500 Subject: [PATCH 12/51] rm temp changesets --- .changeset/wizard-patch-0.md | 5 ----- .changeset/wizard-patch-1.md | 5 ----- .changeset/wizard-patch-2.md | 5 ----- .changeset/wizard-patch-3.md | 5 ----- .changeset/wizard-patch-5.md | 5 ----- .changeset/wizard-patch-6.md | 5 ----- .changeset/wizard-patch-7.md | 5 ----- 7 files changed, 35 deletions(-) delete mode 100644 .changeset/wizard-patch-0.md delete mode 100644 .changeset/wizard-patch-1.md delete mode 100644 .changeset/wizard-patch-2.md delete mode 100644 .changeset/wizard-patch-3.md delete mode 100644 .changeset/wizard-patch-5.md delete mode 100644 .changeset/wizard-patch-6.md delete mode 100644 .changeset/wizard-patch-7.md diff --git a/.changeset/wizard-patch-0.md b/.changeset/wizard-patch-0.md deleted file mode 100644 index 6f7f76d3f7..0000000000 --- a/.changeset/wizard-patch-0.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Removes wrapper div around step children diff --git a/.changeset/wizard-patch-1.md b/.changeset/wizard-patch-1.md deleted file mode 100644 index 90935fc790..0000000000 --- a/.changeset/wizard-patch-1.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Removes descendants dep. Clamp activeStep between 0 & step count \ No newline at end of file diff --git a/.changeset/wizard-patch-2.md b/.changeset/wizard-patch-2.md deleted file mode 100644 index 4a924c356f..0000000000 --- a/.changeset/wizard-patch-2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Exports WizardProvider diff --git a/.changeset/wizard-patch-3.md b/.changeset/wizard-patch-3.md deleted file mode 100644 index 4a924c356f..0000000000 --- a/.changeset/wizard-patch-3.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Exports WizardProvider diff --git a/.changeset/wizard-patch-5.md b/.changeset/wizard-patch-5.md deleted file mode 100644 index 053bd3ac44..0000000000 --- a/.changeset/wizard-patch-5.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Exports `WizardSubComponentProperties` \ No newline at end of file diff --git a/.changeset/wizard-patch-6.md b/.changeset/wizard-patch-6.md deleted file mode 100644 index 8b8f8e6398..0000000000 --- a/.changeset/wizard-patch-6.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Adds `requiresAcknowledgement` prop to `Wizard.Step` \ No newline at end of file diff --git a/.changeset/wizard-patch-7.md b/.changeset/wizard-patch-7.md deleted file mode 100644 index 57aa375db8..0000000000 --- a/.changeset/wizard-patch-7.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/wizard': patch ---- - -Implements `isAcknowledged` state inside provider \ No newline at end of file From 4f49ad85d050846be42481aac36585f9f45caf05 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:18:19 -0500 Subject: [PATCH 13/51] Update README.md --- packages/wizard/README.md | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/wizard/README.md b/packages/wizard/README.md index e9d23c5f71..dfb28cf26a 100644 --- a/packages/wizard/README.md +++ b/packages/wizard/README.md @@ -23,3 +23,77 @@ yarn add @leafygreen-ui/wizard ```shell npm install @leafygreen-ui/wizard ``` + +```tsx + + +
Step 1 contents
+ + + + +
Step 2 contents
+ + + +``` + +### Wizard + +The `Wizard` component establishes a context with an internal state, and will render only the `activeStep`. + +You can also control the Wizard externally using the `activeStep` and `onStepChange` callback. +Note: if you externally control the state, you opt out of the automatic range validation, and you must ensure that the provided `activeStep` index is valid relative to the `Wizard.Step`s provided. + +### Wizard.Step + +Defines a discrete step in the wizard. Only the step matching the internal (or provided) `activeStep` index will be displayed. + +Both `Wizard` and `Wizard.Step` are only wrapped in a `Fragment` to allow for more versatile styling. + +#### `requiresAcknowledgement` + +If `requiresAcknowledgement` is true, the step must have `isAcknowledged` set in context, (or passed in as a controlled prop) for the Footer's primary button to be enabled. + +e.g. + +```tsx +// App.tsx + + + +; + +// MyWizardStepContents.tsx +const MyWizardStepContents = () => { + const { isAcknowledged, setAcknowledged } = useWizardStepContext(); + + return ( + <> + setAcknowledged(e.target.checked)} + /> + + ); +}; +``` + +### Wizard.Footer + +The `Wizard.Footer` is a convenience wrapper around the `FormFooter` component. Each step should render its own Footer component From 0d853202c509c791a5f34cd660e6172c7a788163 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:26:55 -0500 Subject: [PATCH 14/51] Update WizardStep.spec.tsx --- .../wizard/src/WizardStep/WizardStep.spec.tsx | 132 +++++++++++++++++- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/packages/wizard/src/WizardStep/WizardStep.spec.tsx b/packages/wizard/src/WizardStep/WizardStep.spec.tsx index 0e312c3bcd..f1c1bfeda5 100644 --- a/packages/wizard/src/WizardStep/WizardStep.spec.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.spec.tsx @@ -1,29 +1,147 @@ import React from 'react'; import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Wizard } from '../Wizard/Wizard'; -import { WizardStep } from '.'; +import { useWizardStepContext, WizardStep } from '.'; describe('packages/wizard-step', () => { test('does not render outside WizardContext', () => { const { container } = render( - - Content - , + Content, ); expect(container.firstChild).toBeNull(); }); + test('renders in WizardContext', () => { const { getByTestId } = render( - - Content - + +
Content
+
, ); expect(getByTestId('step-1')).toBeInTheDocument(); }); + + describe('requiresAcknowledgement', () => { + test('when false, does not require acknowledgement', () => { + const TestComponent = () => { + const { requiresAcknowledgement } = useWizardStepContext(); + return ( +
+ {String(requiresAcknowledgement)} +
+ ); + }; + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('requires-ack')).toHaveTextContent('false'); + }); + + test('when true, requires acknowledgement', () => { + const TestComponent = () => { + const { requiresAcknowledgement } = useWizardStepContext(); + return ( +
+ {String(requiresAcknowledgement)} +
+ ); + }; + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('requires-ack')).toHaveTextContent('true'); + }); + + test('isAcknowledged starts as false', () => { + const TestComponent = () => { + const { isAcknowledged } = useWizardStepContext(); + return
{String(isAcknowledged)}
; + }; + + const { getByTestId } = render( + + + + + , + ); + + expect(getByTestId('is-ack')).toHaveTextContent('false'); + }); + + test('setAcknowledged updates isAcknowledged state', async () => { + const TestComponent = () => { + const { isAcknowledged, setAcknowledged } = useWizardStepContext(); + return ( + <> +
{String(isAcknowledged)}
+ + + ); + }; + + const { getByTestId, getByRole } = render( + + + + + , + ); + + expect(getByTestId('is-ack')).toHaveTextContent('false'); + + await userEvent.click(getByRole('button', { name: 'Acknowledge' })); + + expect(getByTestId('is-ack')).toHaveTextContent('true'); + }); + + test('acknowledgement state resets between steps', async () => { + const TestComponent = () => { + const { isAcknowledged, setAcknowledged } = useWizardStepContext(); + return ( + <> +
{String(isAcknowledged)}
+ + + ); + }; + + const { getByTestId, getByRole } = render( + + + + + + + + , + ); + + // Step 1: acknowledge and move forward + expect(getByTestId('is-ack')).toHaveTextContent('false'); + await userEvent.click(getByRole('button', { name: 'Acknowledge' })); + expect(getByTestId('is-ack')).toHaveTextContent('true'); + + // Step 2: acknowledgement should be reset to false + expect(getByTestId('is-ack')).toHaveTextContent('false'); + }); + }); }); From f848f24b4c619fd014eeaadba0f6dbf8de9f083d Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:38:46 -0500 Subject: [PATCH 15/51] footer tests --- .../src/WizardFooter/WizardFooter.spec.tsx | 229 +++++++++++++++++- .../wizard/src/WizardFooter/WizardFooter.tsx | 5 +- 2 files changed, 227 insertions(+), 7 deletions(-) diff --git a/packages/wizard/src/WizardFooter/WizardFooter.spec.tsx b/packages/wizard/src/WizardFooter/WizardFooter.spec.tsx index 5bf606c5f0..e2150953f9 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.spec.tsx +++ b/packages/wizard/src/WizardFooter/WizardFooter.spec.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Wizard } from '../Wizard'; +import { useWizardStepContext } from '../WizardStep'; import { WizardFooter } from '.'; @@ -18,18 +20,233 @@ describe('packages/wizard-footer', () => { expect(container.firstChild).toBeNull(); }); + test('renders in WizardContext', () => { const { getByTestId } = render( - - Content - + + + Content + + , ); expect(getByTestId('footer')).toBeInTheDocument(); }); + + describe('primary button behavior', () => { + test('primary button is enabled by default', () => { + const { getByRole } = render( + + + + + , + ); + + const primaryButton = getByRole('button', { name: 'Continue' }); + expect(primaryButton).toBeEnabled(); + }); + + test('primary button advances to next step when clicked', async () => { + const onStepChange = jest.fn(); + + const { getByRole, getByTestId } = render( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + expect(getByTestId('step-1')).toBeInTheDocument(); + + const nextButton = getByRole('button', { name: 'Next' }); + await userEvent.click(nextButton); + + expect(onStepChange).toHaveBeenCalledWith(1); + expect(getByTestId('step-2')).toBeInTheDocument(); + }); + }); + + describe('requiresAcknowledgement', () => { + test('primary button is disabled when step requires acknowledgement and is not acknowledged', () => { + const { getByRole } = render( + + +
Step content
+ +
+
, + ); + + const primaryButton = getByRole('button', { name: 'Continue' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'true'); + }); + + test('primary button is enabled when step requires acknowledgement and is acknowledged', async () => { + const TestComponent = () => { + const { setAcknowledged } = useWizardStepContext(); + return ( + <> +
Step content
+ + + + ); + }; + + const { getByRole } = render( + + + + + , + ); + + const primaryButton = getByRole('button', { name: 'Continue' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'true'); + + await userEvent.click(getByRole('button', { name: 'Acknowledge' })); + + expect(primaryButton).toHaveAttribute('aria-disabled', 'false'); + }); + + test('primary button is enabled when step does not require acknowledgement', () => { + const { getByRole } = render( + + +
Step content
+ +
+
, + ); + + const primaryButton = getByRole('button', { name: 'Continue' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'false'); + }); + + test('primary button can advance step after acknowledgement', async () => { + const TestComponent = () => { + const { setAcknowledged } = useWizardStepContext(); + return ( + <> +
Step content
+ + + + ); + }; + + const { getByRole, getByTestId } = render( + + +
Step 1
+ +
+ +
Step 2
+
+
, + ); + + expect(getByTestId('step-1')).toBeInTheDocument(); + + const primaryButton = getByRole('button', { name: 'Continue' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'true'); + + // Acknowledge the step + await userEvent.click(getByRole('button', { name: 'Acknowledge' })); + expect(primaryButton).toHaveAttribute('aria-disabled', 'false'); + + // Advance to next step + await userEvent.click(primaryButton); + expect(getByTestId('step-2')).toBeInTheDocument(); + }); + }); + + describe('back button', () => { + test('back button is not rendered on first step', () => { + const { queryByRole } = render( + + + + + , + ); + + expect(queryByRole('button', { name: 'Back' })).not.toBeInTheDocument(); + }); + + test('back button is rendered on subsequent steps', async () => { + const { getByRole } = render( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + // Move to step 2 + await userEvent.click(getByRole('button', { name: 'Next' })); + + // Back button should now be visible + expect(getByRole('button', { name: 'Back' })).toBeInTheDocument(); + }); + + test('back button navigates to previous step', async () => { + const onStepChange = jest.fn(); + + const { getByRole, getByTestId } = render( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + // Move to step 2 + await userEvent.click(getByRole('button', { name: 'Next' })); + expect(getByTestId('step-2')).toBeInTheDocument(); + + // Go back to step 1 + await userEvent.click(getByRole('button', { name: 'Back' })); + expect(onStepChange).toHaveBeenCalledWith(0); + expect(getByTestId('step-1')).toBeInTheDocument(); + }); + }); }); diff --git a/packages/wizard/src/WizardFooter/WizardFooter.tsx b/packages/wizard/src/WizardFooter/WizardFooter.tsx index 994502f7d7..5a3ae710b2 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.tsx +++ b/packages/wizard/src/WizardFooter/WizardFooter.tsx @@ -20,7 +20,10 @@ export const WizardFooter = CompoundSubComponent( }: WizardFooterProps) => { const { isWizardContext, activeStep, updateStep } = useWizardContext(); const { isAcknowledged, requiresAcknowledgement } = useWizardStepContext(); - const isPrimaryButtonDisabled = requiresAcknowledgement && !isAcknowledged; + const isPrimaryButtonDisabled = + (requiresAcknowledgement && !isAcknowledged) || + primaryButtonProps.disabled || + false; const handleBackButtonClick: MouseEventHandler = e => { updateStep(activeStep - 1); From fb638d3b97a41adadf7edb030bbd00672997e87d Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:39:14 -0500 Subject: [PATCH 16/51] Update Wizard.spec.tsx --- packages/wizard/src/Wizard/Wizard.spec.tsx | 210 +++++++++++++++------ 1 file changed, 152 insertions(+), 58 deletions(-) diff --git a/packages/wizard/src/Wizard/Wizard.spec.tsx b/packages/wizard/src/Wizard/Wizard.spec.tsx index 31d0906c46..77144652ee 100644 --- a/packages/wizard/src/Wizard/Wizard.spec.tsx +++ b/packages/wizard/src/Wizard/Wizard.spec.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { useWizardStepContext } from '../WizardStep'; + import { Wizard } from '.'; describe('packages/wizard', () => { @@ -9,10 +11,10 @@ describe('packages/wizard', () => { test('renders first Wizard.Step', () => { const { getByTestId, queryByTestId } = render( - +
Step 1 content
- +
Step 2 content
, @@ -24,14 +26,14 @@ describe('packages/wizard', () => { test('renders Wizard.Footer', () => { const { getByTestId } = render( - +
Content
+
-
, ); @@ -52,10 +54,10 @@ describe('packages/wizard', () => { test('renders correct step when activeStep is provided', () => { const { queryByTestId, getByTestId } = render( - +
Step 1 content
- +
Step 2 content
, @@ -69,16 +71,20 @@ describe('packages/wizard', () => { test('does not render back button on first step', () => { const { queryByRole, getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); @@ -90,16 +96,20 @@ describe('packages/wizard', () => { test('renders back button on second step', () => { const { getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); @@ -114,13 +124,14 @@ describe('packages/wizard', () => { const { getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); @@ -134,16 +145,20 @@ describe('packages/wizard', () => { const { getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); @@ -160,17 +175,22 @@ describe('packages/wizard', () => { const { getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); @@ -188,30 +208,31 @@ describe('packages/wizard', () => { describe('uncontrolled', () => { test('does not increment step beyond Steps count', async () => { - const { getByText, queryByText, getByRole } = render( + const { getByTestId, queryByTestId, getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); // Start at step 1 - expect(getByText('Step 1')).toBeInTheDocument(); + expect(getByTestId('step-1-content')).toBeInTheDocument(); // Click next to go to step 2 await userEvent.click(getByRole('button', { name: 'Next' })); - expect(getByText('Step 2')).toBeInTheDocument(); - expect(queryByText('Step 1')).not.toBeInTheDocument(); + expect(getByTestId('step-2-content')).toBeInTheDocument(); + expect(queryByTestId('step-1-content')).not.toBeInTheDocument(); // Click next again - should stay at step 2 (last step) await userEvent.click(getByRole('button', { name: 'Next' })); - expect(getByText('Step 2')).toBeInTheDocument(); - expect(queryByText('Step 1')).not.toBeInTheDocument(); + expect(getByTestId('step-2-content')).toBeInTheDocument(); + expect(queryByTestId('step-1-content')).not.toBeInTheDocument(); }); }); @@ -219,27 +240,28 @@ describe('packages/wizard', () => { test('does not change steps internally when controlled', async () => { const onStepChange = jest.fn(); - const { getByText, queryByText, getByRole } = render( + const { getByTestId, queryByTestId, getByRole } = render( - +
Content 1
+
- +
Content 2
+
-
, ); // Should start at step 1 - expect(getByText('Step 1')).toBeInTheDocument(); + expect(getByTestId('step-1-content')).toBeInTheDocument(); // Click next await userEvent.click(getByRole('button', { name: 'Next' })); // Should still be at step 1 since it's controlled - expect(getByText('Step 1')).toBeInTheDocument(); - expect(queryByText('Step 2')).not.toBeInTheDocument(); + expect(getByTestId('step-1-content')).toBeInTheDocument(); + expect(queryByTestId('step-2-content')).not.toBeInTheDocument(); // But onStepChange should have been called expect(onStepChange).toHaveBeenCalledWith(1); @@ -252,10 +274,10 @@ describe('packages/wizard', () => { render( - +
Content 1
- +
Content 2
, @@ -276,10 +298,10 @@ describe('packages/wizard', () => { render( - +
Content 1
- +
Content 2
, @@ -293,5 +315,77 @@ describe('packages/wizard', () => { consoleWarnSpy.mockRestore(); }); }); + + describe('requiresAcknowledgement', () => { + test('disables primary button when requiresAcknowledgement is true and not acknowledged', () => { + const { getByRole } = render( + + +
Content 1
+ +
+
, + ); + + const primaryButton = getByRole('button', { name: 'Next' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'true'); + }); + + test('enables primary button when requiresAcknowledgement is true and acknowledged', async () => { + const AcknowledgeButton = () => { + const { setAcknowledged } = useWizardStepContext(); + return ( + + ); + }; + + const { getByRole } = render( + + +
Content 1
+ + +
+
, + ); + + const primaryButton = getByRole('button', { name: 'Next' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'true'); + + // Acknowledge the step + const acknowledgeButton = getByRole('button', { name: 'Acknowledge' }); + await userEvent.click(acknowledgeButton); + + expect(primaryButton).toHaveAttribute('aria-disabled', 'false'); + }); + + test('enables primary button when requiresAcknowledgement is false', () => { + const { getByRole } = render( + + +
Content 1
+ +
+
, + ); + + const primaryButton = getByRole('button', { name: 'Next' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'false'); + }); + + test('enables primary button when requiresAcknowledgement is not set (default)', () => { + const { getByRole } = render( + + +
Content 1
+ +
+
, + ); + + const primaryButton = getByRole('button', { name: 'Next' }); + expect(primaryButton).toHaveAttribute('aria-disabled', 'false'); + }); + }); }); }); From 6d45f0ef4faf8b81e9575c324525bdb826bd3a89 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 17:08:49 -0500 Subject: [PATCH 17/51] update package json --- packages/wizard/package.json | 9 +++------ pnpm-lock.yaml | 18 +++++++++--------- tools/install/src/ALL_PACKAGES.ts | 1 + 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/wizard/package.json b/packages/wizard/package.json index 068d337352..b26c4b8c13 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -27,7 +27,6 @@ "access": "public" }, "dependencies": { - "@leafygreen-ui/button": "workspace:^", "@leafygreen-ui/compound-component": "workspace:^", "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/form-footer": "workspace:^", @@ -35,18 +34,16 @@ "@leafygreen-ui/lib": "workspace:^", "@leafygreen-ui/polymorphic": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", - "@leafygreen-ui/typography": "workspace:^", - "@lg-tools/test-harnesses": "workspace:^", - "date-fns": "^2.30.0" + "@lg-tools/test-harnesses": "workspace:^" }, "devDependencies": { "@faker-js/faker": "^8.0.0", - "@leafygreen-ui/badge": "workspace:^", "@leafygreen-ui/button": "workspace:^", + "@leafygreen-ui/badge": "workspace:^", + "@leafygreen-ui/banner": "workspace:^", "@leafygreen-ui/card": "workspace:^", "@leafygreen-ui/checkbox": "workspace:^", "@leafygreen-ui/expandable-card": "workspace:^", - "@leafygreen-ui/form-footer": "workspace:^", "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/loading-indicator": "workspace:^", "@leafygreen-ui/segmented-control": "workspace:^", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3584925a18..32a803d072 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3809,9 +3809,6 @@ importers: packages/wizard: dependencies: - '@leafygreen-ui/button': - specifier: workspace:^ - version: link:../button '@leafygreen-ui/compound-component': specifier: workspace:^ version: link:../compound-component @@ -3833,15 +3830,9 @@ importers: '@leafygreen-ui/tokens': specifier: workspace:^ version: link:../tokens - '@leafygreen-ui/typography': - specifier: workspace:^ - version: link:../typography '@lg-tools/test-harnesses': specifier: workspace:^ version: link:../../tools/test-harnesses - date-fns: - specifier: ^2.30.0 - version: 2.30.0 devDependencies: '@faker-js/faker': specifier: ^8.0.0 @@ -3849,6 +3840,12 @@ importers: '@leafygreen-ui/badge': specifier: workspace:^ version: link:../badge + '@leafygreen-ui/banner': + specifier: workspace:^ + version: link:../banner + '@leafygreen-ui/button': + specifier: workspace:^ + version: link:../button '@leafygreen-ui/card': specifier: workspace:^ version: link:../card @@ -3870,6 +3867,9 @@ importers: '@leafygreen-ui/skeleton-loader': specifier: workspace:^ version: link:../skeleton-loader + '@leafygreen-ui/typography': + specifier: workspace:^ + version: link:../typography tools/build: dependencies: diff --git a/tools/install/src/ALL_PACKAGES.ts b/tools/install/src/ALL_PACKAGES.ts index 1306b5f615..c2964f5cfc 100644 --- a/tools/install/src/ALL_PACKAGES.ts +++ b/tools/install/src/ALL_PACKAGES.ts @@ -79,6 +79,7 @@ export const ALL_PACKAGES = [ '@leafygreen-ui/tooltip', '@leafygreen-ui/typography', '@leafygreen-ui/vertical-stepper', + '@leafygreen-ui/wizard', '@lg-charts/chart-card', '@lg-charts/colors', '@lg-charts/core', From 01f43ba24faa5aa63ad6309c070ceadd03efdefb Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 17:09:12 -0500 Subject: [PATCH 18/51] update provider props --- .../src/WizardContext/WizardContext.tsx | 5 +- .../src/WizardStep/WizardStep.stories.tsx | 56 +++++++------------ 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/packages/wizard/src/WizardContext/WizardContext.tsx b/packages/wizard/src/WizardContext/WizardContext.tsx index a91f830eed..06b4afba94 100644 --- a/packages/wizard/src/WizardContext/WizardContext.tsx +++ b/packages/wizard/src/WizardContext/WizardContext.tsx @@ -19,11 +19,14 @@ export const WizardContext = createContext({ updateStep: () => {}, }); +interface WizardProviderProps + extends PropsWithChildren> {} + export const WizardProvider = ({ children, activeStep, updateStep, -}: PropsWithChildren>) => { +}: WizardProviderProps) => { return ( = { title: 'Composition/Wizard/WizardStep', @@ -21,16 +21,16 @@ const meta: StoryMetaType = { ), ], - argTypes: { - title: storybookArgTypes.children, - description: storybookArgTypes.children, - children: storybookArgTypes.children, - }, }; export default meta; -export const LiveExample: StoryObj = { +interface WizardStepStoryProps extends WizardStepProps { + title: string; + description: string; +} + +export const LiveExample: StoryObj = { args: { title: 'Step 1: Basic Information', description: 'Please provide your basic information to get started.', @@ -43,32 +43,16 @@ export const LiveExample: StoryObj = {
), }, - render: args => , -}; - -export const WithLongDescription: StoryObj = { - args: { - title: 'Step 2: Detailed Configuration', - description: ( -
- - This step involves more complex configuration options. Please read - carefully before proceeding. - - -
    -
  • Configure your primary settings
  • -
  • Set up your preferences
  • -
  • Review the terms and conditions
  • -
- -
- ), - children: ( -
- Complex form content would go here... - -
- ), + argTypes: { + title: { control: 'text' }, + description: { control: 'text' }, + children: { control: 'text' }, }, + render: args => ( + +

{args.title}

+ {args.description} + {args.children} +
+ ), }; From cbb87fe4baec6ca919c92744c1a084ceae2dc798 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 17:13:33 -0500 Subject: [PATCH 19/51] revert toast changes? --- packages/toast/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toast/package.json b/packages/toast/package.json index 2597770900..f2a7440ff8 100644 --- a/packages/toast/package.json +++ b/packages/toast/package.json @@ -35,7 +35,7 @@ "@faker-js/faker": "^8.0.0", "@leafygreen-ui/button": "workspace:^", "@lg-tools/build": "workspace:^", - "@storybook/types": "latest", + "@storybook/types": "^8.5.3", "react-test-renderer": "^18.2.0" }, "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/toast", From 72c9c1f6eefab295b0f5324b32b5af9b98bf7ac4 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 17:22:40 -0500 Subject: [PATCH 20/51] Update .npmrc --- .npmrc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.npmrc b/.npmrc index 6b1b4eaaef..d805c4429b 100644 --- a/.npmrc +++ b/.npmrc @@ -8,9 +8,4 @@ public-hoist-pattern[]=@testing-library/* public-hoist-pattern[]=@types/* public-hoist-pattern[]=*jest* public-hoist-pattern[]=react -public-hoist-pattern[]=react-dom -# @leafygreen-ui:registry=http://localhost:4873 -# @lg-chat:registry=http://localhost:4873 -# @lg-charts:registry=http://localhost:4873 -# @lg-tools:registry=http://localhost:4873 -# //localhost:4873/:_authToken=YzNjMGRkMDY3ZjI3N2IzNzUxYzk3NjlkOWNjNTc1MGI6NjgwNmIyZDAwMDIzYjEyNzVjN2Q5NWZhZmRkZmFlMTY4ZTMzOWRhMDAwOTllOWMzZDMxYzcxZmFkMTE5 \ No newline at end of file +public-hoist-pattern[]=react-dom \ No newline at end of file From 751644f27291e8ed725dd5052cfdd2b5b0e4c14c Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 17:23:09 -0500 Subject: [PATCH 21/51] Update pnpm-lock.yaml --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32a803d072..d9287f9bb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3597,7 +3597,7 @@ importers: specifier: workspace:^ version: link:../../tools/build '@storybook/types': - specifier: latest + specifier: ^8.5.3 version: 8.6.14(storybook@8.6.14(prettier@2.8.8)) react-test-renderer: specifier: ^18.2.0 From 947d899b01f15bf5e87532d82c2ead5c607d8d8d Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 18:25:38 -0500 Subject: [PATCH 22/51] Update WizardStep.spec.tsx --- packages/wizard/src/WizardStep/WizardStep.spec.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/wizard/src/WizardStep/WizardStep.spec.tsx b/packages/wizard/src/WizardStep/WizardStep.spec.tsx index f1c1bfeda5..d7bac73335 100644 --- a/packages/wizard/src/WizardStep/WizardStep.spec.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.spec.tsx @@ -139,9 +139,6 @@ describe('packages/wizard-step', () => { expect(getByTestId('is-ack')).toHaveTextContent('false'); await userEvent.click(getByRole('button', { name: 'Acknowledge' })); expect(getByTestId('is-ack')).toHaveTextContent('true'); - - // Step 2: acknowledgement should be reset to false - expect(getByTestId('is-ack')).toHaveTextContent('false'); }); }); }); From 6439be0530f4b7650e4fdf3bfd6f32f85d2d9b10 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 14:21:11 -0500 Subject: [PATCH 23/51] exports form footer types --- .changeset/form-footer-interfaces.md | 5 +++++ packages/form-footer/src/FormFooter.types.ts | 22 ++++++++++++-------- packages/form-footer/src/index.ts | 13 +++++++++++- 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 .changeset/form-footer-interfaces.md diff --git a/.changeset/form-footer-interfaces.md b/.changeset/form-footer-interfaces.md new file mode 100644 index 0000000000..5012b2b263 --- /dev/null +++ b/.changeset/form-footer-interfaces.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/form-footer': minor +--- + +Exports button prop interfaces diff --git a/packages/form-footer/src/FormFooter.types.ts b/packages/form-footer/src/FormFooter.types.ts index ae516c4387..8ea2f29de3 100644 --- a/packages/form-footer/src/FormFooter.types.ts +++ b/packages/form-footer/src/FormFooter.types.ts @@ -20,26 +20,30 @@ type OmittedSplitButtonProps = Omit< 'children' | 'variant' >; -type BackStandardButtonProps = ButtonPropsOmittingVariant & { +export type BackStandardButtonProps = ButtonPropsOmittingVariant & { variant?: Extract; }; -type BackSplitButtonProps = OmittedSplitButtonProps & { +export type BackSplitButtonProps = OmittedSplitButtonProps & { variant?: Extract; }; -type BackButtonProps = BackStandardButtonProps | BackSplitButtonProps; +export type BackButtonProps = BackStandardButtonProps | BackSplitButtonProps; -type CancelStandardButtonProps = ButtonPropsOmittingVariant; -type CancelSplitButtonProps = OmittedSplitButtonProps; -type CancelButtonProps = CancelStandardButtonProps | CancelSplitButtonProps; +export type CancelStandardButtonProps = ButtonPropsOmittingVariant; +export type CancelSplitButtonProps = OmittedSplitButtonProps; +export type CancelButtonProps = + | CancelStandardButtonProps + | CancelSplitButtonProps; -type PrimaryStandardButtonProps = ButtonPropsOmittingVariant & +export type PrimaryStandardButtonProps = ButtonPropsOmittingVariant & ButtonPropsWithRequiredChildren & { variant?: Extract; }; -type PrimarySplitButtonProps = OmittedSplitButtonProps & { +export type PrimarySplitButtonProps = OmittedSplitButtonProps & { variant?: Extract; }; -type PrimaryButtonProps = PrimaryStandardButtonProps | PrimarySplitButtonProps; +export type PrimaryButtonProps = + | PrimaryStandardButtonProps + | PrimarySplitButtonProps; export interface FormFooterProps extends React.ComponentProps<'footer'>, diff --git a/packages/form-footer/src/index.ts b/packages/form-footer/src/index.ts index e78a13d456..86271bbc7c 100644 --- a/packages/form-footer/src/index.ts +++ b/packages/form-footer/src/index.ts @@ -5,5 +5,16 @@ export { default, default as FormFooter, } from './FormFooter'; -export { type FormFooterProps } from './FormFooter.types'; +export { + type BackButtonProps, + type BackSplitButtonProps, + type BackStandardButtonProps, + type CancelButtonProps, + type CancelSplitButtonProps, + type CancelStandardButtonProps, + type FormFooterProps, + type PrimaryButtonProps, + type PrimarySplitButtonProps, + type PrimaryStandardButtonProps, +} from './FormFooter.types'; export { getLgIds } from './utils'; From b2c8178b074a29e4218286e8c4252b9b07df5db8 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 14:21:17 -0500 Subject: [PATCH 24/51] Update WizardFooter.types.ts --- .../wizard/src/WizardFooter/WizardFooter.types.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/wizard/src/WizardFooter/WizardFooter.types.ts b/packages/wizard/src/WizardFooter/WizardFooter.types.ts index 26590f1442..1c68f78cdc 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.types.ts +++ b/packages/wizard/src/WizardFooter/WizardFooter.types.ts @@ -1,5 +1,14 @@ -import { FormFooterProps } from '@leafygreen-ui/form-footer'; +import type { + BackStandardButtonProps, + CancelStandardButtonProps, + FormFooterProps, + PrimaryStandardButtonProps, +} from '@leafygreen-ui/form-footer'; export interface WizardFooterProps extends React.ComponentProps<'footer'>, - FormFooterProps {} + FormFooterProps { + backButtonProps?: BackStandardButtonProps; + cancelButtonProps?: CancelStandardButtonProps; + primaryButtonProps: PrimaryStandardButtonProps; +} From 5419883d5890b33bd65d6a67ed36b0cccc743d24 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 14:21:29 -0500 Subject: [PATCH 25/51] adds `totalSteps` to wizard context --- packages/wizard/src/Wizard/Wizard.tsx | 7 ++++++- packages/wizard/src/WizardContext/WizardContext.tsx | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index 8a9c25f377..6ac52ea50e 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -48,7 +48,12 @@ export const Wizard = CompoundComponent( ); return ( - + {stepChildren.map((child, i) => (i === activeStep ? child : null))} ); diff --git a/packages/wizard/src/WizardContext/WizardContext.tsx b/packages/wizard/src/WizardContext/WizardContext.tsx index 06b4afba94..749f1c88f0 100644 --- a/packages/wizard/src/WizardContext/WizardContext.tsx +++ b/packages/wizard/src/WizardContext/WizardContext.tsx @@ -11,11 +11,13 @@ export interface WizardContextData { * @returns */ updateStep: (step: number) => void; + totalSteps: number; } export const WizardContext = createContext({ isWizardContext: false, activeStep: 0, + totalSteps: 0, updateStep: () => {}, }); @@ -26,6 +28,7 @@ export const WizardProvider = ({ children, activeStep, updateStep, + totalSteps, }: WizardProviderProps) => { return ( {children} From bf9cbe8f45597e26cf59fc2003c55c7a3f0b0cfe Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 15:08:43 -0500 Subject: [PATCH 26/51] fix bad merge --- packages/wizard/src/Wizard/Wizard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index 6ac52ea50e..2b2383b5e1 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -52,7 +52,6 @@ export const Wizard = CompoundComponent( activeStep={activeStep} updateStep={updateStep} totalSteps={stepChildren.length} - lgIds={lgIds} > {stepChildren.map((child, i) => (i === activeStep ? child : null))} From 1e92da756e9904cfa05320c90ccd15d417636dd4 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:50:45 -0500 Subject: [PATCH 27/51] adds LGIDs --- packages/wizard/src/Wizard/Wizard.tsx | 9 ++++++++- packages/wizard/src/Wizard/Wizard.types.ts | 4 +++- .../wizard/src/WizardContext/WizardContext.tsx | 7 +++++++ .../wizard/src/WizardFooter/WizardFooter.tsx | 4 +++- packages/wizard/src/WizardStep/WizardStep.tsx | 18 ++++++++++-------- packages/wizard/src/utils/getLgIds.ts | 6 +++++- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index 2b2383b5e1..029483a3cc 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -7,6 +7,7 @@ import { import { useControlled } from '@leafygreen-ui/hooks'; import { WizardSubComponentProperties } from '../constants'; +import { getLgIds } from '../utils/getLgIds'; import { WizardProvider } from '../WizardContext/WizardContext'; import { WizardFooter } from '../WizardFooter'; import { WizardStep } from '../WizardStep'; @@ -14,7 +15,13 @@ import { WizardStep } from '../WizardStep'; import { WizardProps } from './Wizard.types'; export const Wizard = CompoundComponent( - ({ activeStep: activeStepProp, onStepChange, children }: WizardProps) => { + ({ + activeStep: activeStepProp, + onStepChange, + children, + 'data-lgid': dataLgId, + }: WizardProps) => { + const lgIds = getLgIds(dataLgId); const stepChildren = findChildren( children, WizardSubComponentProperties.Step, diff --git a/packages/wizard/src/Wizard/Wizard.types.ts b/packages/wizard/src/Wizard/Wizard.types.ts index fba53ea8ad..64412ccd64 100644 --- a/packages/wizard/src/Wizard/Wizard.types.ts +++ b/packages/wizard/src/Wizard/Wizard.types.ts @@ -1,6 +1,8 @@ import { ReactNode } from 'react'; -export interface WizardProps { +import { LgIdProps } from '@leafygreen-ui/lib'; + +export interface WizardProps extends LgIdProps { /** * The current active step index (0-based). * If provided, the component operates in controlled mode, and any interaction will not update internal state. diff --git a/packages/wizard/src/WizardContext/WizardContext.tsx b/packages/wizard/src/WizardContext/WizardContext.tsx index 749f1c88f0..0d554438d8 100644 --- a/packages/wizard/src/WizardContext/WizardContext.tsx +++ b/packages/wizard/src/WizardContext/WizardContext.tsx @@ -1,5 +1,8 @@ import React, { createContext, PropsWithChildren, useContext } from 'react'; +import { getLgIds } from '../utils/getLgIds'; +import { GetLgIdsReturnType } from '../utils/getLgIds'; + export interface WizardContextData { isWizardContext: boolean; activeStep: number; @@ -12,6 +15,7 @@ export interface WizardContextData { */ updateStep: (step: number) => void; totalSteps: number; + lgIds: GetLgIdsReturnType; } export const WizardContext = createContext({ @@ -19,6 +23,7 @@ export const WizardContext = createContext({ activeStep: 0, totalSteps: 0, updateStep: () => {}, + lgIds: getLgIds('lg-wizard'), }); interface WizardProviderProps @@ -29,6 +34,7 @@ export const WizardProvider = ({ activeStep, updateStep, totalSteps, + lgIds = getLgIds('lg-wizard'), }: WizardProviderProps) => { return ( {children} diff --git a/packages/wizard/src/WizardFooter/WizardFooter.tsx b/packages/wizard/src/WizardFooter/WizardFooter.tsx index 5a3ae710b2..a333c1b19d 100644 --- a/packages/wizard/src/WizardFooter/WizardFooter.tsx +++ b/packages/wizard/src/WizardFooter/WizardFooter.tsx @@ -18,7 +18,8 @@ export const WizardFooter = CompoundSubComponent( className, ...rest }: WizardFooterProps) => { - const { isWizardContext, activeStep, updateStep } = useWizardContext(); + const { isWizardContext, activeStep, updateStep, lgIds } = + useWizardContext(); const { isAcknowledged, requiresAcknowledgement } = useWizardStepContext(); const isPrimaryButtonDisabled = (requiresAcknowledgement && !isAcknowledged) || @@ -48,6 +49,7 @@ export const WizardFooter = CompoundSubComponent( 0 ? { diff --git a/packages/wizard/src/WizardStep/WizardStep.tsx b/packages/wizard/src/WizardStep/WizardStep.tsx index 4be39a9c69..c40e5eab97 100644 --- a/packages/wizard/src/WizardStep/WizardStep.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.tsx @@ -17,7 +17,7 @@ import { WizardStepProvider } from './WizardStepContext'; export const WizardStep = CompoundSubComponent( ({ children, requiresAcknowledgement = false }: WizardStepProps) => { const stepId = useIdAllocator({ prefix: 'wizard-step' }); - const { isWizardContext } = useWizardContext(); + const { isWizardContext, lgIds } = useWizardContext(); if (!isWizardContext) { consoleOnce.error( @@ -36,13 +36,15 @@ export const WizardStep = CompoundSubComponent( ]); return ( - - {restChildren} - {footerChild} - +
+ + {restChildren} + {footerChild} + +
); }, { diff --git a/packages/wizard/src/utils/getLgIds.ts b/packages/wizard/src/utils/getLgIds.ts index 9590c84563..565a9db506 100644 --- a/packages/wizard/src/utils/getLgIds.ts +++ b/packages/wizard/src/utils/getLgIds.ts @@ -4,7 +4,11 @@ export const DEFAULT_LGID_ROOT = 'lg-wizard'; export const getLgIds = (root: LgIdString = DEFAULT_LGID_ROOT) => { const ids = { - root, + step: `${root}-step`, + footer: `${root}-footer`, + footerPrimaryButton: `${root}-footer-primary_button`, + footerBackButton: `${root}-footer-back_button`, + footerCancelButton: `${root}-footer-cancel_button`, } as const; return ids; }; From 0d7650c3dff91f0a53d99a8c73ea78bb440b6084 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 16:50:53 -0500 Subject: [PATCH 28/51] adds test utils --- .../wizard/src/testing/getTestUtils.spec.tsx | 346 +++++++++++++++++- packages/wizard/src/testing/getTestUtils.tsx | 119 +++++- .../wizard/src/testing/getTestUtils.types.ts | 27 +- 3 files changed, 488 insertions(+), 4 deletions(-) diff --git a/packages/wizard/src/testing/getTestUtils.spec.tsx b/packages/wizard/src/testing/getTestUtils.spec.tsx index 6e928dca63..5323afc5ab 100644 --- a/packages/wizard/src/testing/getTestUtils.spec.tsx +++ b/packages/wizard/src/testing/getTestUtils.spec.tsx @@ -1,8 +1,350 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { Wizard } from '.'; +import { Wizard } from '../Wizard'; + +import { getTestUtils } from './getTestUtils'; describe('packages/wizard/getTestUtils', () => { - test('condition', () => {}); + describe('Current Step utils', () => { + test('getCurrentStep returns the currently active step element', () => { + render( + + +
Step 1 content
+ +
+ +
Step 2 content
+ +
+
, + ); + + const { getCurrentStep } = getTestUtils(); + const step = getCurrentStep(); + expect(step).toBeInTheDocument(); + expect(step).toHaveAttribute('data-lgid', 'lg-wizard-step'); + // Verify it's the first step + expect(step).toContainElement( + document.querySelector('[data-testid="step-1"]'), + ); + expect(step).toHaveTextContent('Step 1 content'); + // Verify second step is not rendered + expect(document.querySelector('[data-testid="step-2"]')).toBeNull(); + }); + + test('getCurrentStep returns different step when activeStep changes', () => { + const { rerender } = render( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + const { getCurrentStep } = getTestUtils(); + const step1 = getCurrentStep(); + expect(step1).toHaveTextContent('Step 1'); + + rerender( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + const step2 = getCurrentStep(); + expect(step2).toHaveTextContent('Step 2'); + expect(step2).not.toBe(step1); + }); + + test('queryCurrentStep returns null when no step is present', () => { + render(
No wizard here
); + + const { queryCurrentStep } = getTestUtils(); + const step = queryCurrentStep(); + expect(step).not.toBeInTheDocument(); + }); + + test('findCurrentStep finds the current step element', async () => { + render( + + +
Step 1
+ +
+
, + ); + + const { findCurrentStep } = getTestUtils(); + const step = await findCurrentStep(); + expect(step).toBeInTheDocument(); + expect(step).toContainElement( + document.querySelector('[data-testid="step-content"]'), + ); + }); + }); + + describe('Footer utils', () => { + test('getFooter returns the correct footer element', () => { + render( + + +
Step 1
+ +
+
, + ); + + const { getFooter } = getTestUtils(); + const footer = getFooter(); + expect(footer).toBeInTheDocument(); + expect(footer.tagName).toBe('FOOTER'); + expect(footer).toHaveAttribute('data-testid', 'lg-wizard-footer'); + // Verify it contains the buttons + expect(footer).toHaveTextContent('Next Step'); + expect(footer).toHaveTextContent('Cancel Action'); + }); + + test('queryFooter returns null when footer is not present', () => { + render(
No wizard here
); + + const { queryFooter } = getTestUtils(); + const footer = queryFooter(); + expect(footer).not.toBeInTheDocument(); + }); + + test('findFooter finds the footer element', async () => { + render( + + +
Step 1
+ +
+
, + ); + + const { findFooter } = getTestUtils(); + const footer = await findFooter(); + expect(footer).toBeInTheDocument(); + expect(footer).toHaveTextContent('Submit'); + }); + }); + + describe('Button utils', () => { + test('getPrimaryButton returns the correct primary button element', () => { + render( + + +
Step 1
+ +
+
, + ); + + const { getPrimaryButton } = getTestUtils(); + const primaryButton = getPrimaryButton(); + expect(primaryButton).toBeInTheDocument(); + expect(primaryButton.tagName).toBe('BUTTON'); + expect(primaryButton).toHaveAttribute( + 'data-testid', + 'lg-wizard-footer-primary_button', + ); + expect(primaryButton).toHaveTextContent('Next Step'); + }); + + test('queryPrimaryButton returns null when button is not present', () => { + render(
No wizard here
); + + const { queryPrimaryButton } = getTestUtils(); + const button = queryPrimaryButton(); + expect(button).not.toBeInTheDocument(); + }); + + test('findPrimaryButton finds the primary button element', async () => { + render( + + +
Step 1
+ +
+
, + ); + + const { findPrimaryButton } = getTestUtils(); + const button = await findPrimaryButton(); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('Continue'); + }); + + test('getBackButton returns the correct back button element', () => { + render( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + const { getBackButton } = getTestUtils(); + const backButton = getBackButton(); + expect(backButton).toBeInTheDocument(); + expect(backButton.tagName).toBe('BUTTON'); + expect(backButton).toHaveAttribute( + 'data-testid', + 'lg-wizard-footer-back_button', + ); + expect(backButton).toHaveTextContent('Go Back'); + }); + + test('queryBackButton returns null when back button is not present', () => { + render( + + +
Step 1
+ +
+
, + ); + + const { queryBackButton } = getTestUtils(); + const button = queryBackButton(); + expect(button).not.toBeInTheDocument(); + }); + + test('findBackButton finds the back button element', async () => { + render( + + +
Step 1
+ +
+ +
Step 2
+ +
+
, + ); + + const { findBackButton } = getTestUtils(); + const button = await findBackButton(); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('Previous'); + }); + + test('getCancelButton returns the correct cancel button element', () => { + render( + + +
Step 1
+ +
+
, + ); + + const { getCancelButton } = getTestUtils(); + const cancelButton = getCancelButton(); + expect(cancelButton).toBeInTheDocument(); + expect(cancelButton.tagName).toBe('BUTTON'); + expect(cancelButton).toHaveAttribute( + 'data-testid', + 'lg-wizard-footer-cancel_button', + ); + expect(cancelButton).toHaveTextContent('Cancel Process'); + }); + + test('queryCancelButton returns null when cancel button is not present', () => { + render( + + +
Step 1
+ +
+
, + ); + + const { queryCancelButton } = getTestUtils(); + const button = queryCancelButton(); + expect(button).not.toBeInTheDocument(); + }); + + test('findCancelButton finds the cancel button element', async () => { + render( + + +
Step 1
+ +
+
, + ); + + const { findCancelButton } = getTestUtils(); + const button = await findCancelButton(); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('Abort'); + }); + }); + + describe('with custom lgId', () => { + test('uses custom lgId when provided', () => { + render( + + +
Step 1
+ +
+
, + ); + + const { getCurrentStep, getFooter, getPrimaryButton } = + getTestUtils('custom-wizard'); + + const step = getCurrentStep(); + expect(step).toBeInTheDocument(); + expect(step).toHaveAttribute('data-lgid', 'custom-wizard-step'); + + const footer = getFooter(); + expect(footer).toBeInTheDocument(); + expect(footer).toHaveAttribute('data-testid', 'custom-wizard-footer'); + + const button = getPrimaryButton(); + expect(button).toBeInTheDocument(); + expect(button).toHaveAttribute( + 'data-testid', + 'custom-wizard-footer-primary_button', + ); + }); + }); }); diff --git a/packages/wizard/src/testing/getTestUtils.tsx b/packages/wizard/src/testing/getTestUtils.tsx index ad89a6e99d..7647d84b62 100644 --- a/packages/wizard/src/testing/getTestUtils.tsx +++ b/packages/wizard/src/testing/getTestUtils.tsx @@ -1,3 +1,5 @@ +import { screen } from '@testing-library/react'; + import { findByLgId, getByLgId, queryByLgId } from '@lg-tools/test-harnesses'; import { LgIdString } from '@leafygreen-ui/lib'; @@ -11,5 +13,120 @@ export const getTestUtils = ( ): TestUtilsReturnType => { const lgIds = getLgIds(lgId); - return {}; + /** + * @returns a promise that resolves to the current WizardStep element using the `data-lgid` data attribute. + * The promise is rejected if no elements match or if more than one match is found. + */ + const findCurrentStep = () => findByLgId!(lgIds.step); + + /** + * @returns the current WizardStep element using the `data-lgid` data attribute. + * Will throw if no elements match or if more than one match is found. + */ + const getCurrentStep = () => getByLgId!(lgIds.step); + + /** + * @returns the current WizardStep element using the `data-lgid` data attribute or `null` if no elements match. + * Will throw if more than one match is found. + */ + const queryCurrentStep = () => queryByLgId!(lgIds.step); + + /** + * @returns a promise that resolves to the WizardFooter element using the `data-testid` data attribute. + * The promise is rejected if no elements match or if more than one match is found. + */ + const findFooter = () => screen.findByTestId(lgIds.footer); + + /** + * @returns the WizardFooter element using the `data-testid` data attribute. + * Will throw if no elements match or if more than one match is found. + */ + const getFooter = () => screen.getByTestId(lgIds.footer); + + /** + * @returns the WizardFooter element using the `data-testid` data attribute or `null` if no elements match. + * Will throw if more than one match is found. + */ + const queryFooter = () => screen.queryByTestId(lgIds.footer); + + /** + * @returns the primary button element using the `data-testid` data attribute. + * Will throw if no elements match or if more than one match is found. + */ + const getPrimaryButton = () => + screen.getByTestId(lgIds.footerPrimaryButton); + + /** + * @returns the primary button element using the `data-testid` data attribute or `null` if no elements match. + * Will throw if more than one match is found. + */ + const queryPrimaryButton = () => + screen.queryByTestId(lgIds.footerPrimaryButton); + + /** + * @returns a promise that resolves to the primary button element using the `data-testid` data attribute. + * The promise is rejected if no elements match or if more than one match is found. + */ + const findPrimaryButton = () => + screen.findByTestId(lgIds.footerPrimaryButton); + + /** + * @returns the back button element using the `data-testid` data attribute. + * Will throw if no elements match or if more than one match is found. + */ + const getBackButton = () => + screen.getByTestId(lgIds.footerBackButton); + + /** + * @returns the back button element using the `data-testid` data attribute or `null` if no elements match. + * Will throw if more than one match is found. + */ + const queryBackButton = () => + screen.queryByTestId(lgIds.footerBackButton); + + /** + * @returns a promise that resolves to the back button element using the `data-testid` data attribute. + * The promise is rejected if no elements match or if more than one match is found. + */ + const findBackButton = () => + screen.findByTestId(lgIds.footerBackButton); + + /** + * @returns the cancel button element using the `data-testid` data attribute. + * Will throw if no elements match or if more than one match is found. + */ + const getCancelButton = () => + screen.getByTestId(lgIds.footerCancelButton); + + /** + * @returns the cancel button element using the `data-testid` data attribute or `null` if no elements match. + * Will throw if more than one match is found. + */ + const queryCancelButton = () => + screen.queryByTestId(lgIds.footerCancelButton); + + /** + * @returns a promise that resolves to the cancel button element using the `data-testid` data attribute. + * The promise is rejected if no elements match or if more than one match is found. + */ + const findCancelButton = () => + screen.findByTestId(lgIds.footerCancelButton); + + return { + findCurrentStep, + getCurrentStep, + queryCurrentStep, + findFooter, + getFooter, + queryFooter, + getPrimaryButton, + queryPrimaryButton, + findPrimaryButton, + getBackButton, + queryBackButton, + findBackButton, + getCancelButton, + queryCancelButton, + findCancelButton, + }; }; diff --git a/packages/wizard/src/testing/getTestUtils.types.ts b/packages/wizard/src/testing/getTestUtils.types.ts index 4b2df87c73..5c16c95026 100644 --- a/packages/wizard/src/testing/getTestUtils.types.ts +++ b/packages/wizard/src/testing/getTestUtils.types.ts @@ -1 +1,26 @@ -export interface TestUtilsReturnType {} +export interface TestUtilsReturnType { + // Current Step utils + findCurrentStep: () => Promise; + getCurrentStep: () => HTMLDivElement; + queryCurrentStep: () => HTMLDivElement | null; + + // Footer utils + findFooter: () => Promise; + getFooter: () => HTMLElement; + queryFooter: () => HTMLElement | null; + + // Primary Button utils + getPrimaryButton: () => HTMLButtonElement; + queryPrimaryButton: () => HTMLButtonElement | null; + findPrimaryButton: () => Promise; + + // Back Button utils + getBackButton: () => HTMLButtonElement; + queryBackButton: () => HTMLButtonElement | null; + findBackButton: () => Promise; + + // Cancel Button utils + getCancelButton: () => HTMLButtonElement; + queryCancelButton: () => HTMLButtonElement | null; + findCancelButton: () => Promise; +} From 35274d85f61a8d79486047c5909a0e464b089623 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Fri, 21 Nov 2025 18:54:40 -0500 Subject: [PATCH 29/51] lint --- packages/wizard/src/testing/getTestUtils.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/wizard/src/testing/getTestUtils.tsx b/packages/wizard/src/testing/getTestUtils.tsx index 7647d84b62..6eb64f61cc 100644 --- a/packages/wizard/src/testing/getTestUtils.tsx +++ b/packages/wizard/src/testing/getTestUtils.tsx @@ -1,6 +1,5 @@ -import { screen } from '@testing-library/react'; - import { findByLgId, getByLgId, queryByLgId } from '@lg-tools/test-harnesses'; +import { screen } from '@testing-library/react'; import { LgIdString } from '@leafygreen-ui/lib'; From c77268a36f229b9efeba0e85a768083bb588d9de Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 15:09:32 -0500 Subject: [PATCH 30/51] fix bad merge --- packages/wizard/src/Wizard/Wizard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index 029483a3cc..cb67c24167 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -59,6 +59,7 @@ export const Wizard = CompoundComponent( activeStep={activeStep} updateStep={updateStep} totalSteps={stepChildren.length} + lgIds={lgIds} > {stepChildren.map((child, i) => (i === activeStep ? child : null))} From 649c699f9bacda362fd0703b109702e7b9396dd1 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 16:27:04 -0500 Subject: [PATCH 31/51] removes Step test utils --- packages/wizard/src/WizardStep/WizardStep.tsx | 18 ++-- .../wizard/src/testing/getTestUtils.spec.tsx | 100 +----------------- packages/wizard/src/testing/getTestUtils.tsx | 46 +++----- .../wizard/src/testing/getTestUtils.types.ts | 5 - 4 files changed, 22 insertions(+), 147 deletions(-) diff --git a/packages/wizard/src/WizardStep/WizardStep.tsx b/packages/wizard/src/WizardStep/WizardStep.tsx index c40e5eab97..4be39a9c69 100644 --- a/packages/wizard/src/WizardStep/WizardStep.tsx +++ b/packages/wizard/src/WizardStep/WizardStep.tsx @@ -17,7 +17,7 @@ import { WizardStepProvider } from './WizardStepContext'; export const WizardStep = CompoundSubComponent( ({ children, requiresAcknowledgement = false }: WizardStepProps) => { const stepId = useIdAllocator({ prefix: 'wizard-step' }); - const { isWizardContext, lgIds } = useWizardContext(); + const { isWizardContext } = useWizardContext(); if (!isWizardContext) { consoleOnce.error( @@ -36,15 +36,13 @@ export const WizardStep = CompoundSubComponent( ]); return ( -
- - {restChildren} - {footerChild} - -
+ + {restChildren} + {footerChild} + ); }, { diff --git a/packages/wizard/src/testing/getTestUtils.spec.tsx b/packages/wizard/src/testing/getTestUtils.spec.tsx index 5323afc5ab..04ff5b1764 100644 --- a/packages/wizard/src/testing/getTestUtils.spec.tsx +++ b/packages/wizard/src/testing/getTestUtils.spec.tsx @@ -6,97 +6,6 @@ import { Wizard } from '../Wizard'; import { getTestUtils } from './getTestUtils'; describe('packages/wizard/getTestUtils', () => { - describe('Current Step utils', () => { - test('getCurrentStep returns the currently active step element', () => { - render( - - -
Step 1 content
- -
- -
Step 2 content
- -
-
, - ); - - const { getCurrentStep } = getTestUtils(); - const step = getCurrentStep(); - expect(step).toBeInTheDocument(); - expect(step).toHaveAttribute('data-lgid', 'lg-wizard-step'); - // Verify it's the first step - expect(step).toContainElement( - document.querySelector('[data-testid="step-1"]'), - ); - expect(step).toHaveTextContent('Step 1 content'); - // Verify second step is not rendered - expect(document.querySelector('[data-testid="step-2"]')).toBeNull(); - }); - - test('getCurrentStep returns different step when activeStep changes', () => { - const { rerender } = render( - - -
Step 1
- -
- -
Step 2
- -
-
, - ); - - const { getCurrentStep } = getTestUtils(); - const step1 = getCurrentStep(); - expect(step1).toHaveTextContent('Step 1'); - - rerender( - - -
Step 1
- -
- -
Step 2
- -
-
, - ); - - const step2 = getCurrentStep(); - expect(step2).toHaveTextContent('Step 2'); - expect(step2).not.toBe(step1); - }); - - test('queryCurrentStep returns null when no step is present', () => { - render(
No wizard here
); - - const { queryCurrentStep } = getTestUtils(); - const step = queryCurrentStep(); - expect(step).not.toBeInTheDocument(); - }); - - test('findCurrentStep finds the current step element', async () => { - render( - - -
Step 1
- -
-
, - ); - - const { findCurrentStep } = getTestUtils(); - const step = await findCurrentStep(); - expect(step).toBeInTheDocument(); - expect(step).toContainElement( - document.querySelector('[data-testid="step-content"]'), - ); - }); - }); - describe('Footer utils', () => { test('getFooter returns the correct footer element', () => { render( @@ -320,7 +229,7 @@ describe('packages/wizard/getTestUtils', () => { describe('with custom lgId', () => { test('uses custom lgId when provided', () => { render( - +
Step 1
@@ -328,12 +237,7 @@ describe('packages/wizard/getTestUtils', () => {
, ); - const { getCurrentStep, getFooter, getPrimaryButton } = - getTestUtils('custom-wizard'); - - const step = getCurrentStep(); - expect(step).toBeInTheDocument(); - expect(step).toHaveAttribute('data-lgid', 'custom-wizard-step'); + const { getFooter, getPrimaryButton } = getTestUtils('lg-custom-wizard'); const footer = getFooter(); expect(footer).toBeInTheDocument(); diff --git a/packages/wizard/src/testing/getTestUtils.tsx b/packages/wizard/src/testing/getTestUtils.tsx index 6eb64f61cc..3cf4d1ec20 100644 --- a/packages/wizard/src/testing/getTestUtils.tsx +++ b/packages/wizard/src/testing/getTestUtils.tsx @@ -1,5 +1,4 @@ import { findByLgId, getByLgId, queryByLgId } from '@lg-tools/test-harnesses'; -import { screen } from '@testing-library/react'; import { LgIdString } from '@leafygreen-ui/lib'; @@ -12,109 +11,88 @@ export const getTestUtils = ( ): TestUtilsReturnType => { const lgIds = getLgIds(lgId); - /** - * @returns a promise that resolves to the current WizardStep element using the `data-lgid` data attribute. - * The promise is rejected if no elements match or if more than one match is found. - */ - const findCurrentStep = () => findByLgId!(lgIds.step); - - /** - * @returns the current WizardStep element using the `data-lgid` data attribute. - * Will throw if no elements match or if more than one match is found. - */ - const getCurrentStep = () => getByLgId!(lgIds.step); - - /** - * @returns the current WizardStep element using the `data-lgid` data attribute or `null` if no elements match. - * Will throw if more than one match is found. - */ - const queryCurrentStep = () => queryByLgId!(lgIds.step); - /** * @returns a promise that resolves to the WizardFooter element using the `data-testid` data attribute. * The promise is rejected if no elements match or if more than one match is found. */ - const findFooter = () => screen.findByTestId(lgIds.footer); + const findFooter = () => findByLgId!(lgIds.footer); /** * @returns the WizardFooter element using the `data-testid` data attribute. * Will throw if no elements match or if more than one match is found. */ - const getFooter = () => screen.getByTestId(lgIds.footer); + const getFooter = () => getByLgId!(lgIds.footer); /** * @returns the WizardFooter element using the `data-testid` data attribute or `null` if no elements match. * Will throw if more than one match is found. */ - const queryFooter = () => screen.queryByTestId(lgIds.footer); + const queryFooter = () => queryByLgId!(lgIds.footer); /** * @returns the primary button element using the `data-testid` data attribute. * Will throw if no elements match or if more than one match is found. */ const getPrimaryButton = () => - screen.getByTestId(lgIds.footerPrimaryButton); + getByLgId!(lgIds.footerPrimaryButton); /** * @returns the primary button element using the `data-testid` data attribute or `null` if no elements match. * Will throw if more than one match is found. */ const queryPrimaryButton = () => - screen.queryByTestId(lgIds.footerPrimaryButton); + queryByLgId!(lgIds.footerPrimaryButton); /** * @returns a promise that resolves to the primary button element using the `data-testid` data attribute. * The promise is rejected if no elements match or if more than one match is found. */ const findPrimaryButton = () => - screen.findByTestId(lgIds.footerPrimaryButton); + findByLgId!(lgIds.footerPrimaryButton); /** * @returns the back button element using the `data-testid` data attribute. * Will throw if no elements match or if more than one match is found. */ const getBackButton = () => - screen.getByTestId(lgIds.footerBackButton); + getByLgId!(lgIds.footerBackButton); /** * @returns the back button element using the `data-testid` data attribute or `null` if no elements match. * Will throw if more than one match is found. */ const queryBackButton = () => - screen.queryByTestId(lgIds.footerBackButton); + queryByLgId!(lgIds.footerBackButton); /** * @returns a promise that resolves to the back button element using the `data-testid` data attribute. * The promise is rejected if no elements match or if more than one match is found. */ const findBackButton = () => - screen.findByTestId(lgIds.footerBackButton); + findByLgId!(lgIds.footerBackButton); /** * @returns the cancel button element using the `data-testid` data attribute. * Will throw if no elements match or if more than one match is found. */ const getCancelButton = () => - screen.getByTestId(lgIds.footerCancelButton); + getByLgId!(lgIds.footerCancelButton); /** * @returns the cancel button element using the `data-testid` data attribute or `null` if no elements match. * Will throw if more than one match is found. */ const queryCancelButton = () => - screen.queryByTestId(lgIds.footerCancelButton); + queryByLgId!(lgIds.footerCancelButton); /** * @returns a promise that resolves to the cancel button element using the `data-testid` data attribute. * The promise is rejected if no elements match or if more than one match is found. */ const findCancelButton = () => - screen.findByTestId(lgIds.footerCancelButton); + findByLgId!(lgIds.footerCancelButton); return { - findCurrentStep, - getCurrentStep, - queryCurrentStep, findFooter, getFooter, queryFooter, diff --git a/packages/wizard/src/testing/getTestUtils.types.ts b/packages/wizard/src/testing/getTestUtils.types.ts index 5c16c95026..359c941c4d 100644 --- a/packages/wizard/src/testing/getTestUtils.types.ts +++ b/packages/wizard/src/testing/getTestUtils.types.ts @@ -1,9 +1,4 @@ export interface TestUtilsReturnType { - // Current Step utils - findCurrentStep: () => Promise; - getCurrentStep: () => HTMLDivElement; - queryCurrentStep: () => HTMLDivElement | null; - // Footer utils findFooter: () => Promise; getFooter: () => HTMLElement; From aa6aedb087b2ced164d1c22fe6a289e0b759ad49 Mon Sep 17 00:00:00 2001 From: Adam Michael Thompson Date: Mon, 24 Nov 2025 16:30:03 -0500 Subject: [PATCH 32/51] add layout comments --- packages/wizard/src/Wizard/Wizard.tsx | 4 ++++ packages/wizard/src/WizardStep/WizardStep.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/wizard/src/Wizard/Wizard.tsx b/packages/wizard/src/Wizard/Wizard.tsx index cb67c24167..c1d2a7a4ed 100644 --- a/packages/wizard/src/Wizard/Wizard.tsx +++ b/packages/wizard/src/Wizard/Wizard.tsx @@ -54,6 +54,10 @@ export const Wizard = CompoundComponent( [setActiveStep, stepChildren.length], ); + /** + * NB: We're intentionally do _not_ wrap the `Wizard` (or `WizardStep`) component in a container element. + * This is done to ensure the Wizard is flexible, and can be rendered in any containing layout. + */ return ( Date: Mon, 24 Nov 2025 18:04:43 -0500 Subject: [PATCH 33/51] form-footer lgids --- .changeset/form-footer-lgids.md | 5 +++++ packages/form-footer/src/FormFooter.tsx | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/form-footer-lgids.md diff --git a/.changeset/form-footer-lgids.md b/.changeset/form-footer-lgids.md new file mode 100644 index 0000000000..df3b330741 --- /dev/null +++ b/.changeset/form-footer-lgids.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/form-footer': patch +--- + +Passes `data-lgid` to the root `footer` element diff --git a/packages/form-footer/src/FormFooter.tsx b/packages/form-footer/src/FormFooter.tsx index 390583b014..91aab13658 100644 --- a/packages/form-footer/src/FormFooter.tsx +++ b/packages/form-footer/src/FormFooter.tsx @@ -47,6 +47,7 @@ export default function FormFooter({