Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No ticket] Preprint branding rework #1913

1 change: 0 additions & 1 deletion app/preprints/discover/template.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{{page-title (t 'preprints.discover.title')}}
<div data-analytics-scope='{{concat this.model.providerTitle ' Discover'}}'>
<SearchPage
{{with-branding (get-model this.model.brand)}}
@route='search'
@query={{this.q}}
@defaultQueryOptions={{this.defaultQueryOptions}}
Expand Down
16 changes: 9 additions & 7 deletions app/preprints/template.hbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{{page-title this.theme.provider.providerTitle replace=true}}
<ThemeStyles />
<BrandedNavbar
@translateKey='collections.general.brand'
@brandRoute='preprints.discover'
@addLinkKey='collections.navbar.add'
/>
{{outlet}}
<div {{with-branding (get-model this.theme.provider.brand)}}>
<ThemeStyles />
<BrandedNavbar
@translateKey='collections.general.brand'
@brandRoute='preprints.discover'
@addLinkKey='collections.navbar.add'
/>
{{outlet}}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { tracked } from 'tracked-built-ins';

import { serviceLinks } from 'ember-osf-web/const/service-links';
import { layout, requiredAction } from 'ember-osf-web/decorators/component';
import BrandModel from 'ember-osf-web/models/brand';
import PreprintProviderModel from 'ember-osf-web/models/preprint-provider';
import ProviderModel from 'ember-osf-web/models/provider';
import Analytics from 'ember-osf-web/services/analytics';
Expand Down Expand Up @@ -53,6 +54,7 @@ export default class BrandedNavbar extends Component {

@alias('theme.provider') provider!: ProviderModel;
@alias('theme.provider.id') providerId!: string;
@alias('theme.provider.brand.primaryColor') brandPrimaryColor!: BrandModel;

@computed('intl.locale', 'theme.{providerType,provider.providerTitle}', 'translateKey')
get brandTitle(): string {
Expand All @@ -68,7 +70,7 @@ export default class BrandedNavbar extends Component {

@action
toggleSecondaryNavigation() {
this.toggleProperty('showNavLinks');
this.showNavLinks = !this.showNavLinks;
}

get isMobileOrTablet() {
Expand Down
38 changes: 38 additions & 0 deletions lib/app-components/addon/components/branded-navbar/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
background-position-y: center;
background-size: contain;
background-repeat: no-repeat;
// This is overwritten by theme-styles component,
// but if there is no provider.asset.square_color_transparent, we fallback to use the logo off the brand
background-image: var(--navbar-logo-img-url);
}

.NavBarBuffer {
Expand Down Expand Up @@ -103,3 +106,38 @@
right: 0;
}
}

// preprint-branded navbar uses brand relationship and not custom CSS
// Extra specificity needed to override the default styles set in app/styles/_components.scss
// branded-navbar dark/light text only used for preprint providers, as collection providers currently use custom CSS
.preprint-branded-navbar.preprint-branded-navbar {
background-color: var(--primary-color);
background-image: none;

.secondary-navigation {
border-color: transparent;
}

&.light-text {
a,
:global(.secondary-nav-dropdown) {
color: $color-text-white !important;
}
}

&.dark-text {
a,
:global(.secondary-nav-dropdown) {
color: $color-text-black !important;
}
}
}

.biohackrxiv-branded-navbar.biohackrxiv-branded-navbar.biohackrxiv-branded-navbar {
background-color: #fff;

a,
:global(.secondary-nav-dropdown) {
color: $color-text-black !important;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<div local-class='branded-nav-wrapper'>
<nav
local-class='branded-nav'
local-class='branded-nav
{{if (eq this.theme.providerType 'preprint') 'preprint-branded-navbar'}}
{{if (sufficient-contrast this.brandPrimaryColor '#fff') 'light-text' 'dark-text'}}
{{if (eq this.theme.id 'biohackrxiv') 'biohackrxiv-branded-navbar'}}'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌚

class='navbar navbar-inverse navbar-fixed-top'
id='navbarScope'
>
Expand Down
8 changes: 5 additions & 3 deletions lib/app-components/addon/components/theme-styles/template.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{{!-- template-lint-disable no-bare-strings --}}
<style>
.navbar-image {
background-image: url('{{this.assets.square_color_transparent}}');
}
{{#if this.assets.square_color_transparent}}
.navbar-image {
background-image: url('{{this.assets.square_color_transparent}}');
}
{{/if}}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Component from '@glimmer/component';

export default class BrandColorInputs extends Component {
primaryColor: string | null = null;
secondaryColor: string | null = null;

@action
setPrimaryColor() {
Expand All @@ -12,4 +13,13 @@ export default class BrandColorInputs extends Component {
style.setProperty('--primary-color', this.primaryColor);
});
}

@action
setSecondaryColor() {
const brandContainers = Array.from(document.getElementsByClassName('with-custom-branding'));
// @ts-ignore - style is present on CSSStyleDeclaration
brandContainers.map(container => container.style).forEach(style => {
style.setProperty('--secondary-color', this.secondaryColor);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<label>{{t 'app_components.brand_color_inputs.primary_label'}}</label>
<Input @value={{this.primaryColor}} />
<Button {{on 'click' this.setPrimaryColor}}>{{t 'app_components.brand_color_inputs.primary_set'}}</Button>
<label>
{{t 'app_components.brand_color_inputs.primary_label'}}
<Input @value={{this.primaryColor}} />
</label>
<Button {{on 'click' this.setPrimaryColor}}>{{t 'app_components.brand_color_inputs.primary_set'}}</Button>
<br>
<label>
{{t 'app_components.brand_color_inputs.secondary_label'}}
<Input @value={{this.secondaryColor}} />
</label>
<Button {{on 'click' this.setSecondaryColor}}>{{t 'app_components.brand_color_inputs.secondary_set'}}</Button>
15 changes: 15 additions & 0 deletions lib/osf-components/addon/components/button/component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';

import Theme from 'ember-osf-web/services/theme';
import PreprintProviderModel from 'ember-osf-web/models/preprint-provider';

const layoutClasses = {
small: 'SmallButton',
Expand All @@ -24,6 +28,8 @@ interface Args {
}

export default class Button extends Component<Args> {
@service theme!: Theme;

get classList(): string {
const classes = [];
const { layout, type } = this.args;
Expand All @@ -42,4 +48,13 @@ export default class Button extends Component<Args> {

return classes.join(' ');
}

get primaryColor(): string {
if (!this.theme?.provider) {
return '#337ab7'; // $color-osf-primary;
}
// Only preprint-providers will have brands that need to be checked for color contrast
const brand = (this.theme.provider as PreprintProviderModel).brand;
return brand ? brand.get('primaryColor') : '#337ab7';
}
}
6 changes: 6 additions & 0 deletions lib/osf-components/addon/components/button/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@
}
}

// This should only be used in preprint branding as we move away from using custom CSS
// Please don't rely on this class for new brands
.DarkText {
color: $color-text-black;
}

.SecondaryButton {
background-color: $color-bg-white;
border: 1px solid $color-border-gray-light;
Expand Down
5 changes: 4 additions & 1 deletion lib/osf-components/addon/components/button/template.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<button type='button' local-class='Button {{this.classList}}' ...attributes>
<button type='button' local-class='Button {{this.classList}}
{{if (sufficient-contrast this.primaryColor '#ffffff') '' 'DarkText'}}'
...attributes
>
{{yield}}
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

.auth-trigger.auth-trigger.auth-trigger.auth-trigger {
padding: 11px 15px;
cursor: pointer;
}

@media (max-width: 767px) {
Expand Down
3 changes: 2 additions & 1 deletion lib/osf-components/addon/components/search-page/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,11 @@
width: 50vw;
}

.help-button,
.search-button {
position: relative;
right: 3px;
bottom: 4px;
bottom: 3px;
}

.topbar {
Expand Down
79 changes: 79 additions & 0 deletions lib/osf-components/addon/helpers/sufficient-contrast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { helper } from '@ember/component/helper';

/**
* Criteria is based on WCAG 2.0 Guidelines.
* https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0#qr-visual-audio-contrast-contrast
*
* Relative Luminance is calculated using the formula from WCAG 2.0 Guidelines.
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
*
* @param {Array} backgroundColor The background color in hex format
* @param {Array} foregroundColor The foreground color in hex format
* @param {Object} options {largeText: true if text is at least 18 point if not bold and at least 14 point if bold}
* @return {Boolean} Whether the contrast between the two colors is sufficient
*/

const wcagAA = {
normalText: 4.5,
largeText: 3,
};
const wcagAAA = {
normalText: 7,
largeText: 4.5,
};

function threeDigitHexToSixDigit(hex: string): string {
if (hex.length === 3) {
return hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
return hex;
}

export function sufficientContrast(
[backgroundColor, foregroundColor]: [string, string], { largeText = false, useAAA = false },
): boolean {
const standard = useAAA ? wcagAAA : wcagAA;
const threshold = largeText ? standard.largeText : standard.normalText;

if (!backgroundColor || !foregroundColor) {
return false;
}

let bg = backgroundColor.replace('#', '');
let fg = foregroundColor.replace('#', '');
bg = bg.length === 3 ? threeDigitHexToSixDigit(bg) : bg;
fg = fg.length === 3 ? threeDigitHexToSixDigit(fg) : fg;

// convert background and foreground color hex to sRGB
const bgSRGB = {
r: parseInt('0x' + bg.substring(0, 2), 16) / 255,
g: parseInt('0x' + bg.substring(2, 4), 16) / 255,
b: parseInt('0x' + bg.substring(4, 6), 16) / 255,
};
const fgSRGB = {
r: parseInt('0x' + fg.substring(0, 2), 16) / 255,
g: parseInt('0x' + fg.substring(2, 4), 16) / 255,
b: parseInt('0x' + fg.substring(4, 6), 16) / 255,
};

const bgRGBLuminance = {
r: bgSRGB.r <= 0.03928 ? bgSRGB.r / 12.92 : Math.pow((bgSRGB.r + 0.055) / 1.055, 2.4),
g: bgSRGB.g <= 0.03928 ? bgSRGB.g / 12.92 : Math.pow((bgSRGB.g + 0.055) / 1.055, 2.4),
b: bgSRGB.b <= 0.03928 ? bgSRGB.b / 12.92 : Math.pow((bgSRGB.b + 0.055) / 1.055, 2.4),
};
const fgRGBLuminance = {
r: fgSRGB.r <= 0.03928 ? fgSRGB.r / 12.92 : Math.pow((fgSRGB.r + 0.055) / 1.055, 2.4),
g: fgSRGB.g <= 0.03928 ? fgSRGB.g / 12.92 : Math.pow((fgSRGB.g + 0.055) / 1.055, 2.4),
b: fgSRGB.b <= 0.03928 ? fgSRGB.b / 12.92 : Math.pow((fgSRGB.b + 0.055) / 1.055, 2.4),
};

// calculate relative luminance
const bgLuminance = 0.2126 * bgRGBLuminance.r + 0.7152 * bgRGBLuminance.g + 0.0722 * bgRGBLuminance.b;
const fgLuminance = 0.2126 * fgRGBLuminance.r + 0.7152 * fgRGBLuminance.g + 0.0722 * fgRGBLuminance.b;

// calculate contrast ratio
const contrastRatio = (Math.max(bgLuminance, fgLuminance) + 0.05) / (Math.min(bgLuminance, fgLuminance) + 0.05);
return contrastRatio >= threshold;
}

export default helper(sufficientContrast);
1 change: 1 addition & 0 deletions lib/osf-components/app/helpers/sufficient-contrast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'osf-components/helpers/sufficient-contrast';
12 changes: 12 additions & 0 deletions mirage/fixtures/preprint-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ const preprintProviders: Array<Partial<PreprintProvider>> = [
preprintWord: 'default',
assets: randomAssets(),
},
{
id: 'agrixiv',
name: 'AgriXiv',
preprintWord: 'preprint',
assets: randomAssets(),
},
{
id: 'biohackrxiv',
name: 'BioHackrXiv',
preprintWord: 'preprint',
assets: randomAssets(),
},
];

export default preprintProviders;
28 changes: 27 additions & 1 deletion mirage/scenarios/preprints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export function preprintsScenario(
currentUser: ModelInstance<User>,
) {
const thesisCommons = server.schema.preprintProviders.find('thesiscommons') as ModelInstance<PreprintProvider>;
const brand = server.create('brand');
const brand = server.create('brand', {
primaryColor: '#821e1e',
secondaryColor: '#94918e',
heroBackgroundImage: 'https://singlecolorimage.com/get/94918e/1000x1000',
});
const currentUserModerator = server.create('moderator',
{ id: currentUser.id, user: currentUser, provider: thesisCommons }, 'asAdmin');

Expand All @@ -21,4 +25,26 @@ export function preprintsScenario(
preprints,
description: '<p style="color: red">This is the description for Thesis Commons and it has an inline-style!</p>',
});

const agrixiv = server.schema.preprintProviders.find('agrixiv') as ModelInstance<PreprintProvider>;
const agrixivBrand = server.create('brand', {
primaryColor: '#85BF9B',
secondaryColor: '#E7F7E1',
heroBackgroundImage: 'https://singlecolorimage.com/get/E7F7E1/1000x1000',
});
agrixiv.update({
brand: agrixivBrand,
description: '<p style="color: black">This is the description for agrixiv!</p>',
});

const biohackrxiv = server.schema.preprintProviders.find('biohackrxiv') as ModelInstance<PreprintProvider>;
const biohackrxivBrand = server.create('brand', {
primaryColor: '#000000',
secondaryColor: '#888888',
heroBackgroundImage: 'https://singlecolorimage.com/get/ffffff/1000x1000',
});
biohackrxiv.update({
brand: biohackrxivBrand,
description: '<p style="color: black">This is the description for biohackrxiv!</p>',
});
}
Loading
Loading