Create <Button> component and use it in <GroupV2JoinDialog>

This commit is contained in:
Evan Hahn 2021-02-23 13:28:48 -06:00 committed by Josh Perez
parent c4551ca7ca
commit 8ee3bd9687
7 changed files with 239 additions and 27 deletions

View File

@ -113,7 +113,7 @@ module.exports = grunt => {
tasks: ['exec:build-protobuf'],
},
sass: {
files: ['./stylesheets/*.scss'],
files: ['./stylesheets/*.scss', './stylesheets/**/*.scss'],
tasks: ['sass'],
},
transpile: {

View File

@ -11006,30 +11006,15 @@ button.module-image__border-overlay:focus {
display: flex;
}
.module-group-v2-join-dialog__button {
@include button-reset;
@include font-body-1-bold;
// Start flex basis at zero so text width doesn't affect layout. We want the buttons
// evenly distributed.
flex: 1 1 0px;
border-radius: 4px;
padding: 8px;
padding-left: 15px;
padding-right: 15px;
@include button-primary;
&:not(:first-of-type) {
margin-left: 16px;
}
}
.module-group-v2-join-dialog__button--secondary {
@include button-secondary;
}
// Module: Progress Dialog
.module-progress-dialog {

View File

@ -0,0 +1,109 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
.module-Button {
@mixin focus-box-shadow($inner-color, $outer-color) {
&:focus {
box-shadow: 0 0 0 1px $inner-color, 0 0 0 4px $outer-color;
}
}
@mixin hover-and-active-states($background-color, $mix-color) {
&:hover:not(:disabled) {
background: mix($background-color, $mix-color, 85%);
}
&:active:not(:disabled) {
background: mix($background-color, $mix-color, 75%);
}
}
@include button-reset;
@include font-body-1-bold;
border-radius: 4px;
padding: 8px 16px;
text-align: center;
user-select: none;
@include keyboard-mode {
@include focus-box-shadow($color-white, $ultramarine-ui-light);
}
@include dark-keyboard-mode {
@include focus-box-shadow($color-black, $ultramarine-brand-light);
}
&--primary {
$color: $color-white;
$background-color: $ultramarine-ui-light;
color: $color;
background: $background-color;
&:disabled {
color: fade-out($color, 0.4);
background: fade-out($background-color, 0.6);
}
@include light-theme {
@include hover-and-active-states($background-color, $color-black);
}
@include dark-theme {
@include hover-and-active-states($background-color, $color-white);
}
}
&--secondary {
@include light-theme {
$color: $color-gray-90;
$background-color: $color-gray-05;
color: $color;
background: $background-color;
&:disabled {
color: $color-black-alpha-40;
background: fade-out($background-color, 0.6);
}
@include hover-and-active-states($background-color, $color-black);
}
@include dark-theme {
$color: $color-gray-05;
$background-color: $color-gray-65;
color: $color;
background: $background-color;
&:disabled {
color: $color-white-alpha-20;
background: fade-out($background-color, 0.6);
}
@include hover-and-active-states($background-color, $color-white);
}
}
&--destructive {
$color: $color-white;
$background-color: $color-accent-red;
color: $color;
background: $background-color;
&:disabled {
color: fade-out($color, 0.4);
background: fade-out($background-color, 0.6);
}
@include light-theme {
@include hover-and-active-states($background-color, $color-black);
}
@include dark-theme {
@include hover-and-active-states($background-color, $color-white);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2014-2020 Signal Messenger, LLC
// Copyright 2014-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// Global Settings, Variables, and Mixins
@ -7,7 +7,7 @@
@import 'mixins';
@import 'global';
// Components
// Old style: components
@import 'progress';
@import 'modal';
@import 'debugLog';
@ -16,12 +16,15 @@
@import 'emoji';
@import 'settings';
// Build the main view
// Old style: main view
@import 'index';
@import 'conversation';
// New CSS
// Old style: modules
@import 'modules';
// Installer
// Old style: installer
@import 'options';
// New style: components
@import './components/Button.scss';

View File

@ -0,0 +1,59 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Button, ButtonVariant } from './Button';
const story = storiesOf('Components/Button', module);
story.add('Kitchen sink', () => (
<>
<p>
<Button onClick={action('onClick')} variant={ButtonVariant.Primary}>
Hello world
</Button>
</p>
<p>
<Button
onClick={action('onClick')}
variant={ButtonVariant.Primary}
disabled
>
Hello world
</Button>
</p>
<p>
<Button onClick={action('onClick')} variant={ButtonVariant.Secondary}>
Hello world
</Button>
</p>
<p>
<Button
onClick={action('onClick')}
variant={ButtonVariant.Secondary}
disabled
>
Hello world
</Button>
</p>
<p>
<Button onClick={action('onClick')} variant={ButtonVariant.Destructive}>
Hello world
</Button>
</p>
<p>
<Button
onClick={action('onClick')}
variant={ButtonVariant.Destructive}
disabled
>
Hello world
</Button>
</p>
</>
));

55
ts/components/Button.tsx Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { MouseEventHandler, ReactNode } from 'react';
import classNames from 'classnames';
import { assert } from '../util/assert';
export enum ButtonVariant {
Primary,
Secondary,
Destructive,
}
type PropsType = {
children: ReactNode;
className?: string;
disabled?: boolean;
onClick: MouseEventHandler<HTMLButtonElement>;
variant?: ButtonVariant;
};
const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
[ButtonVariant.Primary, 'module-Button--primary'],
[ButtonVariant.Secondary, 'module-Button--secondary'],
[ButtonVariant.Destructive, 'module-Button--destructive'],
]);
export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
(
{
children,
className,
disabled = false,
onClick,
variant = ButtonVariant.Primary,
},
ref
) => {
const variantClassName = VARIANT_CLASS_NAMES.get(variant);
assert(variantClassName, '<Button> variant not found');
return (
<button
className={classNames('module-Button', variantClassName, className)}
disabled={disabled}
onClick={onClick}
ref={ref}
type="button"
>
{children}
</button>
);
}
);

View File

@ -6,6 +6,7 @@ import classNames from 'classnames';
import { LocalizerType } from '../types/Util';
import { Avatar } from './Avatar';
import { Spinner } from './Spinner';
import { Button, ButtonVariant } from './Button';
import { PreJoinConversationType } from '../state/ducks/conversations';
@ -90,30 +91,30 @@ export const GroupV2JoinDialog = React.memo((props: PropsType) => {
</div>
<div className="module-group-v2-join-dialog__prompt">{promptString}</div>
<div className="module-group-v2-join-dialog__buttons">
<button
<Button
className={classNames(
'module-group-v2-join-dialog__button',
'module-group-v2-join-dialog__button--secondary'
)}
disabled={isWorking}
type="button"
onClick={wrappedClose}
variant={ButtonVariant.Secondary}
>
{i18n('cancel')}
</button>
<button
</Button>
<Button
className="module-group-v2-join-dialog__button"
disabled={isWorking}
ref={focusRef}
type="button"
onClick={wrappedJoin}
variant={ButtonVariant.Primary}
>
{isJoining ? (
<Spinner size="20px" svgSize="small" direction="on-avatar" />
) : (
joinString
)}
</button>
</Button>
</div>
</div>
);