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:
parent
33757683ec
commit
8060f78893
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 |
|
|
@ -181,3 +181,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.forem-background {
|
||||
fill: var(--accent-brand);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
56
app/javascript/utilities/color/accentCalculator.js
Normal file
56
app/javascript/utilities/color/accentCalculator.js
Normal 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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue