Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Move components to @clayui/core #5783

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
179 changes: 179 additions & 0 deletions packages/clay-core/src/alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
* 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<number>(0);
const timer = useRef<number | undefined>(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' ? (
<div className="container">{children}</div>
) : (
<>{children}</>
);

const showDismissible = onClose && !hideCloseIcon;

const AlertIndicator = () => (
<span className="alert-indicator">
<Icon
spritemap={spritemap}
symbol={symbol || ICON_MAP[displayType]}
/>
</span>
);

return (
<div
{...otherProps}
className={classNames(className, 'alert', {
'alert-dismissible': showDismissible,
'alert-feedback alert-indicator-start': variant === 'feedback',
'alert-fluid': variant === 'stripe',
'alert-inline': variant === 'inline',
[`alert-${displayType}`]: displayType,
})}
onMouseOut={startAutoCloseTimer}
onMouseOver={pauseAutoCloseTimer}
>
<ConditionalContainer>
<Layout.ContentRow
className="alert-autofit-row"
role={role as string}
>
{!VARIANTS.includes(variant as string) && (
<Layout.ContentCol>
<Layout.ContentSection>
<AlertIndicator />
</Layout.ContentSection>
</Layout.ContentCol>
)}

<Layout.ContentCol expand>
<Layout.ContentSection>
{VARIANTS.includes(variant as string) && (
<AlertIndicator />
)}

{title && <strong className="lead">{title}</strong>}

{children}

{variant !== 'inline' && actions && (
<AlertFooter>{actions}</AlertFooter>
)}
</Layout.ContentSection>
</Layout.ContentCol>

{variant === 'inline' && actions && (
<Layout.ContentCol>
<Layout.ContentSection>
{actions}
</Layout.ContentSection>
</Layout.ContentCol>
)}
</Layout.ContentRow>

{showDismissible && (
<button
aria-label="Close"
className="close"
onClick={onClose}
type="button"
>
<Icon spritemap={spritemap} symbol="times" />
</button>
)}
</ConditionalContainer>
</div>
);
}

Alert.Footer = AlertFooter;
Alert.ToastContainer = ToastContainer;
19 changes: 19 additions & 0 deletions packages/clay-core/src/alert/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/

import classNames from 'classnames';
import React from 'react';

export const AlertFooter = ({
children,
className,
...otherProps
}: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div className={classNames(className, 'alert-footer')} {...otherProps}>
{children}
</div>
);
};
31 changes: 31 additions & 0 deletions packages/clay-core/src/alert/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/

import classNames from 'classnames';
import React from 'react';

import {AlertProps} from './types';

export type Props = React.HTMLAttributes<HTMLDivElement> & {
/**
* Children of the ToastContainer must be a ClayAlert
*/
children?:
| React.ReactElement<AlertProps>
| Array<React.ReactElement<AlertProps>>;
};

export const ToastContainer = ({children, className, ...otherProps}: Props) => {
return (
<div
{...otherProps}
className={classNames(className, 'alert-container container')}
>
<div className="alert-notifications alert-notifications-fixed">
{children}
</div>
</div>
);
};
6 changes: 6 additions & 0 deletions packages/clay-core/src/alert/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* SPDX-FileCopyrightText: © 2024 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/

export * from './Alert';
66 changes: 66 additions & 0 deletions packages/clay-core/src/alert/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* SPDX-FileCopyrightText: © 2024 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/

export type DisplayType =
| 'danger'
| 'info'
| 'secondary'
| 'success'
| 'warning';

export type AlertProps = Omit<React.HTMLAttributes<HTMLDivElement>, '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';
};
84 changes: 84 additions & 0 deletions packages/clay-core/src/badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
* 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<HTMLSpanElement> & {
/**
* 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<HTMLSpanElement, Props>(
(
{
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 (
<span
{...otherProps}
className={classNames(
'badge',
`badge-${displayType}`,
className,
{
'badge-translucent': translucent,
'clay-dark': dark,
}
)}
ref={ref}
>
<span className="badge-item badge-item-expand">{label}</span>
</span>
);
}
);

Badge.displayName = 'ClayBadge';
6 changes: 6 additions & 0 deletions packages/clay-core/src/badge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* SPDX-FileCopyrightText: © 2024 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: BSD-3-Clause
*/

export * from './Badge';