diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js index d4a886a8b30..519941dd609 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/components/dropdown_menu.js @@ -133,8 +133,12 @@ export default class Dropdown extends React.PureComponent { this.props.onModalOpen({ status, - actions: items, - onClick: this.handleItemClick, + actions: items.map( + (item, i) => ({ + ...item, + name: `${item.text}-${i}`, + onClick: this.handleItemClick.bind(i), + }), }); return; @@ -162,8 +166,7 @@ export default class Dropdown extends React.PureComponent { } } - handleItemClick = e => { - const i = Number(e.currentTarget.getAttribute('data-index')); + handleItemClick = (i, e) => { const { action, to } = this.props.items[i]; this.handleClose(); diff --git a/app/javascript/flavours/glitch/components/link.js b/app/javascript/flavours/glitch/components/link.js new file mode 100644 index 00000000000..c49fc487cea --- /dev/null +++ b/app/javascript/flavours/glitch/components/link.js @@ -0,0 +1,97 @@ +// Inspired by from Mastodon GO! +// ~ 😘 kibi! + +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; + +// Utils. +import { assignHandlers } from 'flavours/glitch/util/react_helpers'; + +// Handlers. +const handlers = { + + // We don't handle clicks that are made with modifiers, since these + // often have special browser meanings (eg, "open in new tab"). + click (e) { + const { onClick } = this.props; + if (!onClick || e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) { + return; + } + onClick(e); + e.preventDefault(); // Prevents following of the link + }, +}; + +// The component. +export default class Link extends React.PureComponent { + + // Constructor. + constructor (props) { + super(props); + assignHandlers(this, handlers); + } + + // Rendering. + render () { + const { click } = this.handlers; + const { + children, + className, + href, + onClick, + role, + title, + ...rest + } = this.props; + const computedClass = classNames('link', className, role); + + // We assume that our `onClick` is a routing function and give it + // the qualities of a link even if no `href` is provided. However, + // if we have neither an `onClick` or an `href`, our link is + // purely presentational. + const conditionalProps = {}; + if (href) { + conditionalProps.href = href; + conditionalProps.onClick = click; + } else if (onClick) { + conditionalProps.onClick = click; + conditionalProps.role = 'link'; + conditionalProps.tabIndex = 0; + } else { + conditionalProps.role = 'presentation'; + } + + // If we were provided a `role` it overwrites any that we may have + // set above. This can be used for "links" which are actually + // buttons. + if (role) { + conditionalProps.role = role; + } + + // Rendering. We set `rel='noopener'` for user privacy, and our + // `target` as `'_blank'`. + return ( + {children} + ); + } + +} + +// Props. +Link.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + href: PropTypes.string, // The link destination + onClick: PropTypes.func, // A function to call instead of opening the link + role: PropTypes.string, // An ARIA role for the link + title: PropTypes.string, // A title for the link +}; diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js index ee52008a75f..daed4ec8a14 100644 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js @@ -80,11 +80,16 @@ const handlers = { }) => ({ ...rest, active: value && name === value, + name, onClick (e) { e.preventDefault(); // Prevents focus from changing onModalClose(); onChange(name); }, + onPassiveClick (e) { + e.preventDefault(); // Prevents focus from changing + onChange(name); + }, }) ), }); @@ -191,7 +196,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { > {({ opacity, scaleX, scaleY }) => (
@@ -100,11 +101,11 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent { } }()} {meta ? ( -
+
{text} {meta}
- ) :
{text}
} + ) :
{text}
}
); } diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js index 0873c282faf..020cc0dd69d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js @@ -6,15 +6,26 @@ import StatusContent from 'flavours/glitch/components/status_content'; import Avatar from 'flavours/glitch/components/avatar'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import DisplayName from 'flavours/glitch/components/display_name'; -import IconButton from 'flavours/glitch/components/icon_button'; import classNames from 'classnames'; +import Icon from 'flavours/glitch/components/icon'; +import Link from 'flavours/glitch/components/link'; +import Toggle from 'react-toggle'; export default class ActionsModal extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map, - actions: PropTypes.array, - onClick: PropTypes.func, + actions: PropTypes.arrayOf(PropTypes.shape({ + active: PropTypes.bool, + href: PropTypes.string, + icon: PropTypes.string, + meta: PropTypes.node, + name: PropTypes.string, + on: PropTypes.bool, + onClick: PropTypes.func, + onPassiveClick: PropTypes.func, + text: PropTypes.node, + })), }; renderAction = (action, i) => { @@ -22,17 +33,57 @@ export default class ActionsModal extends ImmutablePureComponent { return
  • ; } - const { icon = null, text, meta = null, active = false, href = '#' } = action; + const { + active, + href, + icon, + meta, + name, + on, + onClick, + onPassiveClick, + text, + } = action; return ( -
  • - - {icon && } -
    -
    {text}
    -
    {meta}
    -
    -
    +
  • + + {function () { + + // We render a `` if we were provided an `on` + // property, and otherwise show an `` if available. + switch (true) { + case on !== null && typeof on !== 'undefined': + return ( + + ); + case !!icon: + return ( + + ); + default: + return null; + } + }()} + {meta ? ( +
    + {text} + {meta} +
    + ) :
    {text}
    } +
  • ); } diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss index a8d688ea58f..ae9114644f6 100644 --- a/app/javascript/flavours/glitch/styles/components/composer.scss +++ b/app/javascript/flavours/glitch/styles/components/composer.scss @@ -1,315 +1,370 @@ -.composer { - padding: 10px; +.composer { padding: 10px } - .composer--spoiler { +.composer--spoiler { + display: block; + box-sizing: border-box; + margin: 0; + border: none; + border-radius: 4px; + padding: 10px; + width: 100%; + outline: 0; + color: $ui-base-color; + background: $simple-background-color; + font-size: 14px; + font-family: inherit; + resize: vertical; + + &:focus { outline: 0 } + @include single-column('screen and (max-width: 630px)') { font-size: 16px } +} + +.composer--warning { + color: darken($ui-secondary-color, 65%); + margin-bottom: 15px; + background: $ui-primary-color; + box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3); + padding: 8px 10px; + border-radius: 4px; + font-size: 13px; + font-weight: 400; + + a { + color: darken($ui-primary-color, 33%); + font-weight: 500; + text-decoration: underline; + + &:active, + &:focus, + &:hover { text-decoration: none } + } +} + +.composer--reply { + margin: 0 0 -2px; + border-radius: 4px 4px 0 0; + padding: 10px; + background: $ui-primary-color; + + & > header { + margin-bottom: 5px; + overflow: hidden; + + & > .account { + & > .avatar { + float: left; + margin-right: 5px; + } + + & > .display_name { + color: $ui-base-color; + display: block; + padding-right: 25px; + max-width: 100%; + line-height: 24px; + text-decoration: none; + overflow: hidden; + } + } + + & > .cancel { + float: right; + line-height: 24px; + } + } + + & > .content { + position: relative; + margin: 10px 0; + padding: 0 12px; + font-size: 14px; + line-height: 20px; + color: $ui-base-color; + word-wrap: break-word; + font-weight: 400; + overflow: visible; + white-space: pre-wrap; + padding-top: 5px; + } + + .emojione { + width: 20px; + height: 20px; + margin: -5px 0 0; + } + + p { + margin-bottom: 20px; + + &:last-child { margin-bottom: 0 } + } + + a { + color: lighten($ui-base-color, 20%); + text-decoration: none; + + &:hover { text-decoration: underline } + + &.mention { + &:hover { + text-decoration: none; + + span { text-decoration: underline } + } + } + } +} + +.composer--textarea { + background: $simple-background-color; + position: relative; + + &:disabled { background: $ui-secondary-color } + + & > .textarea { display: block; box-sizing: border-box; margin: 0; border: none; - border-radius: 4px; - padding: 10px; + border-radius: 4px 4px 0 0; + padding: 10px 32px 0 10px; width: 100%; + min-height: 100px; outline: 0; color: $ui-base-color; background: $simple-background-color; font-size: 14px; font-family: inherit; - resize: vertical; + resize: none; &:focus { outline: 0 } @include single-column('screen and (max-width: 630px)') { font-size: 16px } - } - .composer--warning { - color: darken($ui-secondary-color, 65%); - margin-bottom: 15px; - background: $ui-primary-color; - box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3); - padding: 8px 10px; - border-radius: 4px; - font-size: 13px; - font-weight: 400; - - a { - color: darken($ui-primary-color, 33%); - font-weight: 500; - text-decoration: underline; - - &:active, - &:focus, - &:hover { text-decoration: none } - } - } - - .composer--reply { - margin: 0 0 -2px; - border-radius: 4px 4px 0 0; - padding: 10px; - background: $ui-primary-color; - - & > header { - margin-bottom: 5px; - overflow: hidden; - - & > .account { - & > .avatar { - float: left; - margin-right: 5px; - } - - & > .display_name { - color: $ui-base-color; - display: block; - padding-right: 25px; - max-width: 100%; - line-height: 24px; - text-decoration: none; - overflow: hidden; - } - } - - & > .cancel { - float: right; - line-height: 24px; - } - } - - & > .content { - position: relative; - margin: 10px 0; - padding: 0 12px; - font-size: 14px; - line-height: 20px; - color: $ui-base-color; - word-wrap: break-word; - font-weight: 400; - overflow: visible; - white-space: pre-wrap; - padding-top: 5px; - } - - .emojione { - width: 20px; - height: 20px; - margin: -5px 0 0; - } - - p { - margin-bottom: 20px; - - &:last-child { margin-bottom: 0 } - } - - a { - color: lighten($ui-base-color, 20%); - text-decoration: none; - - &:hover { text-decoration: underline } - - &.mention { - &:hover { - text-decoration: none; - - span { text-decoration: underline } - } - } - } - } - - .composer--textarea { - background: $simple-background-color; - position: relative; - - &:disabled { background: $ui-secondary-color } - - & > .textarea { - display: block; - box-sizing: border-box; - margin: 0; - border: none; - border-radius: 4px 4px 0 0; - padding: 10px 32px 0 10px; - width: 100%; - min-height: 100px; - outline: 0; - color: $ui-base-color; - background: $simple-background-color; - font-size: 14px; - font-family: inherit; - resize: none; - - &:focus { outline: 0 } - @include single-column('screen and (max-width: 630px)') { font-size: 16px } - - @include limited-single-column('screen and (max-width: 600px)') { - height: 100px !important; // prevent auto-resize textarea - resize: vertical; - } - } - - .composer--textarea--suggestions { - display: block; - position: absolute; - box-sizing: border-box; - top: 100%; - border-radius: 0 0 4px 4px; - padding: 6px; - width: 100%; - color: $ui-base-color; - background: $ui-secondary-color; - box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); - font-size: 14px; - z-index: 99; - - &[hidden] { display: none } - - .composer--textarea--suggestions--item { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - border-radius: 4px; - padding: 10px; - font-size: 14px; - line-height: 18px; - cursor: pointer; - - &:hover, - &:focus, - &:active, - &.active { background: darken($ui-secondary-color, 10%) } - - & > .emoji { - img { - display: block; - float: left; - margin-right: 8px; - width: 16px; - height: 16px; - } - } - } - } - } - - .composer--upload_form { - display: flex; - flex-direction: row; - flex-wrap: wrap; - padding: 5px; - color: $ui-base-color; - background: $simple-background-color; - font-size: 14px; - font-family: inherit; - overflow: hidden; - - .composer--upload_form--item { - flex: 1 1 0; - margin: 5px; - min-width: 40%; - - & > div { - position: relative; - border-radius: 4px; - height: 100px; - width: 100%; - background-position: center; - background-size: cover; - background-repeat: no-repeat; - - input { - display: block; - position: absolute; - box-sizing: border-box; - bottom: 0; - left: 0; - margin: 0; - border: 0; - padding: 10px; - width: 100%; - color: $ui-secondary-color; - background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); - font-size: 14px; - font-family: inherit; - font-weight: 500; - opacity: 0; - z-index: 2; - transition: opacity .1s ease; - - &:focus { color: $white } - - &::placeholder { - opacity: 0.54; - color: $ui-secondary-color; - } - } - - & > .close { mix-blend-mode: difference } - } - - &.active { - & > div { - input { opacity: 1 } - } - } - } - } - - .composer--options { - padding: 10px; - background: darken($simple-background-color, 8%); - box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05); - border-radius: 0 0 4px 4px; - - & > * { - display: inline-block; - box-sizing: content-box; - padding: 0 3px; - line-height: 27px; - } - - & > hr { - display: inline-block; - margin: 0 3px; - border-width: 0 0 0 1px; - border-style: none none none solid; - border-color: transparent transparent transparent darken($simple-background-color, 24%); - padding: 0; - background: transparent; - } - } - - .composer--publisher { - padding-top: 10px; - text-align: right; - white-space: nowrap; - overflow: hidden; - - & > .count { - display: inline-block; - margin: 0 16px 0 8px; - padding-top: 10px; - font-size: 16px; - line-height: 36px; - } - - & > .primary { - display: inline-block; - margin: 0; - padding: 0 10px; - text-align: center; - } - - & > .side_arm { - display: inline-block; - margin: 0 2px 0 0; - padding: 0; - width: 36px; - text-align: center; - } - - &.over { - & > .count { color: $warning-red } + @include limited-single-column('screen and (max-width: 600px)') { + height: 100px !important; // prevent auto-resize textarea + resize: vertical; } } } + +.composer--textarea--suggestions { + display: block; + position: absolute; + box-sizing: border-box; + top: 100%; + border-radius: 0 0 4px 4px; + padding: 6px; + width: 100%; + color: $ui-base-color; + background: $ui-secondary-color; + box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); + font-size: 14px; + z-index: 99; + + &[hidden] { display: none } +} + +.composer--textarea--suggestions--item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + border-radius: 4px; + padding: 10px; + font-size: 14px; + line-height: 18px; + cursor: pointer; + + &:hover, + &:focus, + &:active, + &.active { background: darken($ui-secondary-color, 10%) } + + & > .emoji { + img { + display: block; + float: left; + margin-right: 8px; + width: 16px; + height: 16px; + } + } +} + +.composer--upload_form { + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: 5px; + color: $ui-base-color; + background: $simple-background-color; + font-size: 14px; + font-family: inherit; + overflow: hidden; +} + +.composer--upload_form--item { + flex: 1 1 0; + margin: 5px; + min-width: 40%; + + & > div { + position: relative; + border-radius: 4px; + height: 100px; + width: 100%; + background-position: center; + background-size: cover; + background-repeat: no-repeat; + + input { + display: block; + position: absolute; + box-sizing: border-box; + bottom: 0; + left: 0; + margin: 0; + border: 0; + padding: 10px; + width: 100%; + color: $ui-secondary-color; + background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); + font-size: 14px; + font-family: inherit; + font-weight: 500; + opacity: 0; + z-index: 2; + transition: opacity .1s ease; + + &:focus { color: $white } + + &::placeholder { + opacity: 0.54; + color: $ui-secondary-color; + } + } + + & > .close { mix-blend-mode: difference } + } + + &.active { + & > div { + input { opacity: 1 } + } + } +} + +.composer--options { + padding: 10px; + background: darken($simple-background-color, 8%); + box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05); + border-radius: 0 0 4px 4px; + + & > * { + display: inline-block; + box-sizing: content-box; + padding: 0 3px; + line-height: 27px; + } + + & > hr { + display: inline-block; + margin: 0 3px; + border-width: 0 0 0 1px; + border-style: none none none solid; + border-color: transparent transparent transparent darken($simple-background-color, 24%); + padding: 0; + background: transparent; + } +} + +.composer--options--dropdown { + & > .value { transition: none } + + &.active { + & > .value { + border-radius: 4px 4px 0 0; + box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1); + color: $primary-text-color; + background: $ui-highlight-color; + } + } +} + +.composer--options--dropdown__dropdown { + position: absolute; + margin-left: 40px; + border-radius: 4px; + box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); + background: $simple-background-color; + overflow: hidden; + transform-origin: 50% 0; +} + +.composer--options--dropdown--item { + color: $ui-base-color; + padding: 10px; + cursor: pointer; + display: flex; + + & > .content { + flex: 1 1 auto; + color: darken($ui-primary-color, 24%); + + &:not(:first-child) { margin-left: 10px } + + strong { + display: block; + color: $ui-base-color; + font-weight: 500; + } + } + + &:hover, + &.active { + background: $ui-highlight-color; + color: $primary-text-color; + + & > .content { + color: $primary-text-color; + + strong { color: $primary-text-color } + } + } + + &.active:hover { background: lighten($ui-highlight-color, 4%) } +} + +.composer--publisher { + padding-top: 10px; + text-align: right; + white-space: nowrap; + overflow: hidden; + + & > .count { + display: inline-block; + margin: 0 16px 0 8px; + padding-top: 10px; + font-size: 16px; + line-height: 36px; + } + + & > .primary { + display: inline-block; + margin: 0; + padding: 0 10px; + text-align: center; + } + + & > .side_arm { + display: inline-block; + margin: 0 2px 0 0; + padding: 0; + width: 36px; + text-align: center; + } + + &.over { + & > .count { color: $warning-red } + } +} diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index e9127affc55..98ed5d24acc 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -2784,156 +2784,6 @@ filter: none; } -.privacy-dropdown__dropdown { - position: absolute; - background: $simple-background-color; - box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); - border-radius: 4px; - margin-left: 40px; - overflow: hidden; - transform-origin: 50% 0; -} - -.privacy-dropdown__option { - color: $ui-base-color; - padding: 10px; - cursor: pointer; - display: flex; - - &:hover, - &.active { - background: $ui-highlight-color; - color: $primary-text-color; - - .privacy-dropdown__option__content { - color: $primary-text-color; - - strong { - color: $primary-text-color; - } - } - } - - &.active:hover { - background: lighten($ui-highlight-color, 4%); - } -} - -.privacy-dropdown__option__icon { - display: flex; - align-items: center; - justify-content: center; - margin-right: 10px; -} - -.privacy-dropdown__option__content { - flex: 1 1 auto; - color: darken($ui-primary-color, 24%); - - strong { - font-weight: 500; - display: block; - color: $ui-base-color; - } -} - -.privacy-dropdown.active { - .privacy-dropdown__value { - background: $simple-background-color; - border-radius: 4px 4px 0 0; - box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1); - - .icon-button { - transition: none; - } - - &.active { - background: $ui-highlight-color; - - .icon-button { - color: $primary-text-color; - } - } - } - - .privacy-dropdown__dropdown { - display: block; - box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1); - } -} - -.advanced-options-dropdown { - position: relative; -} - -.advanced-options-dropdown__dropdown { - display: none; - position: absolute; - left: 0; - top: 27px; - width: 210px; - background: $simple-background-color; - border-radius: 0 4px 4px; - z-index: 2; - overflow: hidden; -} - -.advanced-options-dropdown__option { - color: $ui-base-color; - padding: 10px; - cursor: pointer; - display: flex; - - &:hover, - &.active { - background: $ui-highlight-color; - color: $primary-text-color; - - .advanced-options-dropdown__option__content { - color: $primary-text-color; - - strong { - color: $primary-text-color; - } - } - } - - &.active:hover { - background: lighten($ui-highlight-color, 4%); - } -} - -.advanced-options-dropdown__option__toggle { - display: flex; - align-items: center; - justify-content: center; - margin-right: 10px; -} - -.advanced-options-dropdown__option__content { - flex: 1 1 auto; - color: darken($ui-primary-color, 24%); - - strong { - font-weight: 500; - display: block; - color: $ui-base-color; - } -} - -.advanced-options-dropdown.open { - .advanced-options-dropdown__value { - background: $simple-background-color; - border-radius: 4px 4px 0 0; - box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1); - } - - .advanced-options-dropdown__dropdown { - display: block; - box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1); - } -} - .modal-root { transition: opacity 0.3s linear; will-change: opacity; @@ -3488,7 +3338,7 @@ max-height: 80vh; max-width: 80vw; - .actions-modal__item-label { + strong { font-weight: 500; } @@ -3501,31 +3351,24 @@ } li:not(:empty) { - a { + & > .link { color: $ui-base-color; display: flex; padding: 12px 16px; font-size: 15px; align-items: center; text-decoration: none; - - &, - button { - transition: none; - } + transition: none; &.active, &:hover, &:active, &:focus { - &, - button { - background: $ui-highlight-color; - color: $primary-text-color; - } + background: $ui-highlight-color; + color: $primary-text-color; } - button:first-child { + & > .icon { margin-right: 10px; } }