diff --git a/packages/compass-components/src/hooks/use-theme.tsx b/packages/compass-components/src/hooks/use-theme.tsx index d68c3bb019e..99868fd49f6 100644 --- a/packages/compass-components/src/hooks/use-theme.tsx +++ b/packages/compass-components/src/hooks/use-theme.tsx @@ -17,7 +17,7 @@ const ThemeProvider = ({ children, theme, }: { - children: React.ReactChildren; + children: React.ReactNode; theme: ThemeState; }): React.ReactElement => { return ( diff --git a/packages/compass-components/src/hooks/use-toast.spec.tsx b/packages/compass-components/src/hooks/use-toast.spec.tsx new file mode 100644 index 00000000000..1c041f743eb --- /dev/null +++ b/packages/compass-components/src/hooks/use-toast.spec.tsx @@ -0,0 +1,107 @@ +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { expect } from 'chai'; +import React from 'react'; +import sinon from 'sinon'; + +import { ToastArea, ToastVariant, useToast } from '..'; + +const OpenToastButton = ({ + namespace, + id, + title, + variant, + timeout, + body, +}: { + namespace: string; + variant: ToastVariant; + id: string; + title: string; + timeout?: number; + body?: string; +}) => { + const { openToast } = useToast(namespace); + return ( + + ); +}; + +const CloseToastButton = ({ + namespace, + id, +}: { + namespace: string; + id: string; +}) => { + const { closeToast } = useToast(namespace); + return ; +}; + +describe('useToast', function () { + afterEach(cleanup); + + it('opens and closes a toast', async function () { + render( + + + + + ); + + fireEvent.click(screen.getByText('Open Toast')); + + await screen.findByText('My Toast'); + screen.getByText('Toast body'); + + fireEvent.click(screen.getByText('Close Toast')); + + expect(screen.queryByText('My Toast')).to.not.exist; + }); + + describe('with timeout', function () { + let clock; + + beforeEach(function () { + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + }); + + it('closes a toast after timeout expires', async function () { + render( + + + + ); + + fireEvent.click(screen.getByText('Open Toast')); + + await screen.findByText('My Toast'); + + clock.tick(2000); + + await screen.findByText('My Toast'); + + clock.tick(3001); + + expect(screen.queryByText('My Toast')).to.not.exist; + }); + }); +}); diff --git a/packages/compass-components/src/hooks/use-toast.tsx b/packages/compass-components/src/hooks/use-toast.tsx new file mode 100644 index 00000000000..3437b14018c --- /dev/null +++ b/packages/compass-components/src/hooks/use-toast.tsx @@ -0,0 +1,164 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; +import type { ToastVariant } from '..'; +import { css } from '..'; +import { Toast } from '..'; + +type ToastProperties = { + title?: React.ReactNode; + body: React.ReactNode; + variant: ToastVariant; + progress?: number; + timeout?: number; +}; + +interface ToastActions { + openToast: (id: string, toastProperties: ToastProperties) => void; + closeToast: (id: string) => void; +} + +const ToastContext = createContext({ + openToast: () => { + // + }, + closeToast: () => { + // + }, +}); + +const toastStyles = css({ + button: { + position: 'absolute', + }, +}); + +/** + * @example + * + * ``` + * const MyButton = () => { + * const { openToast } = useToast('namespace'); + * return