diff --git a/.changeset/poor-sheep-repair.md b/.changeset/poor-sheep-repair.md new file mode 100644 index 0000000000..fa819c548c --- /dev/null +++ b/.changeset/poor-sheep-repair.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/alert": minor +"@nextui-org/theme": minor +--- + +introduced Alert component (#2250) diff --git a/apps/docs/config/routes.json b/apps/docs/config/routes.json index 583e220fa7..b38e7c8854 100644 --- a/apps/docs/config/routes.json +++ b/apps/docs/config/routes.json @@ -144,6 +144,12 @@ "keywords": "autocomplete, auto suggest, search, typeahead", "path": "/docs/components/autocomplete.mdx" }, + { + "key": "alert", + "title": "Alert", + "keywords": "alert, notification, message", + "path": "/docs/components/alert.mdx" + }, { "key": "avatar", "title": "Avatar", diff --git a/apps/docs/content/components/alert/colors.ts b/apps/docs/content/components/alert/colors.ts new file mode 100644 index 0000000000..c4877551ec --- /dev/null +++ b/apps/docs/content/components/alert/colors.ts @@ -0,0 +1,27 @@ +const App = `import {Alert} from "@nextui-org/react"; + +export default function App() { + const title = "Email Sent!!"; + const description = "You will get a reply soon"; + + return ( +
+
+ {["default", "primary", "secondary", "success", "warning", "danger"].map((color) => ( +
+ {color} + +
+ ))} +
+
+ ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/alert/custom-impl.ts b/apps/docs/content/components/alert/custom-impl.ts new file mode 100644 index 0000000000..9f5a2c70a2 --- /dev/null +++ b/apps/docs/content/components/alert/custom-impl.ts @@ -0,0 +1,142 @@ +const InfoCircleIcon = `export const InfoCircleIcon = (props) => ( + + + +);`; + +const CloseIcon = `export const CloseIcon = (props) => ( + +);`; + +const App = `import React, {forwardRef, useMemo} from "react"; +import {useAlert} from "@nextui-org/react"; +import {InfoCircleIcon} from "./InfoCircleIcon"; +import {CloseIcon} from "./CloseIcon" + +const styles = { + base: [ + "bg-slate-100", + "border", + "shadow", + "hover:bg-slate-200", + "focus-within:!bg-slate-100", + "dark:bg-slate-900", + "dark:hover:bg-slate-800", + "dark:border-slate-800", + "dark:focus-within:!bg-slate-900", + "cursor-pointer" + ], + title: [ + "text-base", + "text-slate-500", + "font-bold" + ], + description: [ + "text-base", + "text-slate-500", + ], +} + +const MyAlert = forwardRef((props, ref) => { + const { + title, + description, + isClosable, + domRef, + handleClose, + getBaseProps, + getMainWrapperProps, + getDescriptionProps, + getTitleProps, + getCloseButtonProps, + color, + isVisible, + onClose, + getCloseIconProps, + getAlertIconProps, + } = useAlert({ + ...props, + ref, + // this is just for the example, the props bellow should be passed by the parent component + title: "Email Sent!!", + description: "You will get a reply soon", + // custom styles + classNames: { + ...styles, + }, + }); + + const mainWrapper = useMemo(() => { + return ( +
+ {title &&
{title}
} +
{description}
+
+ ); + }, [title, description, getMainWrapperProps, getTitleProps, getDescriptionProps]); + + const baseWrapper = useMemo(() => { + return isVisible ? ( +
+ + {mainWrapper} + {(isClosable || onClose) && ( + + )} +
+ ) : null; + }, [ + mainWrapper, + isClosable, + getCloseButtonProps, + isVisible, + domRef, + getBaseProps, + handleClose, + color, + onClose, + getAlertIconProps, + ]); + + return <>{baseWrapper}; +}); + +MyAlert.displayName = "MyAlert"; + +export default MyAlert;`; + +const react = { + "/App.jsx": App, + "/InfoCircleIcon": InfoCircleIcon, + "/CloseIcon": CloseIcon, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/alert/custom-styles.ts b/apps/docs/content/components/alert/custom-styles.ts new file mode 100644 index 0000000000..dc9f6e3deb --- /dev/null +++ b/apps/docs/content/components/alert/custom-styles.ts @@ -0,0 +1,46 @@ +const App = `import {Alert} from "@nextui-org/react"; + +export default function App() { + const title = "Email Sent!!"; + const description = "You will get a reply soon"; + + return ( +
+ +
+ ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/alert/index.ts b/apps/docs/content/components/alert/index.ts new file mode 100644 index 0000000000..e0f9f738c9 --- /dev/null +++ b/apps/docs/content/components/alert/index.ts @@ -0,0 +1,15 @@ +import colors from "./colors"; +import usage from "./usage"; +import isClosable from "./is-closable"; +import radius from "./radius"; +import customImpl from "./custom-impl"; +import customStyles from "./custom-styles"; + +export const alertContent = { + colors, + usage, + isClosable, + radius, + customImpl, + customStyles, +}; diff --git a/apps/docs/content/components/alert/is-closable.ts b/apps/docs/content/components/alert/is-closable.ts new file mode 100644 index 0000000000..e39f9b95d8 --- /dev/null +++ b/apps/docs/content/components/alert/is-closable.ts @@ -0,0 +1,20 @@ +const App = `import {Alert} from "@nextui-org/react"; + +export default function App() { + const title = "Email Sent!!"; + const description = "You will get a reply soon"; + + return ( +
+ +
+ ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/alert/radius.ts b/apps/docs/content/components/alert/radius.ts new file mode 100644 index 0000000000..19c3f552e8 --- /dev/null +++ b/apps/docs/content/components/alert/radius.ts @@ -0,0 +1,27 @@ +const App = `import {Alert} from "@nextui-org/react"; + +export default function App() { + const title = "Email Sent!!"; + const description = "You will get a reply soon"; + + return ( +
+
+ {["none", "sm", "md", "lg", "full"].map((radius) => ( +
+ {radius} + +
+ ))} +
+
+ ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/alert/usage.ts b/apps/docs/content/components/alert/usage.ts new file mode 100644 index 0000000000..faf2a032a5 --- /dev/null +++ b/apps/docs/content/components/alert/usage.ts @@ -0,0 +1,20 @@ +const App = `import {Alert} from "@nextui-org/react"; + +export default function App() { + const title = "Email Sent!!"; + const description = "You will get a reply soon"; + + return ( +
+ +
+ ); +}`; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/index.ts b/apps/docs/content/components/index.ts index ac6e664826..2641e4c404 100644 --- a/apps/docs/content/components/index.ts +++ b/apps/docs/content/components/index.ts @@ -31,3 +31,4 @@ export * from "./dropdown"; export * from "./navbar"; export * from "./table"; export * from "./autocomplete"; +export * from "./alert"; diff --git a/apps/docs/content/docs/components/alert.mdx b/apps/docs/content/docs/components/alert.mdx new file mode 100644 index 0000000000..6da1651c1e --- /dev/null +++ b/apps/docs/content/docs/components/alert.mdx @@ -0,0 +1,125 @@ +--- +title: "Alert" +description: "Alerts are temporary notifications that provide concise feedback about an action or event." +--- + +import {alertContent} from "@/content/components/alert"; + +# Alert + +Alerts are temporary notifications that provide concise feedback about an action or event. + + + +--- + + + +## Installation + + + +## Import + + + + +## Usage +- It accepts `title` and `description` as props for the alert message. +- `title` is an optional property. + + + +### Radius + + + +### Colors + + + + +### isClosable + +If `isClosable` is true, a close button appears on the alert, which can be used to close it. + +> **Note**: If the `onClose` is passed, the close button is visible regardless of the `isClosable` property. + + + + + + +## Slots + +- **base**: + Alert wrapper, it handles alignment, placement, and general appearance. +- **mainWrapper**: + Wraps the `title` and `description` of the alert. +- **closeButton**: + The `closeButton`, it is in the top-right corner of alert. +- **description**: + The description of the alert. +- **title**: + The title of the alert. +- **closeIcon**: + The close icon that is wrapped inside the `closeButton`. +- **alertIcon**: + icon that appears at the top-left corner. + + + +### Custom Alert Styles + +You can customize the alert styles by using the `classNames` property. + +Here's an example of how to customize the alert styles: + + + + + +### Custom Implementation + +In case you need to customize the alert even further, you can use the `useAlert` hook to create your own implementation. + + + + + +## API + +### Alert Props + +| Attribute | Type | Description | Default | +| ---------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------ | --------- | +| title | `string` | Title for alert | - | +| description | `ReactNode` | Description for alert message | - | +| color | `default` \| `primary` \| `secondary` \| `success` \| `warning` \| `danger` | The alert color theme. | `default` | +| radius | `none` \| `sm` \| `md` \| `lg` \| `full` | The alert border radius. | `md` | +| isClosable | `boolean` | Whether the close button should be displayed. | `false` | + +### Alert Events + +| Attribute | Type | Description | +| ------------ | --------------------------- | ----------------------------------------------------------- | +| onClose | `() => void` | Handler that is called when the close button is clicked. | \ No newline at end of file diff --git a/packages/components/alert/README.md b/packages/components/alert/README.md new file mode 100644 index 0000000000..071ac6dd26 --- /dev/null +++ b/packages/components/alert/README.md @@ -0,0 +1,24 @@ +# @nextui-org/alert + +Alerts are temporary notifications that provide concise feedback about an action or event. + +Please refer to the [documentation](https://nextui.org/docs/components/alert) for more information. + +## Installation + +```sh +yarn add @nextui-org/alert +# or +npm i @nextui-org/alert +``` + +## Contribution + +Yes please! See the +[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md) +for details. + +## License + +This project is licensed under the terms of the +[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE). diff --git a/packages/components/alert/__tests__/alert.test.tsx b/packages/components/alert/__tests__/alert.test.tsx new file mode 100644 index 0000000000..dfa3074c18 --- /dev/null +++ b/packages/components/alert/__tests__/alert.test.tsx @@ -0,0 +1,85 @@ +import * as React from "react"; +import {act, render} from "@testing-library/react"; + +import {Alert} from "../src"; + +const title = "Testing Title"; +const description = "Testing Description"; + +describe("Alert", () => { + it("should render correctly", () => { + const wrapper = render(); + + expect(() => wrapper.unmount()).not.toThrow(); + }); + + it("ref should be forwarded", () => { + const ref = React.createRef(); + + render(); + + expect(ref.current).not.toBeNull(); + }); + + it("should display title and description when component is rendered", () => { + const wrapper = render(); + + const titleElement = wrapper.getByText(title); + const descriptionElement = wrapper.getByText(description); + + expect(titleElement).toContainHTML(title); + expect(descriptionElement).toContainHTML(description); + }); + + it("should show close button when is Closable", () => { + const {getByRole} = render(); + + const closeButton = getByRole("button"); + + expect(closeButton).toBeVisible(); + }); + + it("should show close button when onClose is passed", () => { + const onClose = jest.fn(); + + const {getByRole} = render(); + + const closeButton = getByRole("button"); + + expect(closeButton).toBeVisible(); + }); + + it("should not show close button when not isClosable and onClose is not passed", () => { + const wrapper = render(); + + const closeButton = wrapper.queryByRole("button"); + + expect(closeButton).toBeNull(); + }); + + it("should call the onClose function when clicking on close button", () => { + const onClose = jest.fn(); + + const wrapper = render(); + + const closeButton = wrapper.getByRole("button"); + + act(() => { + closeButton.click(); + }); + + expect(onClose).toHaveBeenCalled(); + }); + + it("should close the alert when clicking on close button", () => { + const wrapper = render(); + + const closeButton = wrapper.getByRole("button"); + + act(() => { + closeButton.click(); + }); + + expect(wrapper.container).toBeEmptyDOMElement(); + }); +}); diff --git a/packages/components/alert/package.json b/packages/components/alert/package.json new file mode 100644 index 0000000000..d06a00d598 --- /dev/null +++ b/packages/components/alert/package.json @@ -0,0 +1,59 @@ +{ + "name": "@nextui-org/alert", + "version": "2.0.0", + "description": "Alerts are temporary notifications that provide concise feedback about an action or event.", + "keywords": [ + "alert" + ], + "author": "Junior Garcia ", + "contributors": [ + "Abhinav Agarwal ", + "WK Wong " + ], + "homepage": "https://nextui.org", + "license": "MIT", + "main": "src/index.ts", + "sideEffects": false, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nextui-org/nextui.git", + "directory": "packages/components/alert" + }, + "bugs": { + "url": "https://github.com/nextui-org/nextui/issues" + }, + "scripts": { + "build": "tsup src --dts", + "dev": "pnpm build:fast --watch", + "clean": "rimraf dist .turbo", + "typecheck": "tsc --noEmit", + "build:fast": "tsup src", + "prepack": "clean-package", + "postpack": "clean-package restore" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "@nextui-org/theme": ">=2.1.0", + "@nextui-org/system": ">=2.0.0" + }, + "dependencies": { + "@nextui-org/react-utils": "workspace:*", + "@nextui-org/shared-icons": "workspace:*", + "@nextui-org/shared-utils": "workspace:*" + }, + "devDependencies": { + "@nextui-org/system": "workspace:*", + "@nextui-org/theme": "workspace:*", + "clean-package": "2.2.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "clean-package": "../../../clean-package.config.json" + } \ No newline at end of file diff --git a/packages/components/alert/src/alert.tsx b/packages/components/alert/src/alert.tsx new file mode 100644 index 0000000000..e97880144d --- /dev/null +++ b/packages/components/alert/src/alert.tsx @@ -0,0 +1,85 @@ +import {useMemo} from "react"; +import {forwardRef} from "@nextui-org/system"; +import { + CloseIcon, + DangerIcon, + InfoCircleIcon, + SuccessIcon, + WarningIcon, +} from "@nextui-org/shared-icons"; + +import {useAlert, UseAlertProps} from "./use-alert"; + +export interface AlertProps extends UseAlertProps {} + +const Alert = forwardRef<"div", AlertProps>((props, ref) => { + const { + title, + description, + isClosable, + domRef, + handleClose, + getBaseProps, + getMainWrapperProps, + getDescriptionProps, + getTitleProps, + getCloseButtonProps, + color, + isVisible, + onClose, + getAlertIconProps, + } = useAlert({...props, ref}); + + const mainWrapper = useMemo(() => { + return ( +
+ {title &&
{title}
} +
{description}
+
+ ); + }, [title, description, getMainWrapperProps, getTitleProps, getDescriptionProps]); + + const iconMap = { + primary: InfoCircleIcon, + secondary: InfoCircleIcon, + success: SuccessIcon, + warning: WarningIcon, + danger: DangerIcon, + }; + + const IconComponent = iconMap[color] || InfoCircleIcon; + + const alertIcon = useMemo(() => { + return ; + }, [color, getAlertIconProps]); + + const baseWrapper = useMemo(() => { + return isVisible ? ( +
+ {alertIcon} + {mainWrapper} + {(isClosable || onClose) && ( + + )} +
+ ) : null; + }, [ + mainWrapper, + isClosable, + getCloseButtonProps, + isVisible, + domRef, + getBaseProps, + handleClose, + onClose, + alertIcon, + ]); + + return <>{baseWrapper}; +}); + +Alert.displayName = "NextUI.Alert"; + +export default Alert; diff --git a/packages/components/alert/src/index.ts b/packages/components/alert/src/index.ts new file mode 100644 index 0000000000..e1d0ea8cf6 --- /dev/null +++ b/packages/components/alert/src/index.ts @@ -0,0 +1,10 @@ +import Alert from "./alert"; + +// export types +export type {AlertProps} from "./alert"; + +// export hooks +export {useAlert} from "./use-alert"; + +// export component +export {Alert}; diff --git a/packages/components/alert/src/use-alert.ts b/packages/components/alert/src/use-alert.ts new file mode 100644 index 0000000000..ea6026d222 --- /dev/null +++ b/packages/components/alert/src/use-alert.ts @@ -0,0 +1,124 @@ +import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system"; +import {AlertSlots, SlotsToClasses} from "@nextui-org/theme"; +import {ReactRef, useDOMRef} from "@nextui-org/react-utils"; +import {AlertVariantProps} from "@nextui-org/theme/src/components/alert"; +import {ReactNode, useCallback, useMemo} from "react"; +import {alert} from "@nextui-org/theme"; +import {useState} from "react"; +import {objectToDeps} from "@nextui-org/shared-utils"; + +interface Props extends HTMLNextUIProps<"div"> { + /** + * Ref to the DOM node. + */ + ref?: ReactRef; + + /** + * title of the alert message + */ + title?: string; + + /** + * Main body of the alert message + */ + description: ReactNode; + + /** + * whether the alert can be closed by user + */ + isClosable?: boolean; + + /** + * function which is called when close button is clicked + */ + onClose?: () => void; + + /** + * Classname or List of classes to change the classNames of the element. + * if `className` is passed, it will be added to the base slot. + * + * @example + * ```ts + * + * ``` + */ + classNames?: SlotsToClasses; +} + +export type UseAlertProps = Props & AlertVariantProps; + +export function useAlert(originalProps: UseAlertProps) { + const [props, variantProps] = mapPropsVariants(originalProps, alert.variantKeys); + + const {title, description, onClose, isClosable, ref, classNames} = props; + + const [isVisible, setIsVisible] = useState(true); + + const handleClose = () => { + setIsVisible(false); + onClose?.(); + }; + const domRef = useDOMRef(ref); + + const slots = useMemo(() => alert({...variantProps}), [objectToDeps(variantProps)]); + + const getBaseProps = useCallback(() => { + return { + className: slots.base({class: classNames?.base}), + }; + }, [slots, classNames?.base]); + + const getMainWrapperProps = useCallback(() => { + return { + className: slots.mainWrapper({class: classNames?.mainWrapper}), + }; + }, [slots, classNames?.mainWrapper]); + + const getDescriptionProps = useCallback(() => { + return { + className: slots.description({class: classNames?.description}), + }; + }, [slots, classNames?.description]); + + const getTitleProps = useCallback(() => { + return { + className: slots.title({class: classNames?.title}), + }; + }, [slots, classNames?.title]); + + const getCloseButtonProps = useCallback(() => { + return { + className: slots.closeButton({class: classNames?.closeButton}), + }; + }, [slots, classNames?.closeButton]); + + const getAlertIconProps = useCallback(() => { + return { + className: slots.alertIcon({class: classNames?.alertIcon}), + }; + }, [slots, classNames?.alertIcon]); + + return { + title, + description, + isClosable, + domRef, + getBaseProps, + getMainWrapperProps, + getDescriptionProps, + getTitleProps, + color: variantProps["color"], + getCloseButtonProps, + handleClose, + isVisible, + onClose, + getAlertIconProps, + }; +} diff --git a/packages/components/alert/stories/alert.stories.tsx b/packages/components/alert/stories/alert.stories.tsx new file mode 100644 index 0000000000..de8f01c413 --- /dev/null +++ b/packages/components/alert/stories/alert.stories.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import {Meta} from "@storybook/react"; +import {alert} from "@nextui-org/theme"; + +import {Alert} from "../src"; + +export default { + title: "Components/Alert", + component: Alert, + argTypes: { + color: { + control: { + type: "select", + }, + options: ["default", "primary", "secondary", "success", "warning", "danger"], + }, + radius: { + control: { + type: "select", + }, + options: ["none", "sm", "md", "lg", "full"], + }, + isClosable: { + control: { + type: "boolean", + }, + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} as Meta; + +const defaultProps = { + ...alert.defaultVariants, + title: "Email Sent!!", + description: "You will get a reply soon", +}; + +const Template = (args) => ( + <> + + +); + +const ColorTemplate = (args) => { + return ( +
+ {["default", "primary", "secondary", "success", "warning", "danger"].map((color) => ( +
+ {color} + +
+ ))} +
+ ); +}; + +const RadiusTemplate = (args) => { + return ( +
+ {["none", "sm", "md", "lg", "full"].map((radius) => ( +
+ {radius} + +
+ ))} +
+ ); +}; + +export const Default = { + render: Template, + args: { + ...defaultProps, + }, +}; +export const Color = { + render: ColorTemplate, + args: { + ...defaultProps, + }, +}; +export const Radius = { + render: RadiusTemplate, + args: { + ...defaultProps, + }, +}; +export const isClosable = { + render: Template, + args: { + ...defaultProps, + isClosable: true, + }, +}; +export const CustomWithClassNames = { + render: Template, + args: { + ...defaultProps, + classNames: { + base: [ + "bg-slate-100", + "border", + "shadow", + "hover:bg-slate-200", + "focus-within:!bg-slate-100", + "dark:bg-slate-900", + "dark:hover:bg-slate-800", + "dark:border-slate-800", + "dark:focus-within:!bg-slate-900", + "cursor-pointer", + ], + title: ["text-base", "text-slate-500", "font-bold"], + description: ["text-base", "text-slate-500"], + }, + }, +}; diff --git a/packages/components/alert/tsconfig.json b/packages/components/alert/tsconfig.json new file mode 100644 index 0000000000..5d012f6e61 --- /dev/null +++ b/packages/components/alert/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "tailwind-variants": ["../../../node_modules/tailwind-variants"] + }, + }, + "include": ["src", "index.ts"] +} diff --git a/packages/components/alert/tsup.config.ts b/packages/components/alert/tsup.config.ts new file mode 100644 index 0000000000..3e2bcff6cc --- /dev/null +++ b/packages/components/alert/tsup.config.ts @@ -0,0 +1,8 @@ +import {defineConfig} from "tsup"; + +export default defineConfig({ + clean: true, + target: "es2019", + format: ["cjs", "esm"], + banner: {js: '"use client";'}, +}); diff --git a/packages/core/react/package.json b/packages/core/react/package.json index 1dbd682fd3..b701d5d5f7 100644 --- a/packages/core/react/package.json +++ b/packages/core/react/package.json @@ -84,6 +84,7 @@ "@nextui-org/date-input": "workspace:*", "@nextui-org/date-picker": "workspace:*", "@nextui-org/framer-utils": "workspace:*", + "@nextui-org/alert": "workspace:*", "@react-aria/visually-hidden": "3.8.12" }, "peerDependencies": { diff --git a/packages/core/react/src/index.ts b/packages/core/react/src/index.ts index d504dbd361..96c5475f69 100644 --- a/packages/core/react/src/index.ts +++ b/packages/core/react/src/index.ts @@ -43,6 +43,7 @@ export * from "@nextui-org/autocomplete"; export * from "@nextui-org/calendar"; export * from "@nextui-org/date-input"; export * from "@nextui-org/date-picker"; +export * from "@nextui-org/alert"; /** * React Aria - Exports diff --git a/packages/core/theme/src/components/alert.ts b/packages/core/theme/src/components/alert.ts new file mode 100644 index 0000000000..759350f264 --- /dev/null +++ b/packages/core/theme/src/components/alert.ts @@ -0,0 +1,107 @@ +import type {VariantProps} from "tailwind-variants"; + +import {tv} from "../utils/tv"; +/** + * Alert wrapper **Tailwind Variants** component + * + * @example + * ```js + * const {base, mainWrapper, title, description, closeButton, alertIcon} = alert({...}) + * + * + *
+ * {alertIcon} + *
+ *
Title
+ *
Description
+ *
+ * + *
+ * ``` + */ +const alert = tv({ + slots: { + base: ["flex flex-row max-w-[342px] flex-grow min-h-17 max-h-full p-3"], + mainWrapper: [ + "flex-grow max-w-[268px] min-h-11 max-h-full ms-5 flex flex-col box-border items-start", + ], + title: ["w-full text-medium font-normal block min-h-6 max-h-full"], + description: ["text-small font-normal min-h-5 max-h-full"], + closeButton: ["w-6 h-6 cursor-pointer relative fill-current"], + alertIcon: ["fill-current"], + }, + variants: { + color: { + default: { + base: ["bg-default-100"], + title: ["text-foreground"], + description: ["text-default-600"], + closeButton: ["text-default-400"], + alertIcon: ["text-default-foreground"], + }, + primary: { + base: ["bg-primary-50"], + title: ["text-primary"], + description: ["text-primary"], + closeButton: ["text-primary-200"], + alertIcon: ["text-primary"], + }, + secondary: { + base: ["bg-secondary-50"], + title: ["text-secondary"], + description: ["text-secondary-200"], + closeButton: ["text-secondary-200"], + alertIcon: ["text-secondary"], + }, + success: { + base: ["bg-success-50"], + title: ["text-success"], + description: ["text-success-200"], + closeButton: ["text-success-200"], + alertIcon: ["text-success"], + }, + warning: { + base: ["bg-warning-50"], + title: ["text-warning"], + description: ["text-warning-200"], + closeButton: ["text-warning-200"], + alertIcon: ["text-warning"], + }, + danger: { + base: ["bg-danger-50"], + title: ["text-danger"], + description: ["text-danger-200"], + closeButton: ["text-danger-200"], + alertIcon: ["text-danger"], + }, + }, + radius: { + none: { + base: "rounded-none", + }, + sm: { + base: "rounded-small", + }, + md: { + base: "rounded-medium", + }, + lg: { + base: "rounded-large", + }, + full: { + base: "rounded-full", + }, + }, + }, + defaultVariants: { + color: "default", + radius: "md", + }, +}); + +export type AlertVariantProps = VariantProps; +export type AlertSlots = keyof ReturnType; + +export {alert}; diff --git a/packages/core/theme/src/components/index.ts b/packages/core/theme/src/components/index.ts index 7776b24c52..c5ed21c037 100644 --- a/packages/core/theme/src/components/index.ts +++ b/packages/core/theme/src/components/index.ts @@ -38,3 +38,4 @@ export * from "./autocomplete"; export * from "./calendar"; export * from "./date-input"; export * from "./date-picker"; +export * from "./alert"; diff --git a/packages/utilities/shared-icons/src/danger.tsx b/packages/utilities/shared-icons/src/danger.tsx new file mode 100644 index 0000000000..ffa5c56f6d --- /dev/null +++ b/packages/utilities/shared-icons/src/danger.tsx @@ -0,0 +1,20 @@ +import {IconSvgProps} from "./types"; + +export const DangerIcon = ( + props: IconSvgProps & { + className?: string; + }, +) => { + return ( + + + + ); +}; diff --git a/packages/utilities/shared-icons/src/index.ts b/packages/utilities/shared-icons/src/index.ts index d4a719af5c..dc1504605b 100644 --- a/packages/utilities/shared-icons/src/index.ts +++ b/packages/utilities/shared-icons/src/index.ts @@ -33,7 +33,10 @@ export * from "./arrow-right"; export * from "./arrow-left"; export * from "./link"; export * from "./selector"; - +export * from "./info-circle"; +export * from "./warning"; +export * from "./danger"; +export * from "./success"; // sets export * from "./bulk"; export * from "./bold"; diff --git a/packages/utilities/shared-icons/src/info-circle.tsx b/packages/utilities/shared-icons/src/info-circle.tsx new file mode 100644 index 0000000000..6f4650d0e3 --- /dev/null +++ b/packages/utilities/shared-icons/src/info-circle.tsx @@ -0,0 +1,20 @@ +import {IconSvgProps} from "./types"; + +export const InfoCircleIcon = ( + props: IconSvgProps & { + className?: string; + }, +) => { + return ( + + + + ); +}; diff --git a/packages/utilities/shared-icons/src/success.tsx b/packages/utilities/shared-icons/src/success.tsx new file mode 100644 index 0000000000..dab41491ea --- /dev/null +++ b/packages/utilities/shared-icons/src/success.tsx @@ -0,0 +1,27 @@ +import {IconSvgProps} from "./types"; + +export const SuccessIcon = ( + props: IconSvgProps & { + className?: string; + }, +) => { + return ( + + + + ); +}; diff --git a/packages/utilities/shared-icons/src/warning.tsx b/packages/utilities/shared-icons/src/warning.tsx new file mode 100644 index 0000000000..a647c6b04e --- /dev/null +++ b/packages/utilities/shared-icons/src/warning.tsx @@ -0,0 +1,20 @@ +import {IconSvgProps} from "./types"; + +export const WarningIcon = ( + props: IconSvgProps & { + className?: string; + }, +) => { + return ( + + + + ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11ec4f64e6..f47f92870e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -678,6 +678,34 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + packages/components/alert: + dependencies: + '@nextui-org/react-utils': + specifier: workspace:* + version: link:../../utilities/react-utils + '@nextui-org/shared-icons': + specifier: workspace:* + version: link:../../utilities/shared-icons + '@nextui-org/shared-utils': + specifier: workspace:* + version: link:../../utilities/shared-utils + devDependencies: + '@nextui-org/system': + specifier: workspace:* + version: link:../../core/system + '@nextui-org/theme': + specifier: workspace:* + version: link:../../core/theme + clean-package: + specifier: 2.2.0 + version: 2.2.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + packages/components/autocomplete: dependencies: '@nextui-org/aria-utils': @@ -2831,6 +2859,9 @@ importers: '@nextui-org/accordion': specifier: workspace:* version: link:../../components/accordion + '@nextui-org/alert': + specifier: workspace:* + version: link:../../components/alert '@nextui-org/autocomplete': specifier: workspace:* version: link:../../components/autocomplete