-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pixels): add alert component and fix some issues
- Loading branch information
1 parent
1e552c9
commit 09dbebf
Showing
9 changed files
with
2,820 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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't worry, it's not the end of the world | ||
(but we can't promise anything). <br /> Seriously | ||
though, please review the following info: We've got | ||
some stuff to tell you, and it's probably going to be | ||
boring. But hey, at least you'll know what's up! | ||
</div> | ||
</div> | ||
)} | ||
</Alert> | ||
</div> | ||
))} | ||
</> | ||
); | ||
}, | ||
args: { | ||
title: 'Alert Issued', | ||
}, | ||
}; | ||
|
||
export const SpecialFullWidth: Story = { | ||
args: { | ||
showIcon: false, | ||
className: 'w-screen', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.