button refactor (#383)

Refactored to be more flexible. Now, it has three types

- solid
- outline
- ghost

and subtypes

- green
- grey
- red
- yellow

It defaults to solid and green when no className is provided. It
concatenates defaults if custom classNames are provided.
This commit is contained in:
Red J Adaya 2024-09-17 13:23:05 +08:00 committed by GitHub
parent dbdcc49ff2
commit 09f4616ae0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 190 additions and 94 deletions

View File

@ -319,7 +319,7 @@ const ConnStatusOverlay = React.memo(
showReconnect = false; showReconnect = false;
} }
let reconDisplay = null; let reconDisplay = null;
let reconClassName = "outlined"; let reconClassName = "outlined grey";
if (width && width < 350) { if (width && width < 350) {
reconDisplay = <i className="fa-sharp fa-solid fa-rotate-right"></i>; reconDisplay = <i className="fa-sharp fa-solid fa-rotate-right"></i>;
reconClassName = clsx(reconClassName, "font-size-12 vertical-padding-5 horizontal-padding-6"); reconClassName = clsx(reconClassName, "font-size-12 vertical-padding-5 horizontal-padding-6");

View File

@ -5,8 +5,8 @@
// override default button appearance // override default button appearance
border: 1px solid transparent; border: 1px solid transparent;
outline: 1px solid transparent; outline: 1px solid transparent;
border: none;
background: var(--accent-color);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
padding-top: 8px; padding-top: 8px;
@ -21,75 +21,128 @@
white-space: nowrap; white-space: nowrap;
user-select: none; user-select: none;
font-size: 14px; font-size: 14px;
color: var(--secondary-text-color);
font-weight: normal; font-weight: normal;
opacity: 0.8;
i { &:hover {
fill: var(--main-text-color); opacity: 1;
} }
&.primary:not(.ghost), &.solid {
&.secondary:not(.ghost) { &.green {
color: var(--button-primary-text-color); color: var(--button-green-text-color);
background: var(--accent-color); background-color: var(--button-green-bg);
&:hover { border: 1px solid var(--button-green-border-color);
color: var(--main-text-color); &:hover {
color: var(--button-green-text-color);
}
} }
i { &.grey {
fill: var(--main-text-color); background-color: var(--button-grey-bg);
border: 1px solid var(--button-grey-border-color);
color: var(--button-grey-text-color);
&:hover {
color: var(--button-grey-text-color);
}
} }
}
&.primary.danger { &.red {
background: var(--error-color); background-color: var(--button-red-bg);
} border: 1px solid var(--button-red-border-color);
color: var(--button-red-text-color);
&.primary.warning { &:hover {
background: #e6ba1e; color: var(--button-red-text-color);
} }
&.primary.ghost {
background: none;
i {
fill: var(--accent-color);
} }
}
&.primary.ghost.danger { &.yellow {
i { color: var(--button-yellow-text-color);
fill: var(--app-error-color); background-color: var(--button-yellow-bg);
border: 1px solid var(--button-yellow-border-color);
&:hover {
color: var(--button-yellow-text-color);
}
} }
} }
&.secondary,
&.link-button {
background: var(--highlight-bg-color);
}
&.secondary.ghost {
background: none;
&:hover {
color: 1px solid var(--main-text-color);
}
}
&.secondary.danger {
color: var(--error-color);
}
&.outlined { &.outlined {
background: none; background-color: transparent;
border: 1px solid var(--secondary-text-color); &.green {
color: var(--button-green-bg);
border: 1px solid var(--button-green-bg);
&:hover {
color: var(--button-green-bg);
}
}
&:hover { &.grey {
border: 1px solid var(--main-text-color); border: 1px solid var(--button-grey-border-color);
color: var(--button-grey-text-color);
&:hover {
color: var(--button-grey-text-color);
}
}
&.red {
border: 1px solid var(--button-red-bg);
color: var(--button-red-bg);
&:hover {
color: var(--button-red-bg);
}
}
&.yellow {
color: var(--button-yellow-bg);
border: 1px solid var(--button-yellow-bg);
&:hover {
color: var(--button-yellow-bg);
}
}
}
&.ghost {
background-color: transparent;
padding-top: 8px;
padding-bottom: 8px;
padding-left: 8px;
padding-right: 8px;
&.green {
border: none;
color: var(--button-green-bg);
&:hover {
color: var(--button-green-bg);
}
}
&.grey {
border: none;
color: var(--button-grey-text-color);
&:hover {
color: var(--button-grey-text-color);
}
}
&.red {
border: none;
color: var(--button-red-bg);
&:hover {
color: var(--button-red-bg);
}
}
&.yellow {
border: none;
color: var(--button-yellow-bg);
&:hover {
color: var(--button-yellow-bg);
}
} }
} }
&.disabled { &.disabled {
cursor: default;
opacity: 0.5; opacity: 0.5;
} }
@ -99,9 +152,8 @@
outline-offset: 2px; outline-offset: 2px;
} }
/* // customs styles here
* customs styles here
*/
&.border-radius-2 { &.border-radius-2 {
border-radius: 4px; border-radius: 4px;
} }

View File

@ -1,8 +1,5 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import clsx from "clsx"; import clsx from "clsx";
import React from "react"; import { Children, isValidElement, memo } from "react";
import "./button.less"; import "./button.less";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@ -11,15 +8,25 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode; children?: React.ReactNode;
} }
const Button = React.memo(({ className = "primary", children, disabled, ...props }: ButtonProps) => { const Button = memo(({ className = "", children, disabled, ...props }: ButtonProps) => {
const hasIcon = React.Children.toArray(children).some( const hasIcon = Children.toArray(children).some(
(child) => React.isValidElement(child) && (child as React.ReactElement).type === "svg" (child) => isValidElement(child) && (child as React.ReactElement).type === "svg"
); );
// Check if the className contains any of the categories: solid, outlined, or ghost
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
// If no category is present, default to 'solid'
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;
// Check if the className contains any of the color options: green, grey, red, or yellow
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
// If no color is present, default to 'green'
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;
return ( return (
<button <button
tabIndex={disabled ? -1 : 0} tabIndex={disabled ? -1 : 0}
className={clsx("button", className, { className={clsx("button", finalClassName, {
disabled, disabled,
hasIcon, hasIcon,
})} })}

View File

@ -1,12 +1,21 @@
// Copyright 2024, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
.button { &.link-button {
&.link-button { text-decoration: none;
text-decoration: none;
.button-inner { .button-inner {
padding: 8px 12px; display: flex;
border-radius: 6px;
padding: 8px 12px;
background-color: var(--button-grey-bg);
border: 1px solid var(--button-grey-border-color);
color: var(--button-grey-text-color);
i {
margin-right: 4px;
}
&:hover {
color: var(--button-grey-text-color);
} }
} }
} }

View File

@ -24,8 +24,8 @@ interface LinkButtonProps {
const LinkButton = ({ leftIcon, rightIcon, children, className, ...rest }: LinkButtonProps) => { const LinkButton = ({ leftIcon, rightIcon, children, className, ...rest }: LinkButtonProps) => {
return ( return (
<a {...rest} className="button link-button"> <a {...rest} className={clsx("link-button", className)}>
<span className={clsx("button-inner", className)}> <span className="button-inner">
{leftIcon && <span className="icon-left">{leftIcon}</span>} {leftIcon && <span className="icon-left">{leftIcon}</span>}
{children} {children}
{rightIcon && <span className="icon-right">{rightIcon}</span>} {rightIcon && <span className="icon-right">{rightIcon}</span>}

View File

@ -45,7 +45,7 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>(
<div className="modal-wrapper"> <div className="modal-wrapper">
{renderBackdrop(onClickBackdrop)} {renderBackdrop(onClickBackdrop)}
<div ref={ref} className={clsx(`modal`, className)}> <div ref={ref} className={clsx(`modal`, className)}>
<Button className="secondary ghost modal-close-btn" onClick={onClose} title="Close (ESC)"> <Button className="grey ghost modal-close-btn" onClick={onClose} title="Close (ESC)">
<i className="fa-sharp fa-solid fa-xmark"></i> <i className="fa-sharp fa-solid fa-xmark"></i>
</Button> </Button>
<div className="content-wrapper"> <div className="content-wrapper">
@ -81,15 +81,11 @@ const ModalFooter = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }:
return ( return (
<footer className="modal-footer"> <footer className="modal-footer">
{onCancel && ( {onCancel && (
<Button className="secondary ghost" onClick={onCancel}> <Button className="grey ghost" onClick={onCancel}>
{cancelLabel} {cancelLabel}
</Button> </Button>
)} )}
{onOk && ( {onOk && <Button onClick={onOk}>{okLabel}</Button>}
<Button className="primary" onClick={onOk}>
{okLabel}
</Button>
)}
</footer> </footer>
); );
}; };

View File

@ -205,11 +205,7 @@ const Tab = React.memo(
> >
{tabData?.name} {tabData?.name}
</div> </div>
<Button <Button className="grey ghost close" onClick={onClose} onMouseDown={handleMouseDownOnClose}>
className="secondary ghost close"
onClick={onClose}
onMouseDown={handleMouseDownOnClose}
>
<i className="fa fa-solid fa-xmark" /> <i className="fa fa-solid fa-xmark" />
</Button> </Button>
</div> </div>

View File

@ -69,7 +69,7 @@
// xterm-decoration-overview-ruler: 8 // xterm-decoration-overview-ruler: 8
// xterm-decoration-top: 2 // xterm-decoration-top: 2
/* modal colors */ // modal colors
--modal-bg-color: #232323; --modal-bg-color: #232323;
--modal-header-bottom-border-color: rgba(241, 246, 243, 0.15); --modal-header-bottom-border-color: rgba(241, 246, 243, 0.15);
--modal-border-color: rgba(255, 255, 255, 0.12); /* toggle colors */ --modal-border-color: rgba(255, 255, 255, 0.12); /* toggle colors */
@ -78,15 +78,10 @@
--toggle-thumb-color: var(--main-text-color); --toggle-thumb-color: var(--main-text-color);
--toggle-checked-bg-color: var(--accent-color); --toggle-checked-bg-color: var(--accent-color);
/* link color */ // link color
--link-color: #58c142; --link-color: #58c142;
/* button colors */ // form colors
--button-primary-color: #58c142;
--button-focus-border-color: rgba(88, 193, 66, 0.8);
--button-primary-text-color: #000000;
/* form colors */
--form-element-border-color: rgba(241, 246, 243, 0.15); --form-element-border-color: rgba(241, 246, 243, 0.15);
--form-element-bg-color: var(--main-bg-color); --form-element-bg-color: var(--main-bg-color);
--form-element-text-color: var(--main-text-color); --form-element-text-color: var(--main-text-color);
@ -106,4 +101,45 @@
--conn-icon-color-8: #58c142; --conn-icon-color-8: #58c142;
--bulb-color: rgb(255, 221, 51); --bulb-color: rgb(255, 221, 51);
// term colors (16 + 6) form the base terminal theme
// for consistency these colors should be used by plugins/applications
--term-black: #000000;
--term-red: #cc0000;
--term-green: #4e9a06;
--term-yellow: #c4a000;
--term-blue: #3465a4;
--term-magenta: #bc3fbc;
--term-cyan: #06989a;
--term-white: #d0d0d0;
--term-bright-black: #555753;
--term-bright-red: #ef2929;
--term-bright-green: #58c142;
--term-bright-yellow: #fce94f;
--term-bright-blue: #32afff;
--term-bright-magenta: #ad7fa8;
--term-bright-cyan: #34e2e2;
--term-bright-white: #e7e7e7;
--term-gray: #8b918a; // not an official terminal color
--term-cmdtext: #ffffff;
--term-foreground: #d3d7cf;
--term-background: #000000;
--term-selection-background: #ffffff60;
--term-cursor-accent: #000000;
// button colors
--button-focus-border-color: rgba(88, 193, 66, 0.5);
--button-green-text-color: var(--term-black);
--button-green-bg: var(--term-green);
--button-green-border-color: rgb(26, 52, 21);
--button-grey-text-color: var(--main-text-color);
--button-grey-bg: rgba(255, 255, 255, 0.04);
--button-grey-border-color: rgba(255, 255, 255, 0.1);
--button-red-text-color: var(--main-text-color);
--button-red-bg: var(--term-red);
--button-red-border-color: #ff1818;
--button-yellow-text-color: var(--term-black);
--button-yellow-bg: var(--term-yellow);
--button-yellow-border-color: #fbd93f;
} }

View File

@ -289,9 +289,9 @@ export class PreviewModel implements ViewModel {
onClick: () => this.updateOpenFileModalAndError(true), onClick: () => this.updateOpenFileModalAndError(true),
}, },
]; ];
let saveClassName = "secondary"; let saveClassName = "grey";
if (get(this.newFileContent) !== null) { if (get(this.newFileContent) !== null) {
saveClassName = "primary"; saveClassName = "green";
} }
if (isCeView) { if (isCeView) {
viewTextChildren.push({ viewTextChildren.push({
@ -307,7 +307,7 @@ export class PreviewModel implements ViewModel {
elemtype: "textbutton", elemtype: "textbutton",
text: "Preview", text: "Preview",
className: className:
"secondary border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500", "grey border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500",
onClick: () => this.setEditMode(false), onClick: () => this.setEditMode(false),
}); });
} }
@ -316,7 +316,7 @@ export class PreviewModel implements ViewModel {
elemtype: "textbutton", elemtype: "textbutton",
text: "Edit", text: "Edit",
className: className:
"secondary border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500", "grey border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500",
onClick: () => this.setEditMode(true), onClick: () => this.setEditMode(true),
}); });
} }