Skip to content

Commit f064a89

Browse files
Merge pull request #814 from fahad-aot/feature/fwf-5210-5211-Button-component
feature/fwf-5210-5211: Added button and icon-only button
2 parents 1059428 + 2cb23b9 commit f064a89

File tree

4 files changed

+205
-1
lines changed

4 files changed

+205
-1
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useState } from "react";
2+
import { useTranslation } from "react-i18next";
3+
4+
type Variant = "primary" | "secondary";
5+
6+
interface CustomButtonProps {
7+
variant?: Variant;
8+
loading?: boolean;
9+
ariaLabel?: string;
10+
label?: string;
11+
disabled?: boolean;
12+
selected?: boolean; // for controlled selected state
13+
onClick?: () => void;
14+
name?: string;
15+
dataTestId?: string;
16+
icon?: React.ReactNode;
17+
className?: string;
18+
iconOnly?: boolean;
19+
fullWidth?: boolean;
20+
}
21+
22+
export const V8CustomButton: React.FC<CustomButtonProps> = ({
23+
label = "",
24+
variant = "secondary",
25+
loading = false,
26+
disabled = false,
27+
ariaLabel = "",
28+
selected = false,
29+
name,
30+
dataTestId,
31+
onClick,
32+
icon,
33+
className = "",
34+
iconOnly = false,
35+
fullWidth = false,
36+
...props
37+
}) => {
38+
const { t } = useTranslation();
39+
const [isPressed, setIsPressed] = useState(false);
40+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
41+
if (disabled || loading) {
42+
e.preventDefault();
43+
e.stopPropagation();
44+
return;
45+
}
46+
if (onClick) onClick();
47+
};
48+
return (
49+
<button
50+
className={[
51+
"custom-button",
52+
`custom-button--${variant}`,
53+
loading ? "is-loading" : "",
54+
disabled ? "is-disabled" : "",
55+
selected ? "is-selected" : "",
56+
isPressed ? "is-selected" : "", // apply selected style on mousedown
57+
fullWidth ? "w-100" : "",
58+
iconOnly ? "icon-only" : "",
59+
className
60+
]
61+
.filter(Boolean) //filter to prevent empty strings
62+
.join(" ")}
63+
type="button"
64+
disabled={disabled}
65+
onClick={handleClick}
66+
onMouseDown={() => setIsPressed(true)} // press start
67+
onMouseUp={() => setIsPressed(false)} // press end
68+
onMouseLeave={() => setIsPressed(false)} // cancel if moved away
69+
name={name}
70+
data-testid={dataTestId}
71+
aria-disabled={disabled}
72+
aria-label={ariaLabel}
73+
tabIndex={0}
74+
{...props}
75+
>
76+
{loading && <span className="button-spinner" aria-hidden="true"></span>}
77+
{!loading && icon && icon}
78+
{!iconOnly && t(label)}
79+
</button>
80+
);
81+
};

forms-flow-components/src/components/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ export * from "./CustomComponents/BpmnDiagramView";
3838
export * from "./CustomComponents/SubmissionHistoryWithViewButton";
3939
export * from "./CustomComponents/VariableModal";
4040
export * from "./CustomComponents/FormComponent";
41-
export * from "./CustomComponents/StepperComponent";
41+
export * from "./CustomComponents/StepperComponent";
42+
export * from "./CustomComponents/CustomButton";

forms-flow-theme/scss/index.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@
4040
@import "./external/toastify.scss";
4141
@import "~bootstrap/scss/bootstrap";
4242

43+
//v8 Custom component style override
44+
@import "./v8-scss/button";
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
.custom-button {
2+
display: inline-flex;
3+
gap: 5px; /* TBD */
4+
align-items: center;
5+
justify-content: center;
6+
min-width: 80px;
7+
min-height: 40px;
8+
padding: 11px 22px;
9+
cursor: pointer;
10+
position: relative;
11+
border-radius: 25px;
12+
font-weight: 400;
13+
font-size: 15px;
14+
line-height: 100%;
15+
transition: background 0.13s, border-color 0.13s, color 0.13s, box-shadow 0.13s;
16+
17+
&.icon-only {
18+
padding: 11px !important;
19+
min-width: 0;
20+
min-height: 0;
21+
}
22+
23+
/* Disabled state (common fallback for any button) */
24+
&.is-disabled,
25+
&[disabled] {
26+
cursor: not-allowed;
27+
pointer-events: none;
28+
color: #E5E5E5;
29+
background: #FCFCFC;
30+
border-color: #E5E5E5;
31+
32+
/* Disabled icons */
33+
svg path {
34+
stroke: #E5E5E5;
35+
}
36+
}
37+
38+
/* Loading state */
39+
&.is-loading {
40+
.button-spinner {
41+
display: inline-block;
42+
}
43+
.button-text {
44+
opacity: 0.5;
45+
}
46+
}
47+
48+
/* Primary button */
49+
&.custom-button--primary {
50+
background: #FCFCFC;
51+
border: 1px solid #B8ABFF80;
52+
color: #4A4A4A;
53+
54+
&:hover:not(.is-disabled):not(.is-loading) {
55+
border-color: #B8ABFF;
56+
background: #FCFCFC;
57+
}
58+
59+
&.is-selected {
60+
border-color: #B8ABFF;
61+
background: #B8ABFF33;
62+
}
63+
64+
&.is-disabled {
65+
border-color: #B8ABFF40;
66+
}
67+
68+
&.is-loading:hover {
69+
border-color: #B8ABFF;
70+
background: #FCFCFC;
71+
}
72+
73+
.button-spinner {
74+
border: 0.2em solid #bcb6f5;
75+
border-top: 0.2em solid #8969f2;
76+
}
77+
}
78+
79+
/* Secondary button */
80+
&.custom-button--secondary {
81+
background: #FCFCFC;
82+
border: 1px solid #E5E5E5;
83+
color: #4A4A4A;
84+
85+
&:hover:not(.is-disabled):not(.is-loading) {
86+
border-color: #525254;
87+
}
88+
89+
&.is-selected {
90+
border-color: #525254;
91+
background: #EDEDED;
92+
}
93+
94+
&.is-loading:hover {
95+
border-color: #525254;
96+
background: #FCFCFC;
97+
}
98+
99+
.button-spinner {
100+
border: 0.2em solid #C5C5C5;
101+
border-top: 0.2em solid #525254;
102+
}
103+
}
104+
105+
/* Spinner base */
106+
.button-spinner {
107+
display: none;
108+
width: 1em;
109+
height: 1em;
110+
border-radius: 50%;
111+
animation: button-spin 0.7s linear infinite;
112+
vertical-align: middle;
113+
}
114+
}
115+
116+
@keyframes button-spin {
117+
to {
118+
transform: rotate(360deg);
119+
}
120+
}

0 commit comments

Comments
 (0)