Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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