Dynamically update the brand colors (#15432)

* Added alias for app/javascript/controllers

* Added hooks for Stimulus controller.

* Fixed eslint issue with @controllers alias.

* Initial working logo preview.

* Added explicit accept values for png and svg files only for a logo.

* Fixed content layout shift issue and resize to max height 80px.

* Cleaned up logo preview resizing.

* Added focus style to Upload logo label.

* Now the logo preview image has empty alt text as it's visual only.

* Fixed position of upload logo button.

* Removed tooltip for logo.

* Fixed check to load client-side controller.

* Put back tooltip, minus the aria-describedby

* Fixed E2E tests I broke.

* Made the logo preview visible to the accessibility tree.

* feat: update the brand colors on the page when we select a new one

* feat: update the radio button_tags to use crayons-radio

* feat: remove the fill attributes in the svg

* Added support for JPG image upload.

* Fixed height adjustment when width exceeds max preview width.

* feat: add form-background class with an accent

* feat: update the briightness accent on the page

* Fixed JS error if user cancelled file selection for a logo.

* Added the @routes webpack alias for routes.js.erb.

* Fixed data tooltip for assistive technologies.

* Fixed preview logo alignment with upload logo button.

* remove required as it's not doing anything

* feat: update the code brigtness code

* Opting to not show friendly error message if route fails to load.

* Fixed validation message not appearing for logo.

* Revert "Added the @routes webpack alias for routes.js.erb."

This reverts commit 3b6621dcde541f2fa05df6ff75af38955842b88e.

* Reverted to default styling of input[type="file"].

* Moved creator_settings_controller to admin/controllers.

* Updated E2E test for logo preview on the creator settings page.

* create tests for the brand color updates

* feat: update the description

* Update cypress/integration/creatorOnboardingFlows/creatorSettings.spec.js

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>

* Update app/javascript/admin/controllers/creator_settings_controller.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* feat: do not update branding if an invalid color is provided

* feat: move the brightness code to its own accent calculator file in js utilities

* test: brightness ratios

* chore: remove whitespace

Co-authored-by: Nick Taylor <nick@dev.to>
Co-authored-by: Nick Taylor <nick@iamdeveloper.com>
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
Ridhwana 2021-11-23 09:46:17 +02:00 committed by GitHub
parent 33757683ec
commit 8060f78893
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 140 additions and 11 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -181,3 +181,7 @@
}
}
}
.forem-background {
fill: var(--accent-brand);
}

View file

@ -1,4 +1,5 @@
import { Controller } from '@hotwired/stimulus';
import { brightness } from '../../utilities/color/accentCalculator';
const MAX_LOGO_PREVIEW_HEIGHT = 80;
const MAX_LOGO_PREVIEW_WIDTH = 220;
@ -67,4 +68,29 @@ export class CreatorSettingsController extends Controller {
reader.readAsDataURL(firstFile);
}
/**
* Updates ths branding/colors on the Creator Settings Page.
*
* @param {Event} event
*/
updateBranding(event) {
const { value: color } = event.target;
if (!new RegExp(event.target.getAttribute('pattern')).test(color)) {
return;
}
document.documentElement.style.setProperty('--accent-brand', color);
// We need to recalculate '--accent-brand-darker' in javascript as it's
// currently being calculated in ruby. It is used for the hover effect
// over the button.
// 0.85 represents the brightness value set in Ruby to calculate
// '--accent-brand-darker'
document.documentElement.style.setProperty(
'--accent-brand-darker',
brightness(color, 0.85),
);
}
}

View file

@ -0,0 +1,9 @@
import { brightness } from '@utilities/color/accentCalculator';
describe('Color: Accent Calculator Utilities', () => {
it('should return a hex with the adjusted brightness', () => {
expect(brightness('#ccddee', 0.5)).toBe('#666f77');
expect(brightness('#ccddee')).toBe('#ccddee');
expect(brightness('#41625c', 0.85)).toBe('#37534e');
});
});

View file

@ -0,0 +1,56 @@
/**
* Updates the brightness of the color
* @param {String} color
* @param {Integer} amount
* Based on the ruby implementation
* https://github.com/forem/forem/blob/main/app/services/color/compare_hex.rb
*/
export function brightness(color, amount = 1) {
const rgbObj = hexToRgb(color);
Object.keys(rgbObj).forEach((key) => {
rgbObj[key] = Math.round(rgbObj[key] * amount);
});
return rgbToHex(rgbObj['r'], rgbObj['g'], rgbObj['b']);
}
/**
* Converts the HEX color to an RGB color
* @param {String} Hex color
* @returns {Object} An object with keys for R,G,B based on the HEX color
* @returns {null} If we cannot determine a hex pattern from the string
*/
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
/**
* Converts the RGB parameters to a HEX String
* @param {String} Red value from RGB
* @param {String} Green value from RGB
* @param {String} Blue value from RGB
* @returns {String} The converted HEX String.
*/
function rgbToHex(r, g, b) {
return `#${rgbParameterToHex(r)}${rgbParameterToHex(g)}${rgbParameterToHex(
b,
)}`;
}
/**
* Converts each RGB parameter to its corresponding HEX value
* @param {param} This will be either red, green or blue from RGB.
* @returns {String} The converted number to its two digit HEX String.
*/
function rgbParameterToHex(param) {
const hex = param.toString(16);
return hex.length == 1 ? `0${hex}` : hex;
}

View file

@ -35,13 +35,16 @@
<%= text_field_tag :primary_brand_color_hex,
::Settings::UserExperience.primary_brand_color_hex,
pattern: "^#+([a-fA-F0-9]{6})$",
title: "Provide a valid HEX Color or pick your color from the color picker.",
placeholder: ::Settings::UserExperience.primary_brand_color_hex,
class: "crayons-textfield js-color-field" %>
class: "crayons-textfield js-color-field",
"data-action": "change->creator-settings#updateBranding" %>
<%= color_field_tag :primary_brand_color_hex,
::Settings::UserExperience.primary_brand_color_hex,
pattern: "^#+([a-fA-F0-9]{6})$",
placeholder: ::Settings::UserExperience.primary_brand_color_hex,
class: "crayons-color-selector js-color-field ml-2",
"data-action": "change->creator-settings#updateBranding",
required: true %>
</div>
</div>
@ -52,11 +55,11 @@
<legend class="crayons-field__label mb-2">Who can join this community?</legend>
<div>
<div class="mb-2">
<%= radio_button_tag :invite_only_mode, "0", class: "crayons-field crayons-field--radio", required: true %>
<%= radio_button_tag :invite_only_mode, "0", false, class: "crayons-radio" %>
<label for="invite_only_mode_0">Everyone</label>
</div>
<div>
<%= radio_button_tag :invite_only_mode, "1", class: "crayons-field crayons-field--radio", required: true %>
<%= radio_button_tag :invite_only_mode, "1", true, class: "crayons-radio" %>
<label for="invite_only_mode_1">Only people who are invited</label>
</div>
</div>
@ -68,11 +71,11 @@
<legend class="crayons-field__label mb-2">Who can view content in this community?</legend>
<div>
<div class="mb-2">
<%= radio_button_tag :public, "0", class: "crayons-field crayons-field--radio", required: true %>
<%= radio_button_tag :public, "0", false, class: "crayons-radio" %>
<label for="public_0">Everyone</label>
</div>
<div>
<%= radio_button_tag :public, "1", class: "crayons-field crayons-field--radio", required: true %>
<%= radio_button_tag :public, "1", true, class: "crayons-radio" %>
<label for="public_1">Members only</label>
</div>
</div>

View file

@ -22,7 +22,7 @@
<button class="color-accent-brand text-underline cursor-pointer js-confirmation-button border-none p-0" role="button">Click here</button> if you didn't get the email...
</div>
<%= inline_svg_tag("forem-background.svg", aria: true, title: "forem background", class: "absolute bottom-0 right-0 hidden m:block") %>
<%= inline_svg_tag("forem-background.svg", aria: true, title: "forem background", class: "forem-background absolute bottom-0 right-0 hidden m:block") %>
<div id="confirm-email-modal" class="hidden">
<div>Re-enter the email address below to resend the confirmation link</div>

View file

@ -68,7 +68,7 @@
</header>
<div class="flex items-center">
<%= f.submit t("views.listings.edit.bump.button"), class: "crayons-btn crayons-btn--secondary mr-4" %>
<p class="color-base-70"><%= t("views.listings.edit.bump.last", date: time_ago_in_words(@listing.bumped_at, scope: :'datetime.distance_in_words_ago')) %></p>
<p class="color-base-70"><%= t("views.listings.edit.bump.last", date: time_ago_in_words(@listing.bumped_at, scope: :"datetime.distance_in_words_ago")) %></p>
</div>
<input type="hidden" name="listing[action]" value="bump" />
<% end %>

View file

@ -100,5 +100,5 @@
</div>
<% end %>
</div>
<%= inline_svg_tag("forem-background.svg", aria: true, title: "forem background", class: "absolute bottom-0 right-0 hidden m:block") %>
<%= inline_svg_tag("forem-background.svg", aria: true, title: "forem background", class: "forem-background absolute bottom-0 right-0 hidden m:block") %>
</main>

View file

@ -33,7 +33,7 @@
<div class="profile-header__actions">
<button id="user-follow-butt" class="crayons-btn whitespace-nowrap follow-action-button follow-user" data-info='{"id":<%= @user.id %>,"className":"<%= @user.class.name %>", "name": "<%= @user.name %>"}'><%= t("views.users.follow") %></button>
<div class="profile-dropdown ml-2 s:relative hidden" data-username="<%= @user.username %>">
<div class="profile-dropdown ml-2 s:relative hidden" data-username="<%= @user.username %>">
<button id="user-profile-dropdown" aria-expanded="false" aria-controls="user-profile-dropdownmenu" aria-haspopup="true" class="crayons-btn crayons-btn--ghost-dimmed crayons-btn--icon">
<%= inline_svg_tag("overflow-horizontal.svg", class: "dropdown-icon crayons-icon", aria: true, title: t("views.users.dropdown")) %>
</button>

View file

@ -73,6 +73,37 @@ describe('Creator Settings Page', () => {
cy.url().should('equal', baseUrl);
});
it('should update the colors on the form when a new brand color is selected', () => {
const color = '#25544b';
const rgbColor = 'rgb(37, 84, 75)';
cy.findByLabelText(/^Brand color/)
.clear()
.type(color)
.trigger('change');
cy.findByRole('button', { name: 'Finish' }).should(
'have.css',
'background-color',
rgbColor,
);
cy.findAllByRole('radio', { name: /members only/i })
.check()
.should('have.css', 'background-color', rgbColor)
.should('have.css', 'border-color', rgbColor);
cy.findByRole('textbox', { name: /community name/i })
.focus()
.should('have.css', 'border-color', rgbColor);
cy.findByRole('link', { name: /Forem Admin Guide/i }).should(
'have.css',
'background-color',
rgbColor,
);
});
it('should not submit the creator settings form if any of the fields are not filled out', () => {
// TODO: Circle back around to testing this once the styling for the form is complete
cy.findByRole('textbox', { name: /community name/i }).should(