diff --git a/packages/clay-core/src/alert/Alert.tsx b/packages/clay-core/src/alert/Alert.tsx new file mode 100644 index 0000000000..b39a1cf833 --- /dev/null +++ b/packages/clay-core/src/alert/Alert.tsx @@ -0,0 +1,179 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import Icon from '@clayui/icon'; +import Layout from '@clayui/layout'; +import classNames from 'classnames'; +import React, {useEffect, useRef} from 'react'; + +import {AlertFooter} from './Footer'; +import {ToastContainer} from './ToastContainer'; +import {AlertProps} from './types'; + +const useAutoClose = (autoClose?: boolean | number, onClose = () => {}) => { + const startedTime = useRef(0); + const timer = useRef(undefined); + const timeToClose = useRef(autoClose === true ? 10000 : autoClose); + + let pauseTimer = () => {}; + let startTimer = () => {}; + + if (autoClose) { + pauseTimer = () => { + if (timer.current) { + timeToClose.current = + (timeToClose.current as number) - + (Date.now() - startedTime.current); + + clearTimeout(timer.current); + + timer.current = undefined; + } + }; + + startTimer = () => { + startedTime.current = Date.now(); + timer.current = window.setTimeout( + onClose, + timeToClose.current as number + ); + }; + } + + useEffect(() => { + if (autoClose) { + startTimer(); + + return pauseTimer; + } + }, []); + + return { + pauseAutoCloseTimer: pauseTimer, + startAutoCloseTimer: startTimer, + }; +}; + +const ICON_MAP = { + danger: 'exclamation-full', + info: 'info-circle', + secondary: 'password-policies', + success: 'check-circle-full', + warning: 'warning-full', +}; + +const VARIANTS = ['inline', 'feedback']; + +export function Alert(props: AlertProps): JSX.Element & { + Footer: typeof AlertFooter; + ToastContainer: typeof ToastContainer; +}; + +export function Alert({ + actions, + autoClose, + children, + className, + displayType = 'info', + hideCloseIcon = false, + onClose, + role = 'alert', + spritemap, + symbol, + title, + variant, + ...otherProps +}: AlertProps) { + const {pauseAutoCloseTimer, startAutoCloseTimer} = useAutoClose( + autoClose, + onClose + ); + + const ConditionalContainer = ({children}: any) => + variant === 'stripe' ? ( +
{children}
+ ) : ( + <>{children} + ); + + const showDismissible = onClose && !hideCloseIcon; + + const AlertIndicator = () => ( + + + + ); + + return ( +
+ + + {!VARIANTS.includes(variant as string) && ( + + + + + + )} + + + + {VARIANTS.includes(variant as string) && ( + + )} + + {title && {title}} + + {children} + + {variant !== 'inline' && actions && ( + {actions} + )} + + + + {variant === 'inline' && actions && ( + + + {actions} + + + )} + + + {showDismissible && ( + + )} + +
+ ); +} + +Alert.Footer = AlertFooter; +Alert.ToastContainer = ToastContainer; diff --git a/packages/clay-core/src/alert/Footer.tsx b/packages/clay-core/src/alert/Footer.tsx new file mode 100644 index 0000000000..3284ff908f --- /dev/null +++ b/packages/clay-core/src/alert/Footer.tsx @@ -0,0 +1,19 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import classNames from 'classnames'; +import React from 'react'; + +export const AlertFooter = ({ + children, + className, + ...otherProps +}: React.HTMLAttributes) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/clay-core/src/alert/ToastContainer.tsx b/packages/clay-core/src/alert/ToastContainer.tsx new file mode 100644 index 0000000000..bb5b87a4f6 --- /dev/null +++ b/packages/clay-core/src/alert/ToastContainer.tsx @@ -0,0 +1,31 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import classNames from 'classnames'; +import React from 'react'; + +import {AlertProps} from './types'; + +export type Props = React.HTMLAttributes & { + /** + * Children of the ToastContainer must be a ClayAlert + */ + children?: + | React.ReactElement + | Array>; +}; + +export const ToastContainer = ({children, className, ...otherProps}: Props) => { + return ( +
+
+ {children} +
+
+ ); +}; diff --git a/packages/clay-core/src/alert/index.ts b/packages/clay-core/src/alert/index.ts new file mode 100644 index 0000000000..daf0a0860f --- /dev/null +++ b/packages/clay-core/src/alert/index.ts @@ -0,0 +1,6 @@ +/** + * SPDX-FileCopyrightText: © 2024 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +export * from './Alert'; diff --git a/packages/clay-core/src/alert/types.ts b/packages/clay-core/src/alert/types.ts new file mode 100644 index 0000000000..06d91e45ac --- /dev/null +++ b/packages/clay-core/src/alert/types.ts @@ -0,0 +1,66 @@ +/** + * SPDX-FileCopyrightText: © 2024 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +export type DisplayType = + | 'danger' + | 'info' + | 'secondary' + | 'success' + | 'warning'; + +export type AlertProps = Omit, 'role'> & { + /** + * A React Component to render the alert actions. + */ + actions?: React.ReactNode; + + /** + * Flag to indicate alert should automatically call `onClose`. It also + * accepts a duration (in ms) which indicates how long to wait. If `true` + * is passed in, the timeout will be 10000ms. + */ + autoClose?: boolean | number; + + /** + * Callback function for when the 'x' is clicked. + */ + onClose?: () => void; + + /** + * The alert role is for important, and usually time-sensitive, information. + */ + role?: string | null; + + /** + * Determines the style of the alert. + */ + displayType?: DisplayType; + + /** + * Flag to indicate if close icon should be show. This prop is used in + * conjunction with the `onClose`prop; + */ + hideCloseIcon?: boolean; + + /** + * Path to the spritemap that Icon should use when referencing symbols. + */ + spritemap?: string; + + /** + * The icon's symbol name in the spritemap. + */ + symbol?: string; + + /** + * The summary of the Alert, often is something like 'Error' or 'Info'. + */ + title?: string; + + /** + * Determines the variant of the alert. + */ + variant?: 'feedback' | 'stripe' | 'inline'; +}; diff --git a/packages/clay-core/src/badge/Badge.tsx b/packages/clay-core/src/badge/Badge.tsx new file mode 100644 index 0000000000..bef6118c30 --- /dev/null +++ b/packages/clay-core/src/badge/Badge.tsx @@ -0,0 +1,84 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import classNames from 'classnames'; +import React from 'react'; + +type DisplayType = + | 'primary' + | 'secondary' + | 'info' + | 'danger' + | 'success' + | 'warning' + | 'beta' + | 'beta-dark'; + +export type Props = React.HTMLAttributes & { + /** + * Flag to indicate if the badge should use the clay-dark variant. + */ + dark?: boolean; + + /** + * Determines the color of the badge. + * The values `beta` and `beta-dark` are deprecated since v3.100.0 - use + * `translucent` and `dark` instead. + */ + displayType?: DisplayType; + + /** + * Info that is shown inside of the badge itself. + */ + label?: string | number; + + /** + * Flag to indicate if the badge should use the translucent variant. + */ + translucent?: boolean; +}; + +export const Badge = React.forwardRef( + ( + { + className, + dark, + displayType = 'primary', + label, + translucent, + ...otherProps + }, + ref + ) => { + if (displayType === 'beta') { + displayType = 'info'; + translucent = true; + } else if (displayType === 'beta-dark') { + dark = true; + displayType = 'info'; + translucent = true; + } + + return ( + + {label} + + ); + } +); + +Badge.displayName = 'ClayBadge'; diff --git a/packages/clay-core/src/badge/index.ts b/packages/clay-core/src/badge/index.ts new file mode 100644 index 0000000000..21bdd642ec --- /dev/null +++ b/packages/clay-core/src/badge/index.ts @@ -0,0 +1,6 @@ +/** + * SPDX-FileCopyrightText: © 2024 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +export * from './Badge'; diff --git a/packages/clay-core/src/breadcrumb/Breadcrumb.tsx b/packages/clay-core/src/breadcrumb/Breadcrumb.tsx new file mode 100644 index 0000000000..a79726d278 --- /dev/null +++ b/packages/clay-core/src/breadcrumb/Breadcrumb.tsx @@ -0,0 +1,136 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import {ClayButtonWithIcon} from '@clayui/button'; +import classNames from 'classnames'; +import React, {useState} from 'react'; +import warning from 'warning'; + +import {Item} from './Item'; + +type TItem = React.ComponentProps; +type TItems = Array; + +export type Props = React.HTMLAttributes & { + /** + * Defines the aria label of component elements. + */ + ariaLabels?: { + breadcrumb: string; + open: string; + close: string; + }; + + /** + * The number of Breadcrumb Items to show on each side of the active Breadcrumb Item before + * using an ellipsis dropdown. + * @deprecated since v3.91.0 - It is no longer necessary. + */ + ellipsisBuffer?: number; + + /** + * Use this property for defining `otherProps` that will be passed to ellipsis dropdown trigger. + * @deprecated since v3.91.0 - It is no longer necessary. + */ + ellipsisProps?: Object; + + /** + * Property to define Breadcrumb's items. + */ + items: TItems; + + /** + * Path to the location of the spritemap resource. + */ + spritemap?: string; +}; + +const findActiveItems = (items: TItems) => { + return items.filter((item) => { + return item.active; + }); +}; + +export const Breadcrumb = ({ + ariaLabels = { + breadcrumb: 'Breadcrumb', + close: 'Partially nest breadcrumbs', + open: 'See full nested', + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ellipsisBuffer = 1, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ellipsisProps = {}, + className, + items, + spritemap, + ...otherProps +}: Props) => { + warning( + findActiveItems(items).length === 1, + 'ClayBreadcrumb expects at least one `active` item on `items`.' + ); + + const [collapsed, setCollapsed] = useState(false); + + return ( + + ); +}; + +type ItemsProps = { + items: TItems; +}; + +function Items({items}: ItemsProps) { + return ( + <> + {items.map((item: TItem | React.ReactNode, i: number) => + React.isValidElement(item) ? ( + React.cloneElement(item, {key: `ellipsis${i}`}) + ) : ( + + ) + )} + + ); +} diff --git a/packages/clay-core/src/breadcrumb/Item.tsx b/packages/clay-core/src/breadcrumb/Item.tsx new file mode 100644 index 0000000000..c6babb00e7 --- /dev/null +++ b/packages/clay-core/src/breadcrumb/Item.tsx @@ -0,0 +1,54 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +import Link from '@clayui/link'; +import classNames from 'classnames'; +import React from 'react'; + +type Props = React.HTMLAttributes & { + /** + * Flag to indicate if the Breadcrumb item is active or not. + */ + active?: boolean; + + /** + * This value is used to be the target of the link. + */ + href?: string; + + /** + * Label of the Breadcrumb item + */ + label: string; + + /** + * Callback for when a Breadcrumb item is clicked. + */ + onClick?: (event: React.SyntheticEvent) => void; +}; + +export const Item = ({active, href, label, onClick, ...otherProps}: Props) => ( +
  • + { + if (onClick) { + event.preventDefault(); + onClick(event); + } + }} + > + {label} + +
  • +); diff --git a/packages/clay-core/src/breadcrumb/index.ts b/packages/clay-core/src/breadcrumb/index.ts new file mode 100644 index 0000000000..e8402ebf82 --- /dev/null +++ b/packages/clay-core/src/breadcrumb/index.ts @@ -0,0 +1,6 @@ +/** + * SPDX-FileCopyrightText: © 2024 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +export * from './Breadcrumb'; diff --git a/packages/clay-core/src/index.ts b/packages/clay-core/src/index.ts index e2aaf36752..bcff0fd11a 100644 --- a/packages/clay-core/src/index.ts +++ b/packages/clay-core/src/index.ts @@ -15,6 +15,9 @@ export { } from '@clayui/modal'; export {Provider, useProvider} from '@clayui/provider'; +export {Alert} from './alert'; +export {Badge} from './badge'; +export {Breadcrumb} from './breadcrumb'; export {Heading, Text, TextHighlight} from './typography'; export {OverlayMask} from './overlay-mask'; export {TreeView} from './tree-view';