Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
> v0.4.3 ~ "A shared sign-in page button style"
> v0.4.3 ~ "Shared sign-in page buttons"

---
## Highlights

- **`btn-auth`, a neutral button style for sign-in pages** — an opt-in wrapper class (`@wrapperClass="btn-block btn-auth"`) that gives the console's "Continue with ..." provider buttons and the extension buttons below them (Customer Portal, Track Order) one look: white with a gray-300 border in light mode, gray-900 with a gray-700 border in dark, and a hover that shifts the background and strengthens the border. It replaces each extension's own styling, whose hover faded the button to half opacity. Colours are custom properties on the wrapper, so a single button can be re-coloured, a provider's brand colour say, and keep the same border, hover and transition.
- **`<OauthProviderButton>`, a "Continue with …" button for sign-in providers** — a btn-auth block button with the provider's logo at the far left, re-coloured where a provider's guidelines call for it: Google and Microsoft on white with their full-colour logos (Google does not allow a single-colour "G"), Apple black, GitHub near-black, and every provider on gray-900 in dark mode, where Google's "G" sits on the white disc Google requires. It moves the console's provider buttons into ember-ui so the Fleetbase Cloud sign-up page can use the same ones. `<OauthProviderLogo>` renders a provider's logo on its own.

---
## Need help?
Expand Down
20 changes: 20 additions & 0 deletions addon/components/oauth-provider-button.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{!-- A "Continue with <provider>" button for sign-in and sign-up pages: the btn-auth
block button, re-coloured for the providers whose guidelines call for it (see
button.css), with the provider's logo in the usual icon slot at the far left.

@provider { id, label, icon } as the API's provider list returns it
@text the button text; defaults to the provider's label
@disabled
@onClick called with the provider --}}
<Button
@wrapperClass="btn-block btn-auth oauth-provider-{{@provider.id}}"
@disabled={{@disabled}}
@onClick={{fn this.onClick @provider}}
data-test-oauth-provider={{@provider.id}}
...attributes
>
<span class="btn-icon-wrapper">
<OauthProviderLogo @provider={{@provider.id}} @icon={{@provider.icon}} />
</span>
<span>{{or @text @provider.label}}</span>
</Button>
10 changes: 10 additions & 0 deletions addon/components/oauth-provider-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class OauthProviderButtonComponent extends Component {
@action onClick(provider) {
if (typeof this.args.onClick === 'function') {
this.args.onClick(provider);
}
}
}
27 changes: 27 additions & 0 deletions addon/components/oauth-provider-logo.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{{!-- A sign-in provider's own logo, shown as its brand guidelines require.
Google and Microsoft must be full colour (a single-colour Google "G" is not
allowed), so they are inline SVG. Apple and GitHub are single colour by design
and follow the button's text colour. Any other provider falls back to its icon. --}}
{{#if (eq @provider "google")}}
<span class="oauth-provider-logo oauth-provider-logo-google" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="16" height="16">
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" />
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" />
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
</svg>
</span>
{{else if (eq @provider "microsoft")}}
<span class="oauth-provider-logo oauth-provider-logo-microsoft" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" width="16" height="16">
<rect x="1" y="1" width="9" height="9" fill="#F25022" />
<rect x="11" y="1" width="9" height="9" fill="#7FBA00" />
<rect x="1" y="11" width="9" height="9" fill="#00A4EF" />
<rect x="11" y="11" width="9" height="9" fill="#FFB900" />
</svg>
</span>
{{else if @icon}}
<span class="oauth-provider-logo oauth-provider-logo-{{@provider}}" aria-hidden="true">
<FaIcon @icon={{@icon}} @prefix="fab" />
</span>
{{/if}}
47 changes: 47 additions & 0 deletions addon/styles/components/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,50 @@ body .btn-wrapper.btn-auth > button.btn:focus {
border-color: var(--btn-auth-border-hover);
color: var(--btn-auth-text);
}

/*
* Sign-in provider buttons (<OauthProviderButton>): btn-auth, re-coloured where a
* provider's guidelines call for it. Google and Microsoft keep btn-auth's neutral white
* (their logos are full colour and belong on white). Apple is black (Human Interface
* Guidelines) and GitHub near-black (its mark is only shown in black or white). In dark
* mode every provider uses btn-auth's gray-900: the dark rule above outranks these.
*/
.btn-wrapper.btn-auth.oauth-provider-apple {
--btn-auth-bg: #000000;
--btn-auth-bg-hover: #262626;
--btn-auth-border: #000000;
--btn-auth-border-hover: #262626;
--btn-auth-text: #ffffff;
}

.btn-wrapper.btn-auth.oauth-provider-github {
--btn-auth-bg: #24292f;
--btn-auth-bg-hover: #3a4149;
--btn-auth-border: #24292f;
--btn-auth-border-hover: #3a4149;
--btn-auth-text: #ffffff;
}

.oauth-provider-logo {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
}

.oauth-provider-logo svg {
width: 16px;
height: 16px;
}

/* Google requires its "G" on white, so on a dark button it sits on a white disc. */
body[data-theme='dark'] .oauth-provider-logo-google {
background-color: #ffffff;
border-radius: 9999px;
padding: 2px;
}

body[data-theme='dark'] .oauth-provider-logo-google svg {
width: 14px;
height: 14px;
}
1 change: 1 addition & 0 deletions app/components/oauth-provider-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-ui/components/oauth-provider-button';
1 change: 1 addition & 0 deletions app/components/oauth-provider-logo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-ui/components/oauth-provider-logo';
60 changes: 60 additions & 0 deletions tests/integration/components/oauth-provider-button-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render, click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | oauth-provider-button', function (hooks) {
setupRenderingTest(hooks);

hooks.beforeEach(function () {
this.provider = { id: 'google', label: 'Google', icon: 'google' };
});

test('it is a btn-auth block button carrying the provider', async function (assert) {
await render(hbs`<OauthProviderButton @provider={{this.provider}} />`);

assert.dom('.btn-wrapper').hasClass('btn-block').hasClass('btn-auth').hasClass('oauth-provider-google');
assert.dom('[data-test-oauth-provider="google"]').exists();
});

test('the logo sits in the icon slot, at the far left', async function (assert) {
await render(hbs`<OauthProviderButton @provider={{this.provider}} />`);

assert.dom('button .btn-icon-wrapper .oauth-provider-logo-google').exists();
});

test('it shows the given text, or the provider label', async function (assert) {
await render(hbs`<OauthProviderButton @provider={{this.provider}} @text="Continue with Google" />`);
assert.dom('button').hasText('Continue with Google');

await render(hbs`<OauthProviderButton @provider={{this.provider}} />`);
assert.dom('button').hasText('Google');
});

test('clicking calls onClick with the provider', async function (assert) {
this.clicked = [];
this.onClick = (provider) => this.clicked.push(provider);
await render(hbs`<OauthProviderButton @provider={{this.provider}} @onClick={{this.onClick}} />`);

await click('button');

assert.deepEqual(this.clicked, [this.provider]);
});

test('a disabled button does not call onClick', async function (assert) {
this.clicked = [];
this.onClick = (provider) => this.clicked.push(provider);
await render(hbs`<OauthProviderButton @provider={{this.provider}} @disabled={{true}} @onClick={{this.onClick}} />`);

assert.dom('button').isDisabled();
assert.deepEqual(this.clicked, []);
});

test('clicking without an onClick does nothing', async function (assert) {
await render(hbs`<OauthProviderButton @provider={{this.provider}} />`);

await click('button');

assert.dom('button').exists();
});
});
43 changes: 43 additions & 0 deletions tests/integration/components/oauth-provider-logo-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | oauth-provider-logo', function (hooks) {
setupRenderingTest(hooks);

test('google gets its full-colour G, never a single-colour icon', async function (assert) {
await render(hbs`<OauthProviderLogo @provider="google" @icon="google" />`);

const fills = [...this.element.querySelectorAll('path')].map((path) => path.getAttribute('fill'));

assert.deepEqual(fills, ['#EA4335', '#4285F4', '#FBBC05', '#34A853']);
assert.dom('.svg-inline--fa').doesNotExist();
});

test('microsoft gets its four-colour logo', async function (assert) {
await render(hbs`<OauthProviderLogo @provider="microsoft" @icon="microsoft" />`);

const fills = [...this.element.querySelectorAll('rect')].map((rect) => rect.getAttribute('fill'));

assert.deepEqual(fills, ['#F25022', '#7FBA00', '#00A4EF', '#FFB900']);
});

test('apple, github and any later provider use their brand icon', async function (assert) {
await render(hbs`<OauthProviderLogo @provider="github" @icon="github" />`);

assert.dom('.oauth-provider-logo-github .svg-inline--fa').exists();
});

test('it renders nothing for an unknown provider with no icon', async function (assert) {
await render(hbs`<OauthProviderLogo @provider="okta" />`);

assert.dom('.oauth-provider-logo').doesNotExist();
});

test('it is hidden from screen readers; the button text names the provider', async function (assert) {
await render(hbs`<OauthProviderLogo @provider="google" />`);

assert.dom('.oauth-provider-logo').hasAttribute('aria-hidden', 'true');
});
});
Loading