@dtdot/lego 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/FloatingActionButton/FloatingActionButton.component.d.ts +10 -1
- package/build/components/FloatingActionButton/FloatingActionButton.component.js +21 -6
- package/build/components/FloatingActionButton/_FloatingActionButton.context.d.ts +12 -2
- package/build/components/FloatingActionButton/_FloatingActionButton.context.js +2 -1
- package/build/components/FloatingActionButton/_FloatingActionButton.internal.d.ts +13 -1
- package/build/components/FloatingActionButton/_FloatingActionButton.internal.js +89 -15
- package/build/components/FloatingActionButton/_FloatingActionButton.provider.js +18 -6
- package/package.json +1 -1
|
@@ -5,10 +5,19 @@ interface FloatingActionButtonProps {
|
|
|
5
5
|
'icon': IconDefinition;
|
|
6
6
|
'onClick': () => void;
|
|
7
7
|
'variant'?: ColourVariant;
|
|
8
|
+
'label'?: string;
|
|
8
9
|
'data-testid'?: string;
|
|
9
10
|
}
|
|
10
11
|
declare const FloatingActionButton: {
|
|
11
|
-
({ icon, onClick, variant, "data-testid": dataTestId, }: FloatingActionButtonProps): JSX.Element | null;
|
|
12
|
+
({ icon, onClick, variant, label, "data-testid": dataTestId, }: FloatingActionButtonProps): JSX.Element | null;
|
|
12
13
|
Provider: ({ children }: import("./_FloatingActionButton.provider").FloatingActionButtonProviderProps) => JSX.Element;
|
|
14
|
+
Secondary: ({ icon, onClick, variant, label, "data-testid": dataTestId, }: SecondaryFloatingActionButtonProps) => null;
|
|
13
15
|
};
|
|
16
|
+
interface SecondaryFloatingActionButtonProps {
|
|
17
|
+
'icon': IconDefinition;
|
|
18
|
+
'onClick': () => void;
|
|
19
|
+
'variant'?: ColourVariant;
|
|
20
|
+
'label'?: string;
|
|
21
|
+
'data-testid'?: string;
|
|
22
|
+
}
|
|
14
23
|
export default FloatingActionButton;
|
|
@@ -3,19 +3,34 @@ import FloatingActionButtonContext from './_FloatingActionButton.context';
|
|
|
3
3
|
import FloatingActionButtonProvider from './_FloatingActionButton.provider';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
5
5
|
import FloatingActionButtonInternal from './_FloatingActionButton.internal';
|
|
6
|
-
const FloatingActionButton = ({ icon, onClick, variant = 'primary', 'data-testid': dataTestId, }) => {
|
|
7
|
-
const { contextExists,
|
|
6
|
+
const FloatingActionButton = ({ icon, onClick, variant = 'primary', label, 'data-testid': dataTestId, }) => {
|
|
7
|
+
const { contextExists, setPrimaryButton } = useContext(FloatingActionButtonContext);
|
|
8
8
|
const id = useMemo(() => v4(), []);
|
|
9
9
|
useEffect(() => {
|
|
10
|
-
|
|
10
|
+
setPrimaryButton({ id, icon, onClick, variant, label, dataTestId });
|
|
11
11
|
return () => {
|
|
12
|
-
|
|
12
|
+
setPrimaryButton(undefined);
|
|
13
13
|
};
|
|
14
|
-
}, [icon, onClick, variant, dataTestId,
|
|
14
|
+
}, [icon, onClick, variant, label, dataTestId, setPrimaryButton, id]);
|
|
15
15
|
if (!contextExists) {
|
|
16
|
-
return React.createElement(FloatingActionButtonInternal, { icon: icon, onClick: onClick, variant: variant, "data-testid": dataTestId });
|
|
16
|
+
return (React.createElement(FloatingActionButtonInternal, { icon: icon, onClick: onClick, variant: variant, label: label, "data-testid": dataTestId }));
|
|
17
17
|
}
|
|
18
18
|
return null;
|
|
19
19
|
};
|
|
20
|
+
const SecondaryFloatingActionButton = ({ icon, onClick, variant = 'primary', label, 'data-testid': dataTestId, }) => {
|
|
21
|
+
const { setSecondaryButtons } = useContext(FloatingActionButtonContext);
|
|
22
|
+
const id = useMemo(() => v4(), []);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Register this secondary FAB
|
|
25
|
+
setSecondaryButtons((prev) => [...prev, { id, icon, onClick, variant, label, dataTestId }]);
|
|
26
|
+
return () => {
|
|
27
|
+
// Unregister this secondary FAB
|
|
28
|
+
setSecondaryButtons((prev) => prev.filter((fab) => fab.id !== id));
|
|
29
|
+
};
|
|
30
|
+
}, [icon, onClick, variant, label, dataTestId, setSecondaryButtons, id]);
|
|
31
|
+
// Secondary FABs always return null - rendered by provider
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
20
34
|
FloatingActionButton.Provider = FloatingActionButtonProvider;
|
|
35
|
+
FloatingActionButton.Secondary = SecondaryFloatingActionButton;
|
|
21
36
|
export default FloatingActionButton;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
|
|
3
3
|
import { ColourVariant } from '../../theme/theme.types';
|
|
4
4
|
export interface FabProps {
|
|
@@ -6,11 +6,21 @@ export interface FabProps {
|
|
|
6
6
|
icon: IconDefinition | null;
|
|
7
7
|
onClick: () => void;
|
|
8
8
|
variant?: ColourVariant;
|
|
9
|
+
label?: string;
|
|
10
|
+
dataTestId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SecondaryFabProps {
|
|
13
|
+
id: string;
|
|
14
|
+
icon: IconDefinition;
|
|
15
|
+
onClick: () => void;
|
|
16
|
+
variant?: ColourVariant;
|
|
17
|
+
label?: string;
|
|
9
18
|
dataTestId?: string;
|
|
10
19
|
}
|
|
11
20
|
interface FloatingActionButtonContextProps {
|
|
12
21
|
contextExists: boolean;
|
|
13
|
-
|
|
22
|
+
setPrimaryButton: Dispatch<SetStateAction<FabProps | undefined>>;
|
|
23
|
+
setSecondaryButtons: Dispatch<SetStateAction<SecondaryFabProps[]>>;
|
|
14
24
|
}
|
|
15
25
|
declare const FloatingActionButtonContext: import("react").Context<FloatingActionButtonContextProps>;
|
|
16
26
|
export default FloatingActionButtonContext;
|
|
@@ -6,6 +6,18 @@ interface FloatingActionButtonInternalProps {
|
|
|
6
6
|
'onClick': () => void;
|
|
7
7
|
'variant'?: ColourVariant;
|
|
8
8
|
'data-testid'?: string;
|
|
9
|
+
'label'?: string;
|
|
9
10
|
}
|
|
10
|
-
declare const FloatingActionButtonInternal: (
|
|
11
|
+
declare const FloatingActionButtonInternal: (props: FloatingActionButtonInternalProps) => JSX.Element;
|
|
12
|
+
interface MiniFabInternalProps {
|
|
13
|
+
'icon': IconDefinition;
|
|
14
|
+
'onClick': () => void;
|
|
15
|
+
'variant'?: ColourVariant;
|
|
16
|
+
'label'?: string;
|
|
17
|
+
'bottom': number;
|
|
18
|
+
'staggerDelay': number;
|
|
19
|
+
'data-testid'?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const MiniFabInternal: ({ bottom, staggerDelay, ...rest }: MiniFabInternalProps) => JSX.Element;
|
|
11
22
|
export default FloatingActionButtonInternal;
|
|
23
|
+
export { MiniFabInternal };
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
|
-
import { motion } from 'framer-motion';
|
|
3
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
4
4
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
5
5
|
import getThemeVariantColours from '../../theme/helpers/getThemeVariantColours';
|
|
6
6
|
import MinimalMenuContext from '../MinimalMenu/MinimalMenu.context';
|
|
7
7
|
import zIndexConstants from '../../constants/zIndex.constants';
|
|
8
8
|
import FloatingActionButtonContext from './_FloatingActionButton.context';
|
|
9
|
-
const
|
|
9
|
+
const FAB_SIZES = {
|
|
10
|
+
primary: {
|
|
11
|
+
width: 56,
|
|
12
|
+
height: 56,
|
|
13
|
+
fontSize: 24,
|
|
14
|
+
labelOffset: 68,
|
|
15
|
+
hoverScale: 1.1,
|
|
16
|
+
tapScale: 0.95,
|
|
17
|
+
},
|
|
18
|
+
mini: {
|
|
19
|
+
width: 40,
|
|
20
|
+
height: 40,
|
|
21
|
+
fontSize: 18,
|
|
22
|
+
labelOffset: 52,
|
|
23
|
+
hoverScale: 1.15,
|
|
24
|
+
tapScale: 0.9,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const FabContainer = styled(motion.div) `
|
|
10
28
|
position: fixed;
|
|
11
|
-
bottom: ${(props) =>
|
|
29
|
+
bottom: ${(props) => {
|
|
30
|
+
if (props.bottom !== undefined)
|
|
31
|
+
return `${props.bottom}px`;
|
|
32
|
+
return props.offsetBottom ? '76px' : '20px';
|
|
33
|
+
}};
|
|
12
34
|
right: 20px;
|
|
13
|
-
|
|
14
|
-
|
|
35
|
+
z-index: ${zIndexConstants.floatingActionButton};
|
|
36
|
+
`;
|
|
37
|
+
const FabButton = styled(motion.button) `
|
|
38
|
+
width: ${(props) => FAB_SIZES[props.size].width}px;
|
|
39
|
+
height: ${(props) => FAB_SIZES[props.size].height}px;
|
|
15
40
|
border-radius: 50%;
|
|
16
41
|
background-color: ${(props) => getThemeVariantColours(props.variant, props.theme).main};
|
|
17
42
|
color: ${(props) => getThemeVariantColours(props.variant, props.theme).contrastText};
|
|
@@ -21,25 +46,74 @@ const FloatingButton = styled(motion.button) `
|
|
|
21
46
|
display: flex;
|
|
22
47
|
justify-content: center;
|
|
23
48
|
align-items: center;
|
|
24
|
-
font-size:
|
|
49
|
+
font-size: ${(props) => FAB_SIZES[props.size].fontSize}px;
|
|
25
50
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
|
26
|
-
z-index: ${zIndexConstants.floatingActionButton};
|
|
27
51
|
|
|
28
52
|
&:hover {
|
|
29
53
|
background-color: ${(props) => getThemeVariantColours(props.variant, props.theme).darker};
|
|
30
54
|
}
|
|
31
55
|
`;
|
|
32
|
-
const
|
|
33
|
-
|
|
56
|
+
const FabLabel = styled(motion.div) `
|
|
57
|
+
position: absolute;
|
|
58
|
+
right: ${(props) => FAB_SIZES[props.size].labelOffset}px;
|
|
59
|
+
top: 50%;
|
|
60
|
+
transform: translateY(-50%);
|
|
61
|
+
|
|
62
|
+
padding: 6px 12px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
|
|
65
|
+
background-color: ${(props) => props.theme.colours.tertiary.main};
|
|
66
|
+
color: ${(props) => props.theme.colours.defaultFont};
|
|
67
|
+
|
|
68
|
+
font-family: ${(props) => props.theme.fonts.default.family};
|
|
69
|
+
font-size: ${(props) => props.theme.fonts.default.size};
|
|
70
|
+
font-weight: ${(props) => props.theme.fonts.default.weight};
|
|
71
|
+
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
box-shadow: ${(props) => props.theme.shadows.medium};
|
|
75
|
+
`;
|
|
76
|
+
const BaseFab = ({ icon, onClick, variant = 'primary', label, size, bottom, offsetBottom, staggerDelay = 0, 'data-testid': dataTestId, }) => {
|
|
77
|
+
const [showLabel, setShowLabel] = React.useState(false);
|
|
34
78
|
const { contextExists } = useContext(FloatingActionButtonContext);
|
|
79
|
+
const sizeConfig = FAB_SIZES[size];
|
|
35
80
|
const variants = {
|
|
36
81
|
hidden: { opacity: 0, scale: 0 },
|
|
37
|
-
visible: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
82
|
+
visible: {
|
|
83
|
+
opacity: 1,
|
|
84
|
+
scale: 1,
|
|
85
|
+
transition: {
|
|
86
|
+
duration: 0.3,
|
|
87
|
+
delay: staggerDelay,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
exit: {
|
|
91
|
+
opacity: 0,
|
|
92
|
+
scale: 0,
|
|
93
|
+
transition: {
|
|
94
|
+
duration: 0.3,
|
|
95
|
+
delay: staggerDelay,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
hover: { scale: sizeConfig.hoverScale },
|
|
99
|
+
tap: { scale: sizeConfig.tapScale },
|
|
41
100
|
};
|
|
42
|
-
|
|
43
|
-
|
|
101
|
+
const labelVariants = {
|
|
102
|
+
hidden: { opacity: 0, x: 10 },
|
|
103
|
+
visible: { opacity: 1, x: 0 },
|
|
104
|
+
};
|
|
105
|
+
return (React.createElement(FabContainer, { bottom: bottom, offsetBottom: offsetBottom, initial: contextExists ? 'hidden' : 'visible', animate: 'visible', exit: 'exit', variants: variants, onHoverStart: () => setShowLabel(true), onHoverEnd: () => setShowLabel(false) },
|
|
106
|
+
React.createElement(FabButton, { animate: 'visible', whileHover: 'hover', whileTap: 'tap', variants: variants, onClick: onClick, variant: variant, size: size, "data-testid": dataTestId },
|
|
107
|
+
React.createElement(FontAwesomeIcon, { icon: icon })),
|
|
108
|
+
label && (React.createElement(AnimatePresence, null, showLabel && (React.createElement(FabLabel, { size: size, initial: 'hidden', animate: 'visible', exit: 'hidden', variants: labelVariants, transition: { type: 'spring', duration: 0.3 } }, label))))));
|
|
109
|
+
};
|
|
110
|
+
const FloatingActionButtonInternal = (props) => {
|
|
111
|
+
const { menuExists, isMobile } = useContext(MinimalMenuContext);
|
|
112
|
+
const { contextExists } = useContext(FloatingActionButtonContext);
|
|
113
|
+
return (React.createElement(BaseFab, { ...props, size: 'primary', offsetBottom: menuExists && isMobile, staggerDelay: contextExists ? 0.2 : 0 }));
|
|
114
|
+
};
|
|
115
|
+
const MiniFabInternal = ({ bottom, staggerDelay, ...rest }) => {
|
|
116
|
+
return React.createElement(BaseFab, { ...rest, size: 'mini', bottom: bottom, staggerDelay: staggerDelay });
|
|
44
117
|
};
|
|
45
118
|
export default FloatingActionButtonInternal;
|
|
119
|
+
export { MiniFabInternal };
|
|
@@ -1,15 +1,27 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
1
|
+
import React, { useContext, useMemo, useState } from 'react';
|
|
2
2
|
import { AnimatePresence } from 'framer-motion';
|
|
3
3
|
import FloatingActionButtonContext from './_FloatingActionButton.context';
|
|
4
|
-
import FloatingActionButtonInternal from './_FloatingActionButton.internal';
|
|
4
|
+
import FloatingActionButtonInternal, { MiniFabInternal } from './_FloatingActionButton.internal';
|
|
5
|
+
import MinimalMenuContext from '../MinimalMenu/MinimalMenu.context';
|
|
5
6
|
const FloatingActionButtonProvider = ({ children }) => {
|
|
6
|
-
const [
|
|
7
|
+
const [primaryFab, setPrimaryFab] = useState();
|
|
8
|
+
const [secondaryFabs, setSecondaryFabs] = useState([]);
|
|
9
|
+
const { menuExists, isMobile } = useContext(MinimalMenuContext);
|
|
7
10
|
const contextVal = useMemo(() => ({
|
|
8
11
|
contextExists: true,
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
setPrimaryButton: setPrimaryFab,
|
|
13
|
+
setSecondaryButtons: setSecondaryFabs,
|
|
14
|
+
}), []);
|
|
11
15
|
return (React.createElement(FloatingActionButtonContext.Provider, { value: contextVal },
|
|
12
16
|
children,
|
|
13
|
-
React.createElement(AnimatePresence, null,
|
|
17
|
+
React.createElement(AnimatePresence, null,
|
|
18
|
+
primaryFab?.icon && (React.createElement(FloatingActionButtonInternal, { key: primaryFab.id, icon: primaryFab.icon, onClick: primaryFab.onClick, variant: primaryFab.variant, label: primaryFab.label, "data-testid": primaryFab.dataTestId })),
|
|
19
|
+
secondaryFabs.map((fab, index) => {
|
|
20
|
+
const offsetBottom = menuExists && isMobile;
|
|
21
|
+
const baseOffset = offsetBottom ? 76 : 20;
|
|
22
|
+
const bottom = baseOffset + 56 + 8 + index * (40 + 10);
|
|
23
|
+
const staggerDelay = 0.2 + 0.05 * (index + 1);
|
|
24
|
+
return (React.createElement(MiniFabInternal, { key: fab.id, icon: fab.icon, onClick: fab.onClick, variant: fab.variant, label: fab.label, bottom: bottom, staggerDelay: staggerDelay, "data-testid": fab.dataTestId }));
|
|
25
|
+
}))));
|
|
14
26
|
};
|
|
15
27
|
export default FloatingActionButtonProvider;
|