Update Button and implementations

This commit is contained in:
Justin Edmund 2022-12-06 13:16:11 -08:00
parent 3376aa7b75
commit 1e3480e3c0
7 changed files with 284 additions and 208 deletions

View file

@ -1,35 +1,44 @@
.Button { .Button {
align-items: center; align-items: center;
background: transparent; background: var(--button-bg);
border: none; border: none;
border-radius: 6px; border-radius: $input-corner;
color: $grey-55; color: var(--button-text);
display: inline-flex; display: inline-flex;
font-size: $font-button; font-size: $font-button;
font-weight: $normal; font-weight: $normal;
gap: 6px; gap: 6px;
padding: 8px 12px;
&:hover { &:hover,
background: $grey-100; &.Blended:hover {
background: var(--button-bg-hover);
cursor: pointer; cursor: pointer;
color: $grey-15; color: var(--button-text-hover);
.icon svg { .Accessory svg {
fill: $grey-15; fill: var(--button-text-hover);
} }
.icon.stroke svg { .Accessory svg.stroke {
fill: none; fill: none;
stroke: $grey-15; stroke: var(--button-text-hover);
} }
} }
&.Blended {
background: transparent;
}
&.medium {
height: $unit * 5.5;
padding: ($unit * 1.5) $unit-2x;
}
&.destructive:hover { &.destructive:hover {
background: $error; background: $error;
color: $grey-100; color: $grey-100;
.icon svg { .Accessory svg {
fill: $grey-100; fill: $grey-100;
} }
} }
@ -37,7 +46,7 @@
&.save:hover { &.save:hover {
color: #ff4d4d; color: #ff4d4d;
.icon svg { .Accessory svg {
fill: #ff4d4d; fill: #ff4d4d;
stroke: #ff4d4d; stroke: #ff4d4d;
} }
@ -46,7 +55,7 @@
&.save.Active { &.save.Active {
color: #ff4d4d; color: #ff4d4d;
.icon svg { .Accessory svg {
fill: #ff4d4d; fill: #ff4d4d;
stroke: #ff4d4d; stroke: #ff4d4d;
} }
@ -73,13 +82,30 @@
} }
} }
.icon { .Accessory {
margin-top: 2px; $dimension: $unit-2x;
display: flex;
svg { svg {
fill: $grey-55; fill: var(--button-text);
height: 12px; height: $dimension;
width: 12px; width: $dimension;
&.stroke {
fill: none;
stroke: var(--button-text);
}
&.Add {
height: 18px;
width: 18px;
}
&.Check {
height: 22px;
width: 22px;
}
} }
&.check svg { &.check svg {
@ -88,21 +114,12 @@
width: auto; width: auto;
} }
&.stroke svg { svg &.settings svg {
fill: none;
stroke: $grey-55;
}
&.settings svg {
height: 13px; height: 13px;
width: 13px; width: 13px;
} }
} }
&.Active {
background: $grey-100;
}
&.btn-blue { &.btn-blue {
background: $blue; background: $blue;
color: #8b8b8b; color: #8b8b8b;

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import React, { PropsWithChildren, useEffect, useState } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import Link from 'next/link' import Link from 'next/link'
@ -15,147 +15,169 @@ import SettingsIcon from '~public/icons/Settings.svg'
import './index.scss' import './index.scss'
import { ButtonType } from '~utils/enums' import { ButtonType } from '~utils/enums'
import { access } from 'fs'
interface Props { interface Props
active?: boolean extends React.DetailedHTMLProps<
disabled?: boolean React.ButtonHTMLAttributes<HTMLButtonElement>,
classes?: string[] HTMLButtonElement
icon?: string > {
type?: ButtonType accessoryIcon?: React.ReactNode
children?: React.ReactNode blended?: boolean
onClick?: (event: React.MouseEvent<HTMLElement>) => void size?: 'small' | 'medium' | 'large'
text?: string
} }
const Button = (props: Props) => { const defaultProps = {
// States blended: false,
const [active, setActive] = useState(false) size: 'medium',
const [disabled, setDisabled] = useState(false) }
const [pressed, setPressed] = useState(false)
const [buttonType, setButtonType] = useState(ButtonType.Base)
const classes = classNames( const Button = React.forwardRef<HTMLButtonElement, Props>(
{ ({ accessoryIcon, blended, size, text, ...props }, forwardedRef) => {
Button: true, const classes = classNames(
Active: active, {
'btn-pressed': pressed, Button: true,
'btn-disabled': disabled, Blended: blended,
save: props.icon === 'save', // Active: active,
destructive: props.type == ButtonType.Destructive, // 'btn-pressed': pressed,
}, // 'btn-disabled': disabled,
props.classes // save: props.icon === 'save',
) // destructive: props.type == ButtonType.Destructive,
},
size,
props.className
)
useEffect(() => { const hasAccessory = () => {
if (props.active) setActive(props.active) if (accessoryIcon)
if (props.disabled) setDisabled(props.disabled) return <span className="Accessory">{accessoryIcon}</span>
if (props.type) setButtonType(props.type)
}, [props.active, props.disabled, props.type])
const addIcon = (
<span className="icon">
<AddIcon />
</span>
)
const menuIcon = (
<span className="icon">
<MenuIcon />
</span>
)
const linkIcon = (
<span className="icon stroke">
<LinkIcon />
</span>
)
const checkIcon = (
<span className="icon check">
<CheckIcon />
</span>
)
const crossIcon = (
<span className="icon">
<CrossIcon />
</span>
)
const editIcon = (
<span className="icon">
<EditIcon />
</span>
)
const saveIcon = (
<span className="icon stroke">
<SaveIcon />
</span>
)
const settingsIcon = (
<span className="icon settings">
<SettingsIcon />
</span>
)
function getIcon() {
let icon: React.ReactNode
switch (props.icon) {
case 'new':
icon = addIcon
break
case 'menu':
icon = menuIcon
break
case 'link':
icon = linkIcon
break
case 'check':
icon = checkIcon
break
case 'cross':
icon = crossIcon
break
case 'edit':
icon = editIcon
break
case 'save':
icon = saveIcon
break
case 'settings':
icon = settingsIcon
break
} }
return icon const hasText = () => {
} if (text) return <span className="Text">{text}</span>
}
function handleMouseDown() { return (
setPressed(true) <button {...props} className={classes} ref={forwardedRef}>
} {hasAccessory()}
{hasText()}
</button>
)
function handleMouseUp() { // useEffect(() => {
setPressed(false) // if (props.type) setButtonType(props.type)
} // }, [props.type])
return (
<button
className={classes}
disabled={disabled}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onClick={props.onClick}
>
{getIcon()}
{props.type != ButtonType.IconOnly ? ( // const addIcon = (
<span className="text">{props.children}</span> // <span className="icon">
) : ( // <AddIcon />
'' // </span>
)} // )
</button>
) // const menuIcon = (
} // <span className="icon">
// <MenuIcon />
// </span>
// )
// const linkIcon = (
// <span className="icon stroke">
// <LinkIcon />
// </span>
// )
// const checkIcon = (
// <span className="icon check">
// <CheckIcon />
// </span>
// )
// const crossIcon = (
// <span className="icon">
// <CrossIcon />
// </span>
// )
// const editIcon = (
// <span className="icon">
// <EditIcon />
// </span>
// )
// const saveIcon = (
// <span className="icon stroke">
// <SaveIcon />
// </span>
// )
// const settingsIcon = (
// <span className="icon settings">
// <SettingsIcon />
// </span>
// )
// function getIcon() {
// let icon: React.ReactNode
// switch (props.icon) {
// case 'new':
// icon = addIcon
// break
// case 'menu':
// icon = menuIcon
// break
// case 'link':
// icon = linkIcon
// break
// case 'check':
// icon = checkIcon
// break
// case 'cross':
// icon = crossIcon
// break
// case 'edit':
// icon = editIcon
// break
// case 'save':
// icon = saveIcon
// break
// case 'settings':
// icon = settingsIcon
// break
// }
// return icon
// }
// function handleMouseDown() {
// setPressed(true)
// }
// function handleMouseUp() {
// setPressed(false)
// }
// return (
// <button
// className={classes}
// disabled={disabled}
// onMouseDown={handleMouseDown}
// onMouseUp={handleMouseUp}
// ref={forwardedRef}
// {...props}
// >
// {getIcon()}
// {props.type != ButtonType.IconOnly ? (
// <span className="text">{children}</span>
// ) : (
// ''
// )}
// </button>
// )
}
)
Button.defaultProps = defaultProps
export default Button export default Button

View file

@ -1,6 +1,6 @@
.Header { .Header {
display: flex; display: flex;
height: 34px; margin-bottom: $unit-2x;
width: 100%; width: 100%;
&.bottom { &.bottom {
@ -19,10 +19,10 @@
&:hover { &:hover {
padding-right: 50px; padding-right: 50px;
padding-bottom: 16px;
.Button { .Button {
background: $grey-100; background: var(--button-bg-hover);
color: var(--button-text-hover);
} }
.Menu { .Menu {

View file

@ -1,24 +1,25 @@
.Menu { .Menu {
background: $grey-100; background: var(--menu-bg);
border-radius: 6px; border-radius: 6px;
display: none; display: none;
min-width: 220px; min-width: 220px;
position: absolute; position: absolute;
top: $unit * 5; // This shouldn't be hardcoded. How to calculate it? top: $unit * 5; // This shouldn't be hardcoded. How to calculate it?
// Also, add space that doesn't make the menu disappear if you move your mouse slowly
z-index: 10; z-index: 10;
} }
.MenuItem { .MenuItem {
color: $grey-50; color: var(--text-secondary);
font-weight: $normal; font-weight: $normal;
&:hover:not(.disabled) { &:hover:not(.disabled) {
background: $grey-95; background: var(--menu-bg-item-hover);
color: $grey-15; color: var(--text-primary);
cursor: pointer; cursor: pointer;
a { a {
color: $grey-15; color: var(--text-primary);
} }
} }
@ -112,8 +113,8 @@
&:hover { &:hover {
i.tag { i.tag {
background: $grey-60; background: var(--tag-bg);
color: $grey-100; color: var(--tag-text);
} }
} }
@ -131,7 +132,7 @@
} }
.MenuGroup { .MenuGroup {
border-bottom: 1px solid #f5f5f5; border-bottom: 1px solid var(--menu-separator);
&:first-child .MenuItem:first-child:hover { &:first-child .MenuItem:first-child:hover {
border-top-left-radius: 6px; border-top-left-radius: 6px;

View file

@ -88,6 +88,10 @@
h1 { h1 {
color: var(--text-primary); color: var(--text-primary);
&.empty {
color: var(--text-tertiary);
}
} }
} }
} }

View file

@ -8,7 +8,6 @@ import Linkify from 'react-linkify'
import classNames from 'classnames' import classNames from 'classnames'
import * as AlertDialog from '@radix-ui/react-alert-dialog' import * as AlertDialog from '@radix-ui/react-alert-dialog'
import CrossIcon from '~public/icons/Cross.svg'
import Button from '~components/Button' import Button from '~components/Button'
import CharLimitedFieldset from '~components/CharLimitedFieldset' import CharLimitedFieldset from '~components/CharLimitedFieldset'
@ -18,6 +17,10 @@ import TextFieldset from '~components/TextFieldset'
import { accountState } from '~utils/accountState' import { accountState } from '~utils/accountState'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import CheckIcon from '~public/icons/Check.svg'
import CrossIcon from '~public/icons/Cross.svg'
import EditIcon from '~public/icons/Edit.svg'
import './index.scss' import './index.scss'
import Link from 'next/link' import Link from 'next/link'
import { formatTimeAgo } from '~utils/timeAgo' import { formatTimeAgo } from '~utils/timeAgo'
@ -169,11 +172,11 @@ const PartyDetails = (props: Props) => {
if (party.editable) { if (party.editable) {
return ( return (
<AlertDialog.Root> <AlertDialog.Root>
<AlertDialog.Trigger className="Button destructive"> <AlertDialog.Trigger className="Button Blended medium destructive">
<span className="icon"> <span className="Accessory">
<CrossIcon /> <CrossIcon />
</span> </span>
<span className="text">{t('buttons.delete')}</span> <span className="Text">{t('buttons.delete')}</span>
</AlertDialog.Trigger> </AlertDialog.Trigger>
<AlertDialog.Portal> <AlertDialog.Portal>
<AlertDialog.Overlay className="Overlay" /> <AlertDialog.Overlay className="Overlay" />
@ -236,13 +239,12 @@ const PartyDetails = (props: Props) => {
{router.pathname !== '/new' ? deleteButton() : ''} {router.pathname !== '/new' ? deleteButton() : ''}
</div> </div>
<div className="right"> <div className="right">
<Button active={true} onClick={toggleDetails}> <Button text={t('buttons.cancel')} onClick={toggleDetails} />
{t('buttons.cancel')} <Button
</Button> accessoryIcon={<CheckIcon className="Check" />}
text={t('buttons.save_info')}
<Button active={true} icon="check" onClick={updateDetails}> onClick={updateDetails}
{t('buttons.save_info')} />
</Button>
</div> </div>
</div> </div>
</section> </section>
@ -252,7 +254,9 @@ const PartyDetails = (props: Props) => {
<section className={readOnlyClasses}> <section className={readOnlyClasses}>
<div className="info"> <div className="info">
<div className="left"> <div className="left">
{party.name ? <h1>{party.name}</h1> : ''} <h1 className={!party.name ? 'empty' : ''}>
{party.name ? party.name : 'Untitled'}
</h1>
<div className="attribution"> <div className="attribution">
{party.user ? linkedUserBlock(party.user) : userBlock()} {party.user ? linkedUserBlock(party.user) : userBlock()}
{party.raid ? linkedRaidBlock(party.raid) : ''} {party.raid ? linkedRaidBlock(party.raid) : ''}
@ -270,9 +274,11 @@ const PartyDetails = (props: Props) => {
</div> </div>
<div className="right"> <div className="right">
{party.editable ? ( {party.editable ? (
<Button active={true} icon="edit" onClick={toggleDetails}> <Button
{t('buttons.show_info')} accessoryIcon={<EditIcon />}
</Button> text={t('buttons.show_info')}
onClick={toggleDetails}
/>
) : ( ) : (
<div /> <div />
)} )}
@ -291,9 +297,11 @@ const PartyDetails = (props: Props) => {
const emptyDetails = ( const emptyDetails = (
<div className={emptyClasses}> <div className={emptyClasses}>
{party.editable ? ( {party.editable ? (
<Button active={true} icon="edit" onClick={toggleDetails}> <Button
{t('buttons.show_info')} accessoryIcon={<EditIcon />}
</Button> text={t('buttons.show_info')}
onClick={toggleDetails}
/>
) : ( ) : (
<div /> <div />
)} )}

View file

@ -14,6 +14,11 @@ import Header from '~components/Header'
import Button from '~components/Button' import Button from '~components/Button'
import HeaderMenu from '~components/HeaderMenu' import HeaderMenu from '~components/HeaderMenu'
import AddIcon from '~public/icons/Add.svg'
import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg'
import SaveIcon from '~public/icons/Save.svg'
const TopHeader = () => { const TopHeader = () => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
@ -94,10 +99,26 @@ const TopHeader = () => {
else console.error('Failed to unsave team: No party ID') else console.error('Failed to unsave team: No party ID')
} }
const copyButton = () => {
if (router.route === '/p/[party]')
return (
<Button
accessoryIcon={<LinkIcon className="stroke" />}
blended={true}
text={t('buttons.copy')}
onClick={copyToClipboard}
/>
)
}
const leftNav = () => { const leftNav = () => {
return ( return (
<div className="dropdown"> <div className="dropdown">
<Button icon="menu">{t('buttons.menu')}</Button> <Button
accessoryIcon={<MenuIcon />}
blended={true}
text={t('buttons.menu')}
/>
{account.user ? ( {account.user ? (
<HeaderMenu <HeaderMenu
authenticated={account.authorized} authenticated={account.authorized}
@ -114,15 +135,19 @@ const TopHeader = () => {
const saveButton = () => { const saveButton = () => {
if (party.favorited) if (party.favorited)
return ( return (
<Button icon="save" active={true} onClick={toggleFavorite}> <Button
Saved accessoryIcon={<SaveIcon />}
</Button> text="Saved"
onClick={toggleFavorite}
/>
) )
else else
return ( return (
<Button icon="save" onClick={toggleFavorite}> <Button
Save accessoryIcon={<SaveIcon />}
</Button> text="Save"
onClick={toggleFavorite}
/>
) )
} }
@ -134,16 +159,15 @@ const TopHeader = () => {
(!party.user || party.user.id !== account.user.id) (!party.user || party.user.id !== account.user.id)
? saveButton() ? saveButton()
: ''} : ''}
{router.route === '/p/[party]' ? (
<Button icon="link" onClick={copyToClipboard}> {copyButton()}
{t('buttons.copy')}
</Button> <Button
) : ( accessoryIcon={<AddIcon className="Add" />}
'' blended={true}
)} text={t('buttons.new')}
<Button icon="new" onClick={newParty}> onClick={newParty}
{t('buttons.new')} />
</Button>
</div> </div>
) )
} }