Skip to content

Commit

Permalink
feat(pixels): add alert component and fix some issues
Browse files Browse the repository at this point in the history
  • Loading branch information
fighter3005 committed Jan 20, 2025
1 parent 1e552c9 commit 09dbebf
Show file tree
Hide file tree
Showing 9 changed files with 2,820 additions and 5 deletions.
5 changes: 5 additions & 0 deletions packages/pixels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
"import": "./dist/Accordion/index.js",
"require": "./dist/Accordion/index.cjs"
},
"./Alert": {
"types": "./dist/Alert/index.d.ts",
"import": "./dist/Alert/index.js",
"require": "./dist/Alert/index.cjs"
},
"./Avatar": {
"types": "./dist/Avatar/index.d.ts",
"import": "./dist/Avatar/index.js",
Expand Down
162 changes: 162 additions & 0 deletions packages/pixels/src/Alert/Alert.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import type { Meta, StoryObj } from '@storybook/react';
import type { AlertProps } from './Alert';

import { useArgs } from '@storybook/preview-api';

import { cn } from '@fuf-stack/pixel-utils';

import Alert, { alertVariants } from './Alert';

const meta: Meta<typeof Alert> = {
title: 'pixels/Alert',
component: Alert,
};

const colors = [...Object.keys(alertVariants.variants.color)];

export default meta;
type Story = StoryObj<typeof Alert>;

export const Default: Story = {
args: {},
};

export const TitleOnly: Story = {
args: {
title: <span>System Notification</span>,
},
};

export const ChildrenOnly: Story = {
args: {
children: 'Your attention is required for this matter.',
},
};

export const AllColorsAndVariants: Story = {
render: (args) => (
<>
{colors.map((color) => (
<div key={color} className="mb-12">
<h2 className="mb-4 text-lg font-bold">{color}</h2>
{Object.keys(alertVariants.variants.variant).map((variant) => (
<div key={`${color}-${variant}`} className="mb-6">
<div className="mb-2 text-sm text-foreground">{variant}</div>
<Alert
color={color as AlertProps['color']}
variant={variant as AlertProps['variant']}
{...args}
/>
</div>
))}
</div>
))}
</>
),
args: {
title: "Something's Up",
children: 'A message of varying importance has been detected.',
},
};

export const NoIcon: Story = {
render: (args) => (
<>
{Object.keys(alertVariants.variants.variant).map((variant) => (
<div key={variant} className="mb-12">
<div>{variant}</div>
<Alert variant={variant as AlertProps['variant']} {...args} />
</div>
))}
</>
),
args: {
title: 'Alert',
children: 'More details regarding the alert with no icon.',
showIcon: false,
},
};

export const Endcontent: Story = {
args: {
title: 'Message from Our Team',
children: 'We have some important news to share with you.',
endContent: <button type="button">End Content</button>,
},
};

export const Closable: Story = {
render: (args) => (
<>
{Object.keys(alertVariants.variants.variant).map((variant) => (
<div key={variant} className="mb-12">
<div>{variant}</div>
<Alert variant={variant as AlertProps['variant']} {...args} />
</div>
))}
</>
),
args: {
title: 'Alert: [Close to dismiss]',
children: 'X marks the spot (to close).',
isClosable: true,
color: 'info',
},
};

export const AllColorsWithShowMoreButton: Story = {
render: function Render(args) {
const [{ showMore }, updateArgs] = useArgs();

const toggleShowMore = () => {
updateArgs({ showMore: !showMore });
};

return (
<>
{colors.map((color) => (
<div key={color} className="mb-12">
<div>{color}</div>
<Alert
color={color as AlertProps['color']}
{...args}
endContent={
<button
className="ml-2 mt-2 rounded border px-2 py-1 text-xs"
type="button"
onClick={toggleShowMore}
>
{showMore ? <>Show Less Info</> : <>Show More Info</>}
</button>
}
>
Please take a moment to review the following information.
{showMore && (
<div className={cn('mt-2 border-t pt-4 text-sm')}>
<div className="ml-2">
Our team of highly trained monkeys has detected a minor
issue. Don&apos;t worry, it&apos;s not the end of the world
(but we can&apos;t promise anything). <br /> Seriously
though, please review the following info: We&apos;ve got
some stuff to tell you, and it&apos;s probably going to be
boring. But hey, at least you&apos;ll know what&apos;s up!
</div>
</div>
)}
</Alert>
</div>
))}
</>
);
},
args: {
title: 'Alert Issued',
},
};

export const SpecialFullWidth: Story = {
args: {
showIcon: false,
className: 'w-screen',
},
};
9 changes: 9 additions & 0 deletions packages/pixels/src/Alert/Alert.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { describe } from 'vitest';

import storySnapshots from '@repo/storybook-config/story-snapshots';

import * as stories from './Alert.stories';

describe('Story Snapshots', () => {
storySnapshots(stories);
});
148 changes: 148 additions & 0 deletions packages/pixels/src/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import type { TVClassName, TVProps } from '@fuf-stack/pixel-utils';
import type { AlertProps as NextAlertProps } from '@nextui-org/alert';
import type { ReactNode } from 'react';

import { Alert as NextAlert } from '@nextui-org/alert';
import { alert as nextAlertVariants } from '@nextui-org/theme';

import { tv, variantsToClassNames } from '@fuf-stack/pixel-utils';

export const alertVariants = tv({
slots: {
base: '',
title: '',
description: '',
mainWrapper: '',
closeButton: '',
iconWrapper: '',
alertIcon: '',
},
variants: {
// See: https://github.com/nextui-org/nextui/blob/canary/packages/core/theme/src/components/alert.ts
color: {
info: {
mainWrapper: 'text-inherit',
title: 'text-inherit',
description: 'text-inherit',
},
...nextAlertVariants.variants.color,
},
variant: {
...nextAlertVariants.variants.variant,
},
},
compoundVariants: [
...nextAlertVariants.compoundVariants,
{
color: 'info',
variant: 'solid',
className: {
base: 'bg-info text-info-foreground',
alertIcon: 'text-info-foreground',
closeButton: 'text-inherit',
},
},
{
color: 'info',
variant: 'flat',
className: {
alertIcon: 'fill-current',
base: 'dark:bg-info-50/50 bg-info-50 text-info-600',
closeButton: 'text-info-500 data-[hover]:bg-info-200',
iconWrapper: 'border-info-100 bg-info-50 dark:bg-info-100',
},
},
{
color: 'info',
variant: 'faded',
className: {
alertIcon: 'fill-current',
base: 'dark:bg-info-50/50 border-small border-info-200 bg-info-50 text-info-600 dark:border-info-100',
closeButton: 'text-info-500 data-[hover]:bg-info-200',
iconWrapper: 'border-info-100 bg-info-50 dark:bg-info-100',
},
},
{
color: 'info',
variant: 'bordered',
className: {
alertIcon: 'fill-current',
base: 'border-small border-info text-info',
closeButton: 'text-info-500 data-[hover]:bg-info-200',
iconWrapper: 'bg-info-100 dark:bg-info-50',
},
},
],
});

export type VariantProps = TVProps<typeof alertVariants>;
type ClassName = TVClassName<typeof alertVariants>;

export interface AlertProps extends VariantProps {
/** Content displayed inside the Alert if no description is given! */
children?: ReactNode;
/** CSS class name */
className?: ClassName;
/** Color scheme of the Alert */
color?: VariantProps['color'];
/** Content displayed at the end of the Alert */
endContent?: ReactNode;
/** Icon displayed at the start of the Alert */
icon?: ReactNode;
/** Whether the Alert can be closed */
isClosable?: boolean;
/** Callback fired when the close button is clicked */
onClose?: () => void | undefined;
/** Whether to show the icon at the start */
showIcon?: boolean;
/** HTML data-testid attribute used in e2e tests */
testId?: string;
/** Title displayed in the Alert above the content */
title?: ReactNode;
/** Style variant of the Alert */
variant?: VariantProps['variant'];
}

/**
* Alert component based on [NextUI Alert](https://nextui.org/docs/components/alert)
*/
const Alert = ({
children = undefined,
className = undefined,
color = 'primary',
endContent = undefined,
icon = undefined,
isClosable = false,
onClose = undefined,
showIcon = true,
testId = undefined,
title = undefined,
variant = 'solid',
}: AlertProps) => {
const variants = alertVariants({
color,
variant,
});
const isNextUIColor = Object.keys(nextAlertVariants.variants.color).includes(
color,
);
const classNames = variantsToClassNames(variants, className, 'base');

return (
<NextAlert
classNames={classNames}
color={isNextUIColor ? (color as NextAlertProps['color']) : undefined}
data-testid={testId}
description={title ? children : undefined}
endContent={endContent}
hideIcon={!showIcon}
icon={icon}
isClosable={isClosable}
onClose={onClose}
title={(title || children) as string}
variant={variant}
/>
);
};

export default Alert;
Loading

0 comments on commit 09dbebf

Please sign in to comment.