Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,10 @@
"packages/*",
"packages/react-integration/demo-app-ts"
]
},
"dependencies": {
"@patternfly/react-component-groups": "^6.2.1",
"clsx": "^2.1.1",
"react-jss": "^10.10.0"
}
}
56 changes: 56 additions & 0 deletions packages/react-core/src/demos/Animations/Animations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
id: Motion
section: design-foundations
source: react-demos
---

import { Fragment, useRef, useState, useEffect, useCallback } from 'react';

import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon';
import BarsIcon from '@patternfly/react-icons/dist/js/icons/bars-icon';
import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon';
import PowerOffIcon from '@patternfly/react-icons/dist/esm/icons/power-off-icon';
import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
import pfLogo from '@patternfly/react-core/src/demos/assets/PF-HorizontalLogo-Color.svg';
import MultiContentCard from "@patternfly/react-component-groups/dist/dynamic/MultiContentCard";
import { ArrowRightIcon, LockIcon, PortIcon, CubeIcon, AutomationIcon, ExclamationCircleIcon, CheckCircleIcon, ExclamationTriangleIcon, HamburgerIcon, TimesIcon} from '@patternfly/react-icons';
import { createUseStyles } from 'react-jss';
import clsx from 'clsx';
import UnpluggedIcon from '@patternfly/react-icons/dist/esm/icons/unplugged-icon';
import l_gallery_GridTemplateColumns_min from '@patternfly/react-tokens/dist/esm/l_gallery_GridTemplateColumns_min';
import {applicationsData} from './examples/ResourceTableData.jsx';
import SkeletonTable from "@patternfly/react-component-groups/dist/dynamic/SkeletonTable";
import t_global_text_color_subtle from '@patternfly/react-tokens/dist/esm/t_global_text_color_subtle';
import { AnimationsOverview } from '@patternfly/react-core/dist/esm/demos/Animations/AnimationsOverview';
import { AnimationsNotificationsDrawer } from '@patternfly/react-core/dist/esm/demos/Animations/AnimationsNotificationsDrawer';
import { AnimationsHeaderToolbar } from '@patternfly/react-core/dist/esm/demos/Animations/AnimationsHeaderToolbar';
import { AnimationsStartTourModal } from '@patternfly/react-core/dist/esm/demos/Animations/AnimationsStartTourModal';
import { AnimationsEndTourModal } from '@patternfly/react-core/dist/esm/demos/Animations/AnimationsEndTourModal';
import { AnimationsCreateDatabaseForm } from '@patternfly/react-core/dist/esm/demos/Animations/AnimationsCreateDatabaseForm';
import { GuidedTourProvider, useGuidedTour } from '@patternfly/react-core/dist/esm/demos/Animations/GuidedTourContext';
import BoltIcon from '@patternfly/react-icons/dist/esm/icons/bolt-icon';
import { Table, Thead, Tbody, Tr, Th, Td, ExpandableRowContent } from '@patternfly/react-table';
import PendingIcon from '@patternfly/react-icons/dist/esm/icons/pending-icon';
import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon';
import InfoIcon from '@patternfly/react-icons/dist/esm/icons/info-icon';
import ResourcesFullIcon from '@patternfly/react-icons/dist/esm/icons/resources-full-icon';
import openshiftLogo from '../assets/Summit-collage-depoying-openshift-product-icon-RH.png'
import emptyStateLogo from '../assets/Summit-collage-hybrid-cloud-dark-RH.png'




## Demos

Explore the current state of [PatternFly component animations](https://github.com/orgs/patternfly/projects/7/views/66).

To see how our components can now use motion to provide clear feedback and improve usability, this demo guides you through a UI that contains a variety of motion updates, including animated alerts, icons, expansion, and more.

### Animated UI

```js file="./examples/Animations.tsx" isFullscreen
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { useRef, useState, FunctionComponent } from 'react';
import {
AlertGroup,
Alert,
Button,
Form,
FormGroup,
FormHelperText,
FormAlert,
FormGroupLabelHelp,
HelperText,
HelperTextItem,
TextInput,
Popover,
ActionGroup
} from '../..';
import { useGuidedTour } from './GuidedTourContext';

interface Props {
onClose: () => void;
emptyStateLogo?: any;
}

export const AnimationsCreateDatabaseForm: FunctionComponent<Props> = ({ onClose }) => {
// State variables
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// Submit state variables
const [isSuccess, setIsSuccess] = useState(false);
const [actionCompleted, setActionCompleted] = useState(false);
const { renderTourStepElement } = useGuidedTour();

const labelHelpRef = useRef(null);

// Re-introducing the type alias for validation status
type validationStatus = 'success' | 'warning' | 'error' | 'default';

// Reverting useState to infer the type as a generic string
const [isNameValid, setIsNameValid] = useState('default');
const [isPasswordValid, setIsPasswordValid] = useState('default');
const [isEmailValid, setIsEmailValid] = useState('default');

const handleNameChange = (_event: React.FormEvent<HTMLInputElement>, name: string) => {
setName(name);
};

const handleEmailChange = (_event: React.FormEvent<HTMLInputElement>, email: string) => {
setEmail(email);
};

const handlePasswordChange = (_event: React.FormEvent<HTMLInputElement>, password: string) => {
setPassword(password);
};

const validateName = (value: string) => /^[a-z0-9-]+$/.test(value) && value.length > 0;
const validatePassword = (value: string) => value.length >= 12 && /[0-9]/.test(value) && /[A-Z]/.test(value);
const validateEmail = (value: string) => value.includes('@');

const handleNameBlur = () => {
setIsNameValid(validateName(name) ? 'success' : 'error');
};

const handlePasswordBlur = () => {
setIsPasswordValid(validatePassword(password) ? 'success' : 'error');
};

const handleEmailBlur = () => {
setIsEmailValid(validateEmail(email) ? 'success' : 'error');
};

const handleSubmit = () => {
const isNameCurrentValid = validateName(name);
const isPasswordCurrentValid = validatePassword(password);
const isEmailCurrentValid = validateEmail(email);

setIsNameValid(isNameCurrentValid ? 'success' : 'error');
setIsPasswordValid(isPasswordCurrentValid ? 'success' : 'error');
setIsEmailValid(isEmailCurrentValid ? 'success' : 'error');

const allFieldsValid = isNameCurrentValid && isPasswordCurrentValid && isEmailCurrentValid;

setActionCompleted(true);
setIsSuccess(allFieldsValid);
};

const onReset = () => {
setIsNameValid('default');
setIsPasswordValid('default');
setIsEmailValid('default');
};

return renderTourStepElement(
'validationErrors',
<Form isWidthLimited id="create-database-form">
{actionCompleted && isSuccess ? (
<FormAlert>
<AlertGroup hasAnimations isLiveRegion>
<Alert
variant="success"
title="Successfully created database"
isInline
timeout={4000}
timeoutAnimation={4000}
/>
</AlertGroup>
</FormAlert>
) : null}
<FormGroup
label="Database instance name"
labelHelp={
<Popover
triggerRef={labelHelpRef}
headerContent={<div>The name of your database</div>}
bodyContent={
<div>
<p>
The name of your database is used to identify it in the system. It must be unique and cannot be
changed later.
</p>
</div>
}
>
<FormGroupLabelHelp ref={labelHelpRef} aria-label="More info for name field" />
</Popover>
}
isRequired
fieldId="simple-form-name-01"
>
<TextInput
isRequired
type="text"
id="simple-form-name-01"
name="simple-form-name-01"
aria-describedby="simple-form-name-01-helper"
value={name}
onChange={handleNameChange}
onBlur={handleNameBlur}
validated={isNameValid as validationStatus}
/>
{isNameValid === 'error' && (
<FormHelperText>
<HelperText>
<HelperTextItem variant={isNameValid as validationStatus}>
Must contain only lowercase letters, numbers, and hyphens.
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
<FormGroup label="Admin email" isRequired fieldId="simple-form-email-01">
<TextInput
isRequired
type="email"
id="simple-form-email-01"
name="simple-form-email-01"
value={email}
onChange={handleEmailChange}
onBlur={handleEmailBlur}
validated={isEmailValid as validationStatus}
/>
{isEmailValid === 'error' && (
<FormHelperText>
<HelperText>
<HelperTextItem variant={isEmailValid as validationStatus}>
Must be a valid email address containing an @ symbol.
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
<FormGroup label="Admin password" isRequired fieldId="simple-form-password-01">
<TextInput
isRequired
type="password"
id="simple-form-password-01"
name="simple-form-password-01"
value={password}
onChange={handlePasswordChange}
onBlur={handlePasswordBlur}
validated={isPasswordValid as validationStatus}
/>
{isPasswordValid === 'error' && (
<FormHelperText>
<HelperText>
<HelperTextItem variant={isPasswordValid as validationStatus}>
Password must be at least 12 characters and include one uppercase letter and one number.
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
<ActionGroup>
<Button id="create-database-submit" variant="primary" onClick={handleSubmit}>
Submit
</Button>
<Button variant="link" onClick={onClose}>
Cancel
</Button>
<Button className="pf-u-ml-2xl" variant="link" onClick={onReset}>
Reset
</Button>
</ActionGroup>
</Form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { FunctionComponent } from 'react';
import { Button, Content, Modal, ModalBody, ModalFooter, ModalHeader, ModalVariant } from '../../index';
import { useGuidedTour } from './GuidedTourContext';

export const AnimationsEndTourModal: FunctionComponent = () => {
const { onStart, onFinish } = useGuidedTour();

return (
<Modal
variant={ModalVariant.small}
isOpen
onClose={onFinish}
aria-labelledby="guided-tour-title"
aria-describedby="guided-tour-description"
>
<ModalHeader title="This concludes the tour" labelId="guided-tour-title" />
<ModalBody id="guided-tour-description">
<Content component="p">You’ve reached the end of this tour. Thanks for exploring our new animations!</Content>
<Content component="p">
To take the tour again, click <strong>Restart</strong> or refresh this page.
</Content>
</ModalBody>
<ModalFooter>
<Button key="end" variant="primary" onClick={onFinish}>
End tour
</Button>
<Button key="restart" variant="link" onClick={onStart}>
Restart
</Button>
</ModalFooter>
</Modal>
);
};
Loading
Loading