diff --git a/Gruntfile.js b/Gruntfile.js index c42bf529d..6cec5e6ec 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -113,7 +113,7 @@ module.exports = grunt => { tasks: ['exec:build-protobuf'], }, sass: { - files: ['./stylesheets/*.scss'], + files: ['./stylesheets/*.scss', './stylesheets/**/*.scss'], tasks: ['sass'], }, transpile: { diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 97dc97c29..c19668e35 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -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 { diff --git a/stylesheets/components/Button.scss b/stylesheets/components/Button.scss new file mode 100644 index 000000000..c7fadd621 --- /dev/null +++ b/stylesheets/components/Button.scss @@ -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); + } + } +} diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index 499a67643..b4d853898 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -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'; diff --git a/ts/components/Button.stories.tsx b/ts/components/Button.stories.tsx new file mode 100644 index 000000000..b9efb228d --- /dev/null +++ b/ts/components/Button.stories.tsx @@ -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', () => ( + <> +

+ +

+

+ +

+ +

+ +

+

+ +

+ +

+ +

+

+ +

+ +)); diff --git a/ts/components/Button.tsx b/ts/components/Button.tsx new file mode 100644 index 000000000..596902d64 --- /dev/null +++ b/ts/components/Button.tsx @@ -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; + variant?: ButtonVariant; +}; + +const VARIANT_CLASS_NAMES = new Map([ + [ButtonVariant.Primary, 'module-Button--primary'], + [ButtonVariant.Secondary, 'module-Button--secondary'], + [ButtonVariant.Destructive, 'module-Button--destructive'], +]); + +export const Button = React.forwardRef( + ( + { + children, + className, + disabled = false, + onClick, + variant = ButtonVariant.Primary, + }, + ref + ) => { + const variantClassName = VARIANT_CLASS_NAMES.get(variant); + assert(variantClassName, ' + ); + } +); diff --git a/ts/components/GroupV2JoinDialog.tsx b/ts/components/GroupV2JoinDialog.tsx index d89aa7ff4..8199e0c18 100644 --- a/ts/components/GroupV2JoinDialog.tsx +++ b/ts/components/GroupV2JoinDialog.tsx @@ -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) => {
{promptString}
- - +
);