Skip to content

Commit 7ceb5c8

Browse files
committed
This is a mess
1 parent 28ec69b commit 7ceb5c8

File tree

19 files changed

+435
-354
lines changed

19 files changed

+435
-354
lines changed

src/amo/components/AddonBadges/index.js

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
/* @flow */
22
import * as React from 'react';
33
import { connect } from 'react-redux';
4+
import { withRouter } from 'react-router-dom';
45
import { compose } from 'redux';
56

7+
import { reviewListURL } from 'amo/reducers/reviews';
68
import { CLIENT_APP_FIREFOX } from 'amo/constants';
79
import translate from 'amo/i18n/translate';
810
import { getPromotedCategory } from 'amo/utils/addons';
9-
import Badge from 'amo/components/Badge';
11+
import Badge, {
12+
BadgeContent,
13+
BadgeIcon,
14+
} from 'amo/components/Badge';
15+
import Link from 'amo/components/Link';
1016
import PromotedBadge from 'amo/components/PromotedBadge';
1117
import type { AppState } from 'amo/store';
1218
import type { AddonType } from 'amo/types/addons';
1319
import type { I18nType } from 'amo/types/i18n';
20+
import type { ReactRouterLocationType } from 'amo/types/router';
1421

1522
import './styles.scss';
1623

@@ -31,13 +38,40 @@ type InternalProps = {|
3138
...DefaultProps,
3239
...PropsFromState,
3340
i18n: I18nType,
41+
location: ReactRouterLocationType,
3442
|};
3543

44+
export const roundToOneDigit = (value: number | null): number => {
45+
return value ? Math.round(value * 10) / 10 : 0;
46+
};
47+
3648
export class AddonBadgesBase extends React.Component<InternalProps> {
3749
static defaultProps: DefaultProps = {
3850
_getPromotedCategory: getPromotedCategory,
3951
};
4052

53+
renderRatingMeta(): React.Node {
54+
const { addon, i18n, location } = this.props;
55+
56+
if (!addon?.ratings) return null;
57+
58+
const addonRatingCount: number = addon.ratings.count;
59+
const averageRating: number = addon.ratings.average;
60+
const roundedAverage = roundToOneDigit(averageRating || null);
61+
62+
const reviewCount = i18n.formatNumber(addonRatingCount);
63+
const reviewsLink = reviewListURL({ addonSlug: addon.slug, location });
64+
65+
return (
66+
<Badge link={reviewsLink || undefined} type="rating">
67+
<BadgeIcon name="star-full" alt="" />
68+
<BadgeContent>
69+
{`${roundedAverage} (${reviewCount || 0} reviews)`}
70+
</BadgeContent>
71+
</Badge>
72+
);
73+
}
74+
4175
render(): null | React.Node {
4276
const { _getPromotedCategory, addon, clientApp, i18n } = this.props;
4377

@@ -57,20 +91,27 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
5791
<PromotedBadge category={promotedCategory} size="large" />
5892
) : null}
5993
{addon.is_experimental ? (
60-
<Badge type="experimental" label={i18n.gettext('Experimental')} />
94+
<Badge type="experimental">
95+
<BadgeIcon name="experimental-badge" alt="" />
96+
<BadgeContent>{i18n.gettext('Experimental')}</BadgeContent>
97+
</Badge>
6198
) : null}
6299
{addon.requires_payment ? (
63-
<Badge
64-
type="requires-payment"
65-
label={i18n.gettext('Some features may require payment')}
66-
/>
100+
<Badge type="requires-payment">
101+
<BadgeContent>
102+
{i18n.gettext('Some features may require payment')}
103+
</BadgeContent>
104+
</Badge>
67105
) : null}
68106
{clientApp === CLIENT_APP_FIREFOX && addon.isAndroidCompatible && (
69-
<Badge
70-
type="android-compatible"
71-
label={i18n.gettext('Available on Firefox for Android™')}
72-
/>
107+
<Badge type="android-compatible">
108+
<BadgeIcon name="android" alt="" />
109+
<BadgeContent>
110+
{i18n.gettext('Available on Firefox for Android™')}
111+
</BadgeContent>
112+
</Badge>
73113
)}
114+
{this.renderRatingMeta()}
74115
</div>
75116
);
76117
}
@@ -83,6 +124,7 @@ const mapStateToProps = (state: AppState): PropsFromState => {
83124
};
84125

85126
const AddonBadges: React.ComponentType<Props> = compose(
127+
withRouter,
86128
connect(mapStateToProps),
87129
translate(),
88130
)(AddonBadgesBase);

src/amo/components/AddonBadges/styles.scss

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
@import '~amo/css/styles';
22

33
.AddonBadges {
4-
width: 100%;
4+
display: grid;
5+
gap: 6px;
6+
justify-items: start;
7+
8+
@include respond-to(medium) {
9+
display: flex;
10+
flex-wrap: wrap;
11+
align-items: center;
12+
}
513

614
.Addon-theme & {
715
display: flex;
@@ -27,7 +35,6 @@
2735
}
2836

2937
@include respond-to(large) {
30-
@include margin-start(12px);
3138
@include text-align-end;
3239

3340
.Addon-theme & {

src/amo/components/AddonTitle/styles.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
color: $black;
77
font-size: $font-size-heading-s;
88
grid-column: 1 / span 2;
9-
margin: 0 0 $padding-page-l;
9+
margin: 0;
1010
width: 100%;
1111

1212
@include respond-to(medium) {

src/amo/components/AddonVersionCard/styles.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
.InstallButtonWrapper {
88
grid-column: 1;
99
grid-row: 3;
10-
width: 100%;
1110

1211
.Button {
1312
width: 100%;

src/amo/components/Badge/index.js

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,74 @@
11
/* @flow */
22
import * as React from 'react';
3+
import makeClassName from 'classnames';
34

45
import Icon from 'amo/components/Icon';
56

67
import './styles.scss';
78

8-
export type Props = {|
9-
label: string,
10-
type?: 'experimental' | 'requires-payment' | 'android-compatible',
11-
|};
9+
export const BadgeIcon = ({
10+
name,
11+
alt,
12+
className,
13+
size = 'large',
14+
}: {|
15+
name: string,
16+
alt: string,
17+
className?: string,
18+
size?: 'large' | 'small',
19+
|}): React.Node => (
20+
<Icon
21+
name={name}
22+
alt={alt}
23+
className={makeClassName(
24+
'Badge-icon',
25+
size ? `Badge-icon--${size}` : null,
26+
className,
27+
)}
28+
/>
29+
);
1230

13-
const getIconNameForType = (type) => {
14-
switch (type) {
15-
case 'experimental':
16-
return 'experimental-badge';
17-
case 'android-compatible':
18-
return 'android';
19-
default:
20-
}
31+
export const BadgeContent = ({ children }: {| children: React.Node |}): React.Node => (
32+
<span className="Badge-content">{children}</span>
33+
);
2134

22-
return type;
23-
};
35+
export type BadgeType =
36+
| 'experimental'
37+
| 'requires-payment'
38+
| 'android-compatible'
39+
| 'rating';
2440

25-
const Badge = ({ label, type }: Props): React.Node => {
26-
if (
27-
type &&
28-
!['experimental', 'requires-payment', 'android-compatible'].includes(type)
29-
) {
30-
throw new Error(`Invalid badge type given: "${type}"`);
31-
}
41+
export type Props = {|
42+
children: React.Node,
43+
type?: BadgeType,
44+
link?: string,
45+
className?: string,
46+
|};
3247

48+
const Badge = ({ children, type, link, className }: Props): React.Node => {
3349
return (
34-
<div className={type ? `Badge Badge-${type}` : 'Badge'}>
35-
{type && <Icon alt={label} name={getIconNameForType(type)} />}
36-
{label}
50+
<div
51+
className={makeClassName(
52+
'Badge',
53+
type ? `Badge-${type}` : null,
54+
{
55+
'Badge-border': !!link,
56+
},
57+
className,
58+
)}
59+
>
60+
{link ? (
61+
<a
62+
href={link}
63+
target="_blank"
64+
rel="noopener noreferrer"
65+
className="Badge-link"
66+
>
67+
{children}
68+
</a>
69+
) : (
70+
children
71+
)}
3772
</div>
3873
);
3974
};

src/amo/components/Badge/styles.scss

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,88 @@
11
@import '~amo/css/styles';
22

33
.Badge {
4-
color: $grey-90;
5-
font-size: $font-size-default;
4+
color: $color-dark-gray-05;
65
line-height: $line-height-compressed;
7-
margin: 0 0 6px;
6+
display: flex;
7+
align-items: center;
8+
white-space: nowrap;
9+
margin-top: 6px;
10+
padding: 3px 8px;
11+
12+
&.PromotedBadge--recommended {
13+
color: $orange-50;
14+
}
15+
16+
.Badge-link {
17+
align-items: center;
18+
color: inherit;
19+
display: flex;
20+
text-decoration: none;
21+
}
22+
23+
&.Badge-border {
24+
border: 1px solid $color-light-gray-40;
25+
border-radius: 20px;
26+
27+
display: inline-flex;
28+
box-sizing: border-box;
29+
30+
&:hover {
31+
border-color: $color-light-gray-90;
32+
}
33+
}
834

935
@include respond-to(large) {
1036
display: flex;
1137
flex-direction: row-reverse;
12-
margin: 0 0 6px;
1338

1439
.Addon-theme & {
1540
flex-direction: row;
1641
}
1742
}
1843

1944
.Icon {
20-
@include margin(2px, 6px, 0, 0);
45+
vertical-align: top;
46+
}
47+
}
2148

22-
@include respond-to(large) {
23-
@include margin(2px, 0, 0, 6px);
24-
}
49+
.Badge-icon + .Badge-content {
50+
@include margin-start(6px);
51+
}
2552

26-
vertical-align: top;
53+
// This is for `BadgeIcon`
54+
.Badge-icon {
55+
// From legacy `IconPromotedBadge` component
56+
box-sizing: border-box;
57+
display: flex;
58+
height: auto;
59+
width: auto;
60+
}
61+
62+
$badge-icon-large-size: 24px;
63+
$badge-icon-small-size: 16px;
64+
65+
.Badge-icon--large {
66+
background-size: $badge-icon-large-size;
67+
height: $badge-icon-large-size;
68+
width: $badge-icon-large-size;
69+
70+
&.Icon-line {
71+
transform: scale(0.9);
2772
}
73+
74+
&.Icon-recommended {
75+
// Nudge the trophy into the center of the circle.
76+
transform: translate(2px, 0) scale(0.8);
77+
78+
[dir='rtl'] & {
79+
transform: translate(-2px, 0) scale(0.8);
80+
}
81+
}
82+
}
83+
84+
.Badge-icon--small {
85+
background-size: $badge-icon-small-size;
86+
height: $badge-icon-small-size;
87+
width: $badge-icon-small-size;
2888
}
Lines changed: 1 addition & 1 deletion
Loading

src/amo/components/Icon/styles.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@
180180
@include icon($name: 'recommended');
181181
}
182182

183+
.Icon-star-full {
184+
@include icon($name: 'star-full');
185+
}
186+
183187
.Icon-android {
184188
@include icon($name: 'android');
185189
}

src/amo/components/IconPromotedBadge/index.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
/* @flow */
2-
import makeClassName from 'classnames';
32
import * as React from 'react';
43
import { compose } from 'redux';
54

65
import { LINE, RECOMMENDED } from 'amo/constants';
76
import translate from 'amo/i18n/translate';
8-
import Icon from 'amo/components/Icon';
7+
import { BadgeIcon } from 'amo/components/Badge';
98
import type { I18nType } from 'amo/types/i18n';
10-
import './styles.scss';
119

1210
export type PromotedBadgeCategory = typeof LINE | typeof RECOMMENDED;
1311

@@ -39,13 +37,11 @@ export const IconPromotedBadgeBase = ({
3937
const alt = altTexts[category];
4038

4139
return (
42-
<Icon
43-
alt={showAlt && alt ? alt : undefined}
44-
className={makeClassName('IconPromotedBadge', className, {
45-
'IconPromotedBadge-large': size === 'large',
46-
'IconPromotedBadge-small': size === 'small',
47-
})}
40+
<BadgeIcon
41+
alt={showAlt && alt ? alt : ''}
42+
className={className}
4843
name={category}
44+
size={size}
4945
/>
5046
);
5147
};

0 commit comments

Comments
 (0)