Skip to content

Commit

Permalink
Button rework (#36)
Browse files Browse the repository at this point in the history
* adding fade upgrade and fade animatin to the modal

* changing enums to types

---------

Co-authored-by: Nick Grato <nickgrato@ngrato-27545.local>
  • Loading branch information
nickgrato and Nick Grato committed Jul 12, 2023
1 parent 86cb95b commit 8e3f4d4
Show file tree
Hide file tree
Showing 25 changed files with 354 additions and 306 deletions.
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ Here is an example of a basic app using Lilypad's `Button` component:

```jsx
import * as React from 'react';
import { Button, ButtonCategoriesE } from '@mozilla/lilypad-ui';
import { Button } from '@mozilla/lilypad-ui';

function App() {
return (
<Button
category={ButtonCategoriesE.PRIMARY_SOLID}
icon="alert-circle"
text="click me!!"
/>
<Button category="primary_solid" icon="alert-circle" text="click me!!" />
);
}
```
Expand Down
91 changes: 53 additions & 38 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,27 @@ import Icon, { IconT } from '../Icon/Icon';
export type ButtonT = 'button' | 'submit' | 'reset';

/**
* Enums are formatted in snake_case so that we can use the mapped
* type is formatted in snake_case so that we can use the mapped
* value as a SCSS value.
*/
export enum ButtonCategoriesE {
PRIMARY_SOLID = 'primary_solid',
PRIMARY_OUTLINE = 'primary_outline',
PRIMARY_CLEAR = 'primary_clear',
SECONDARY_SOLID = 'secondary_solid',
SECONDARY_OUTLINE = 'secondary_outline',
SECONDARY_CLEAR = 'secondary_clear',
}
export type ButtonCategoriesT =
| 'primary_solid'
| 'primary_outline'
| 'primary_clear'
| 'secondary_solid'
| 'secondary_outline'
| 'secondary_clear';

export enum ButtonSizesE {
SMALL = 'small',
MEDIUM = 'medium',
LARGE = 'large',
}
export type ButtonSizesT = 'small' | 'medium' | 'large';

export type ButtonPropsT = {
active?: boolean;
id?: string;
text?: string;
label: string;
label?: string;
type?: ButtonT;
category?: ButtonCategoriesE;
size?: ButtonSizesE;
category?: ButtonCategoriesT;
size?: ButtonSizesT;
disabled?: boolean;
icon?: IconT;
iconPlacedRight?: boolean;
Expand All @@ -40,14 +35,40 @@ export type ButtonPropsT = {
classProp?: string;
};

type ButtonIconT = {
icon: IconT | undefined;
hasText: boolean;
position: 'left' | 'right';
};

const ButtonIcon = ({ icon, hasText, position = 'left' }: ButtonIconT) => {
if (!icon) {
return <></>;
}

const styles = {
left: 'mr-10',
right: 'ml-10',
};

return (
<Icon
name={icon}
color="currentColor"
size={22}
classProp={hasText ? styles[position] : ''}
/>
);
};

const Button = ({
active,
id,
text,
label,
type = 'button',
category = ButtonCategoriesE.PRIMARY_SOLID,
size = ButtonSizesE.MEDIUM,
category = 'primary_solid',
size = 'medium',
disabled,
icon,
onClick,
Expand All @@ -58,29 +79,23 @@ const Button = ({
}: ButtonPropsT) => {
const content = (
<>
{/* Left Icon */}
{icon && !iconPlacedRight ? (
<Icon
name={icon}
color="currentColor"
size={22}
classProp={text ? 'mr-10' : ''}
{!iconPlacedRight && (
<ButtonIcon
icon={icon}
hasText={Boolean(text?.length)}
position="left"
/>
) : (
''
)}

{/* Button Text */}
{text}
{/* Right Icon */}
{icon && iconPlacedRight ? (
<Icon
name={icon}
color="currentColor"
size={22}
classProp={text ? 'ml-10' : ''}

{iconPlacedRight && (
<ButtonIcon
icon={icon}
hasText={Boolean(text?.length)}
position="right"
/>
) : (
''
)}
</>
);
Expand Down Expand Up @@ -112,7 +127,7 @@ const Button = ({
<button
className={className}
id={id}
aria-label={label}
aria-label={label ? label : text}
type={type}
disabled={disabled}
onClick={onClick}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default, type ButtonT, ButtonCategoriesE, ButtonSizesE } from './Button';
export { default, type ButtonT, ButtonCategoriesT, ButtonSizesT } from './Button';

6 changes: 3 additions & 3 deletions src/components/CopyButton/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react';
import styles from './CopyButton.module.scss';
import Button, { ButtonCategoriesE, ButtonSizesE } from '../Button';
import Button from '../Button';

export type CopyButtonPropsT = {
onClick?: Function;
Expand Down Expand Up @@ -43,8 +43,8 @@ const CopyButton = ({
classProp={classProp}
onClick={handleClick}
icon="copy"
size={ButtonSizesE.SMALL}
category={ButtonCategoriesE.PRIMARY_CLEAR}
size="small"
category="primary_clear"
/>
)}
</>
Expand Down
36 changes: 12 additions & 24 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
forwardRef,
} from 'react';
import styles from './Dropdown.module.scss';
import FadeIn from '../util/FadeIn';
import FadeIn from '../FadeIn';

export type dropdownT = {
closeDropdown: Function;
Expand All @@ -30,7 +30,6 @@ const Dropdown = forwardRef(
ref
) => {
const [isOpen, setIsOpen] = useState(false);
const [isVisible, setIsVisible] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);

/**
Expand Down Expand Up @@ -70,39 +69,28 @@ const Dropdown = forwardRef(
document.removeEventListener('mousedown', checkIfClickedOutside);
}, [isOpen]);

const handleOpen = useCallback(() => {
setIsVisible((state) => !state);
const onToggleClick = useCallback(() => {
setIsOpen((state) => !state);
}, []);

const handleClose = useCallback(() => {
setIsOpen((state) => !state);
}, []);

const handleOnComplete = useCallback(() => {
if (!isOpen) setIsVisible(false);
}, [isOpen]);

return (
<div
ref={containerRef}
className={`${classProp} ${styles.dropdown_wrapper}`}
>
{/* CTA */}
<div onClick={isVisible ? handleClose : handleOpen}>{cta}</div>
<div onClick={onToggleClick}>{cta}</div>

{/* Dropdown Custom Content */}
<FadeIn isVisible={isOpen} onComplete={handleOnComplete}>
{isVisible && (
<div
style={{ width: `${width}px` }}
className={`${styles.content_wrapper} ${
alignment === 'right' ? styles.right : styles.left
}`}
>
{content}
</div>
)}
<FadeIn visible={isOpen}>
<div
style={{ width: `${width}px` }}
className={`${styles.content_wrapper} ${
alignment === 'right' ? styles.right : styles.left
}`}
>
{content}
</div>
</FadeIn>
</div>
);
Expand Down
132 changes: 132 additions & 0 deletions src/components/FadeIn/FadeIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, {
PropsWithChildren,
useEffect,
useState,
useCallback,
Children,
ReactNode,
} from 'react';

/**
* FADE IN
*/
type FadeInPropsT = {
visible: boolean;
animation?: string;
onComplete?: () => void;
children: ReactNode;
classProp?: string;
};

const FadeIn = ({
visible = true,
onComplete,
animation,
children,
classProp = '',
}: FadeInPropsT) => {
const [isOpen, setIsOpen] = useState<boolean>(visible);
const [isVisible, setIsVisible] = useState<boolean>(visible);
const arrayChildren = Children.toArray(children);

/**
* Watch visible prop and updates state
*/
useEffect(() => {
// only update setIsVisible on show
visible && setIsVisible(visible);
setIsOpen(visible);
}, [visible]);

/**
* Animation has completed call back
*/
const handleOnComplete = useCallback(() => {
if (!isOpen) setIsVisible(false);
onComplete && onComplete();
}, [isOpen]);

return (
<FadeContents
isVisible={isOpen}
onComplete={handleOnComplete}
animation={animation}
classProp={classProp}
>
{Children.map(arrayChildren, (child, i) => {
return isVisible && <>{child}</>;
})}
</FadeContents>
);
};

/**
* FADE IN CONTENTS
*/

type FadeContentsPropsT = {
isVisible: boolean;
animation?: string;
onComplete: () => void;
children: ReactNode;
classProp?: string;
};

const FadeContents = ({
isVisible,
onComplete,
children,
animation = 'translateY(20px)',
classProp = '',
}: PropsWithChildren<FadeContentsPropsT>) => {
const [maxIsVisible, setMaxIsVisible] = useState(0);
const arrayChildren = Children.toArray(children);

/**
* Track fade in children and fade
* them in accordingly
*/
useEffect(() => {
// Get Number of children to fade in
let count = Children.count(arrayChildren);

// Animate all children out
if (!isVisible) count = 0;

// Fire (optional) callback when all visible
if (count === maxIsVisible) {
const timeout = setTimeout(() => {
if (onComplete) onComplete();
}, 500);
return () => clearTimeout(timeout);
}

// Increment or decrement MaxIsVisible
const addOrSubtractOne = count > maxIsVisible ? 1 : -1;
const timeout = setTimeout(() => {
setMaxIsVisible((state) => state + addOrSubtractOne);
}, 50);

return () => clearTimeout(timeout);
}, [maxIsVisible, isVisible, onComplete, arrayChildren]);

return (
<div className={`${classProp}`}>
{Children.map(arrayChildren, (child, i) => {
return (
<div
style={{
transition: `opacity 500ms, transform 500ms`,
transform: maxIsVisible > i ? 'none' : animation,
opacity: maxIsVisible > i ? 1 : 0,
}}
>
{child}
</div>
);
})}
</div>
);
};

export default FadeIn;
1 change: 1 addition & 0 deletions src/components/FadeIn/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './FadeIn';
Loading

0 comments on commit 8e3f4d4

Please sign in to comment.