diff --git a/packages/components/package.json b/packages/components/package.json index e80910e..582a690 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -89,5 +89,5 @@ "typecheck": "tsc --emitDeclarationOnly false --noEmit" }, "types": "dist/index.d.ts", - "version": "3.8.0" + "version": "3.9.0" } diff --git a/packages/components/src/components/AlertWithDetails/AlertWithDetails.story.tsx b/packages/components/src/components/AlertWithDetails/AlertWithDetails.story.tsx new file mode 100644 index 0000000..d774e23 --- /dev/null +++ b/packages/components/src/components/AlertWithDetails/AlertWithDetails.story.tsx @@ -0,0 +1,37 @@ +import { Meta, StoryFn } from '@storybook/react'; +import React, { useState } from 'react'; + +import { AlertWithDetails, AlertWithDetailsProps } from './AlertWithDetails'; + +const variants: string[] = ['error', 'warning', 'info', 'success']; + +const meta: Meta = { + title: 'AlertWithDetails', + component: AlertWithDetails, + parameters: { + controls: { + exclude: ['onRemove'], + }, + }, + args: { + details: '{{some json example}}', + variant: 'error', + title: 'Title', + children: 'Some content', + }, + argTypes: { + details: { control: 'textarea' }, + title: { control: 'text' }, + children: { control: 'text' }, + variant: { + control: { type: 'select', options: variants }, + }, + }, +}; + +export const Basic: StoryFn = ({ ...args }: AlertWithDetailsProps) => { + const [show, setShow] = useState(true); + return
{show && setShow(false)} />}
; +}; + +export default meta; diff --git a/packages/components/src/components/AlertWithDetails/AlertWithDetails.styles.ts b/packages/components/src/components/AlertWithDetails/AlertWithDetails.styles.ts new file mode 100644 index 0000000..fefddec --- /dev/null +++ b/packages/components/src/components/AlertWithDetails/AlertWithDetails.styles.ts @@ -0,0 +1,19 @@ +import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; + +/** + * Styles + */ +export const getStyles = (theme: GrafanaTheme2) => { + return { + details: css` + padding: 0; + margin: ${theme.spacing(1, 0, 0)}; + font-size: ${theme.typography.bodySmall.fontSize}; + `, + detailsContent: css` + padding: ${theme.spacing(1, 0)}; + font-size: ${theme.typography.bodySmall.fontSize}; + `, + }; +}; diff --git a/packages/components/src/components/AlertWithDetails/AlertWithDetails.test.tsx b/packages/components/src/components/AlertWithDetails/AlertWithDetails.test.tsx new file mode 100644 index 0000000..04f7fa6 --- /dev/null +++ b/packages/components/src/components/AlertWithDetails/AlertWithDetails.test.tsx @@ -0,0 +1,86 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { getJestSelectors } from '@volkovlabs/jest-selectors'; +import React from 'react'; + +import { AlertWithDetails } from './AlertWithDetails'; +import { TEST_IDS } from '../../constants'; + +/** + * Properties + */ +type Props = React.ComponentProps; + +describe('AlertWithDetails', () => { + /** + * Selectors + */ + const getSelectors = getJestSelectors(TEST_IDS.alertWithDetails); + const selectors = getSelectors(screen); + + /** + * Get Component + */ + const getComponent = (props: Partial) => { + return ; + }; + + it('Should show Alert box with details', () => { + const onChange = jest.fn(); + + render(getComponent({ details: 'Test Error', title: 'Test title', variant: 'error', onRemove: onChange })); + expect(selectors.root()).toBeVisible(); + }); + + it('Should show Alert box details for errors', () => { + const onChange = jest.fn(); + + render( + getComponent({ + details: 'Test Error text in details', + title: 'Test title', + variant: 'error', + onRemove: onChange, + }) + ); + expect(selectors.root()).toBeVisible(); + expect(selectors.detailsSectionHeader()).toBeVisible(); + expect(selectors.detailsSectionContent(true)).not.toBeInTheDocument(); + + fireEvent.click(selectors.detailsSectionHeader()); + expect(selectors.detailsSectionContent()).toBeVisible(); + expect(selectors.detailsSectionContent()).toHaveTextContent('Test Error text in details'); + }); + + it('Should show Alert box as alert', () => { + render( + getComponent({ + details: '', + title: 'Test title', + children: 'alert content', + variant: 'error', + }) + ); + + expect(selectors.root()).toBeVisible(); + expect(selectors.detailsSectionHeader(true)).not.toBeInTheDocument(); + expect(selectors.detailsSectionContent(true)).not.toBeInTheDocument(); + + expect(selectors.root()).toHaveTextContent('alert content'); + }); + + it('Should show Alert with empty content', () => { + render( + getComponent({ + details: '', + title: '', + children: '', + variant: 'error', + }) + ); + + expect(selectors.root()).toBeVisible(); + expect(selectors.detailsSectionHeader(true)).not.toBeInTheDocument(); + expect(selectors.detailsSectionContent(true)).not.toBeInTheDocument(); + expect(selectors.root()).toHaveTextContent(''); + }); +}); diff --git a/packages/components/src/components/AlertWithDetails/AlertWithDetails.tsx b/packages/components/src/components/AlertWithDetails/AlertWithDetails.tsx new file mode 100644 index 0000000..905c1e5 --- /dev/null +++ b/packages/components/src/components/AlertWithDetails/AlertWithDetails.tsx @@ -0,0 +1,51 @@ +import { Alert, useStyles2 } from '@grafana/ui'; +import React, { ReactNode, useState } from 'react'; + +import { TEST_IDS } from '../../constants'; +import { CollapsableSection } from '../CollapsableSection'; +import { getStyles } from './AlertWithDetails.styles'; + +/** + * Properties + */ +export type AlertWithDetailsProps = { + details?: string; + variant: 'success' | 'warning' | 'error' | 'info'; + title: string; + onRemove?: (event: React.MouseEvent) => void; + children?: ReactNode; +}; + +/** + * Alert With Details + */ +export const AlertWithDetails: React.FC = ({ children, details, variant, title, onRemove }) => { + /** + * Styles + */ + const styles = useStyles2(getStyles); + + /** + * States + */ + const [open, setOpen] = useState(false); + + return ( + + {children} + {!!details && ( + setOpen(!open)} + headerDataTestId={TEST_IDS.alertWithDetails.detailsSectionHeader.selector()} + contentDataTestId={TEST_IDS.alertWithDetails.detailsSectionContent.selector()} + > + {details} + + )} + + ); +}; diff --git a/packages/components/src/components/AlertWithDetails/index.ts b/packages/components/src/components/AlertWithDetails/index.ts new file mode 100644 index 0000000..61e75fd --- /dev/null +++ b/packages/components/src/components/AlertWithDetails/index.ts @@ -0,0 +1 @@ +export * from './AlertWithDetails'; diff --git a/packages/components/src/components/index.ts b/packages/components/src/components/index.ts index b99576b..8cb5ce1 100644 --- a/packages/components/src/components/index.ts +++ b/packages/components/src/components/index.ts @@ -1,3 +1,4 @@ +export * from './AlertWithDetails'; export * from './AutosizeCodeEditor'; export * from './CollapsableSection'; export * from './Collapse'; diff --git a/packages/components/src/constants/tests.ts b/packages/components/src/constants/tests.ts index 1566104..291853a 100644 --- a/packages/components/src/constants/tests.ts +++ b/packages/components/src/constants/tests.ts @@ -38,4 +38,9 @@ export const TEST_IDS = { loadingMessage: createSelector('data-testid payload-editor loading-message'), errorMessage: createSelector('data-testid payload-editor error-message'), }, + alertWithDetails: { + root: createSelector('data-testid alert-with-details root'), + detailsSectionHeader: createSelector('data-testid alert-with-details details-section-header'), + detailsSectionContent: createSelector('data-testid alert-with-details details-section-content'), + }, };