diff options
author | David Yip <yipdw@member.fsf.org> | 2017-09-21 16:11:03 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-21 16:11:03 -0500 |
commit | 9512db920c6056a6cca746491bfb0c298ab44420 (patch) | |
tree | 977feb44d1f287f3223ebbb8110e1dac75dd2a97 | |
parent | c89cce0219646502b4d338213d112a528373bdc4 (diff) | |
parent | 9ed51cecd0f41eeca0e303c0b0787d1928034156 (diff) |
Merge pull request #148 from glitch-soc/better-header
Improvements to status headers and content
-rw-r--r-- | app/javascript/glitch/components/notification/follow.js | 75 | ||||
-rw-r--r-- | app/javascript/glitch/components/status/header.js | 159 | ||||
-rw-r--r-- | app/javascript/mastodon/components/icon_button.js | 17 | ||||
-rw-r--r-- | app/javascript/styles/components.scss | 115 |
4 files changed, 112 insertions, 254 deletions
diff --git a/app/javascript/glitch/components/notification/follow.js b/app/javascript/glitch/components/notification/follow.js index d340e83c8..f471307b9 100644 --- a/app/javascript/glitch/components/notification/follow.js +++ b/app/javascript/glitch/components/notification/follow.js @@ -1,38 +1,12 @@ -/* +// `<NotificationFollow>` +// ====================== -`<NotificationFollow>` -====================== +// * * * * * * * // -This component renders a follow notification. +// Imports +// ------- -__Props:__ - - - __`id` (`PropTypes.number.isRequired`) :__ - This is the id of the notification. - - - __`onDeleteNotification` (`PropTypes.func.isRequired`) :__ - The function to call when a notification should be - dismissed/deleted. - - - __`account` (`PropTypes.object.isRequired`) :__ - The account associated with the follow notification, ie the account - which followed the user. - - - __`intl` (`PropTypes.object.isRequired`) :__ - Our internationalization object, inserted by `@injectIntl`. - -*/ - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - -/* - -Imports: --------- - -*/ - -// Package imports // +// Package imports. import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; @@ -40,22 +14,18 @@ import { FormattedMessage } from 'react-intl'; import escapeTextContentForBrowser from 'escape-html'; import ImmutablePureComponent from 'react-immutable-pure-component'; -// Mastodon imports // +// Mastodon imports. import emojify from '../../../mastodon/emoji'; import Permalink from '../../../mastodon/components/permalink'; import AccountContainer from '../../../mastodon/containers/account_container'; -// Our imports // +// Our imports. import NotificationOverlayContainer from '../notification/overlay/container'; -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - -/* +// * * * * * * * // -Implementation: ---------------- - -*/ +// Implementation +// -------------- export default class NotificationFollow extends ImmutablePureComponent { @@ -65,24 +35,10 @@ export default class NotificationFollow extends ImmutablePureComponent { notification : ImmutablePropTypes.map.isRequired, }; -/* - -### `render()` - -This actually renders the component. - -*/ - render () { const { account, notification } = this.props; -/* - -`link` is a container for the account's `displayName`, which links to -the account timeline using a `<Permalink>`. - -*/ - + // Links to the display name. const displayName = account.get('display_name') || account.get('username'); const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; const link = ( @@ -95,12 +51,7 @@ the account timeline using a `<Permalink>`. /> ); -/* - -We can now render our component. - -*/ - + // Renders. return ( <div className='notification notification-follow'> <div className='notification__message'> diff --git a/app/javascript/glitch/components/status/header.js b/app/javascript/glitch/components/status/header.js index bdb868e4d..f741950b1 100644 --- a/app/javascript/glitch/components/status/header.js +++ b/app/javascript/glitch/components/status/header.js @@ -9,41 +9,30 @@ component for better documentation and maintainance by */ - /* * * * */ +// * * * * * * * // -/* - -Imports: --------- - -*/ +// Imports +// ------- -// Package imports // +// Package imports. import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl } from 'react-intl'; -// Mastodon imports // +// Mastodon imports. import Avatar from '../../../mastodon/components/avatar'; import AvatarOverlay from '../../../mastodon/components/avatar_overlay'; import DisplayName from '../../../mastodon/components/display_name'; import IconButton from '../../../mastodon/components/icon_button'; import VisibilityIcon from './visibility_icon'; - /* * * * */ - -/* +// * * * * * * * // -Inital setup: -------------- - -The `messages` constant is used to define any messages that we need -from inside props. In our case, these are the `collapse` and -`uncollapse` messages used with our collapse/uncollapse buttons. - -*/ +// Initial setup +// ------------- +// Messages for use with internationalization stuff. const messages = defineMessages({ collapse: { id: 'status.collapse', defaultMessage: 'Collapse' }, uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' }, @@ -53,43 +42,10 @@ const messages = defineMessages({ direct: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, }); - /* * * * */ - -/* +// * * * * * * * // -The `<StatusHeader>` component: -------------------------------- - -The `<StatusHeader>` component wraps together the header information -(avatar, display name) and upper buttons and icons (collapsing, media -icons) into a single `<header>` element. - -### Props - - - __`account`, `friend` (`ImmutablePropTypes.map`) :__ - These give the accounts associated with the status. `account` is - the author of the post; `friend` will have their avatar appear - in the overlay if provided. - - - __`mediaIcon` (`PropTypes.string`) :__ - If a mediaIcon should be placed in the header, this string - specifies it. - - - __`collapsible`, `collapsed` (`PropTypes.bool`) :__ - These props tell whether a post can be, and is, collapsed. - - - __`parseClick` (`PropTypes.func`) :__ - This function will be called when the user clicks inside the header - information. - - - __`setExpansion` (`PropTypes.func`) :__ - This function is used to set the expansion state of the post. - - - __`intl` (`PropTypes.object`) :__ - This is our internationalization object, provided by - `injectIntl()`. - -*/ +// The component +// ------------- @injectIntl export default class StatusHeader extends React.PureComponent { @@ -105,18 +61,7 @@ export default class StatusHeader extends React.PureComponent { intl: PropTypes.object.isRequired, }; -/* - -### Implementation - -#### `handleCollapsedClick()`. - -`handleCollapsedClick()` is just a simple callback for our collapsing -button. It calls `setExpansion` to set the collapsed state of the -status. - -*/ - + // Handles clicks on collapsed button handleCollapsedClick = (e) => { const { collapsed, setExpansion } = this.props; if (e.button === 0) { @@ -125,29 +70,13 @@ status. } } -/* - -#### `handleAccountClick()`. - -`handleAccountClick()` handles any clicks on the header info. It calls -`parseClick()` with our `account` as the anticipatory `destination`. - -*/ - + // Handles clicks on account name/image handleAccountClick = (e) => { const { status, parseClick } = this.props; parseClick(e, `/accounts/${+status.getIn(['account', 'id'])}`); } -/* - -#### `render()`. - -`render()` actually puts our element on the screen. `<StatusHeader>` -has a very straightforward rendering process. - -*/ - + // Rendering. render () { const { status, @@ -162,16 +91,28 @@ has a very straightforward rendering process. return ( <header className='status__info'> - { - -/* - -We have to include the status icons before the header content because -it is rendered as a float. - -*/ - - } + <a + href={account.get('url')} + target='_blank' + className='status__avatar' + onClick={this.handleAccountClick} + > + { + friend ? ( + <AvatarOverlay account={account} friend={friend} /> + ) : ( + <Avatar account={account} size={48} /> + ) + } + </a> + <a + href={account.get('url')} + target='_blank' + className='status__display-name' + onClick={this.handleAccountClick} + > + <DisplayName account={account} /> + </a> <div className='status__info__icons'> {mediaIcon ? ( <i @@ -197,32 +138,6 @@ it is rendered as a float. /> ) : null} </div> - { - -/* - -This begins our header content. It is all wrapped inside of a link -which gets handled by `handleAccountClick`. We use an `<AvatarOverlay>` -if we have a `friend` and a normal `<Avatar>` if we don't. - -*/ - - } - <a - href={account.get('url')} - target='_blank' - className='status__display-name' - onClick={this.handleAccountClick} - > - <div className='status__avatar'>{ - friend ? ( - <AvatarOverlay account={account} friend={friend} /> - ) : ( - <Avatar account={account} size={48} /> - ) - }</div> - <DisplayName account={account} /> - </a> </header> ); diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index 8c5b5e0b9..ca4b14b82 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -73,8 +73,23 @@ export default class IconButton extends React.PureComponent { classes.push(this.props.className); } + const flipDeg = this.props.flip ? -180 : -360; + const rotateDeg = this.props.active ? flipDeg : 0; + + const motionDefaultStyle = { + rotate: rotateDeg, + }; + + const springOpts = { + stiffness: this.props.flip ? 60 : 120, + damping: 7, + }; + const motionStyle = { + rotate: this.props.animate ? spring(rotateDeg, springOpts) : 0, + }; + return ( - <Motion defaultStyle={{ rotate: this.props.active ? (this.props.flip ? -180 : -360) : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? (this.props.flip ? -180 : -360) : 0, { stiffness: this.props.flip ? 60 : 120, damping: 7 }) : 0 }}> + <Motion defaultStyle={motionDefaultStyle} style={motionStyle}> {({ rotate }) => <button aria-label={this.props.title} diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 03f4f0800..fb1922113 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -550,6 +550,7 @@ .status__content, .reply-indicator__content { position: relative; + padding: 5px 12px; font-size: 15px; line-height: 20px; color: $primary-text-color; @@ -660,7 +661,6 @@ .status { padding: 8px 10px; - padding-left: 68px; position: relative; height: auto; min-height: 48px; @@ -736,7 +736,7 @@ content: ""; } - .status__display-name:hover strong { + .display-name:hover .display-name__html { text-decoration: none; } @@ -752,7 +752,7 @@ } .notification__message { - margin: -10px 0 10px; + margin: -10px -10px 10px; } } @@ -780,26 +780,21 @@ } .status__display-name { + margin: 0 auto 0 0; color: $ui-base-lighter-color; } -.status__info .status__display-name { - display: block; - max-width: 100%; -} - .status__info { - margin: 2px 0 0; + display: flex; + margin: 2px 0 5px; font-size: 15px; line-height: 24px; } .status__info__icons { - display: inline-block; + flex: none; position: relative; - float: right; color: lighten($ui-base-color, 26%); - z-index: 5; // to make it clickable .status__visibility-icon { padding-left: 6px; @@ -842,15 +837,7 @@ .status__action-bar { align-items: center; display: flex; - margin-top: 10px; - margin-left: -58px; - - &::before { - display: block; - flex: 1 1 0; - max-width: 58px; - content: ""; - } + margin: 10px 12px 0; } .status__action-bar-button { @@ -983,8 +970,7 @@ .account__avatar-wrapper { float: left; - margin-left: 12px; - margin-right: 12px; + margin: 6px 16px 6px 6px; } .account__avatar { @@ -1000,6 +986,7 @@ } .account__avatar-overlay { + position: relative; @include avatar-size(48px); &-base { @@ -1020,7 +1007,7 @@ .account__relationship { height: 18px; - padding: 10px; + padding: 12px 10px; white-space: nowrap; } @@ -1268,15 +1255,6 @@ } } -.status__display-name, -.reply-indicator__display-name, -.detailed-status__display-name, -.account__display-name { - &:hover strong { - text-decoration: underline; - } -} - .account__display-name strong { display: block; } @@ -1312,8 +1290,8 @@ } .status__avatar { - position: absolute; - margin-left: -58px; + flex: none; + margin: 0 10px 0 0; height: 48px; width: 48px; } @@ -1344,9 +1322,7 @@ } .notification__message { - margin-left: 68px; - padding: 8px 0; - padding-bottom: 0; + padding: 8px 10px 0; cursor: default; color: $ui-primary-color; font-size: 15px; @@ -1358,8 +1334,10 @@ } .notification__favourite-icon-wrapper { - left: -26px; - position: absolute; + float: left; + margin: 0 10px 0 0; + width: 48px; + text-align: right; .star-icon { color: $gold-star; @@ -1383,28 +1361,37 @@ .display-name { display: block; - position: relative; + padding: 6px 0; max-width: 100%; - //overflow: hidden; - //text-overflow: ellipsis; - //white-space: nowrap; -} + height: 36px; + overflow: hidden; -.display-name__html { - font-weight: 500; -} + strong { + display: block; + height: 18px; + font-size: 16px; + font-weight: 500; + line-height: 18px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } -.display-name__account { - font-size: 14px; - display: block; - line-height: 1.1; // reduce the distance from the display name - padding-bottom: 3px; + span { + display: block; + height: 18px; + font-size: 15px; + line-height: 18px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } - // block ellipsis - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + &:hover { + strong { + text-decoration: underline; + } + } } .status__relative-time, @@ -3896,17 +3883,7 @@ button.icon-button.active i.fa-retweet { flex-direction: column; .status__display-name { - display: block; - max-width: 100%; - padding-right: 25px; - } - - .status__avatar { - height: 28px; - left: 10px; - position: absolute; - top: 10px; - width: 48px; + display: flex; } } |