Skip to content

Commit

Permalink
feat(mox:theme): add theme switcher component
Browse files Browse the repository at this point in the history
  • Loading branch information
jayjayjpg committed Jun 21, 2023
1 parent d8bf592 commit c4ef6f3
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 2 deletions.
25 changes: 25 additions & 0 deletions addon/components/mox/theme-switch.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div class="mox-theme-switch" ...attributes>
<div class="flex items-center space-x-2 relative {{if this.isChecked "text-gray-800"}}">
<input
id={{this.fieldId}}
aria-label={{this.label}}
type="checkbox"
class="transform appearance-none outline-none
flex items-center rounded-full p-1 cursor-pointer transition"
checked={{this.isChecked}}
{{on 'change' (fn this.switchTheme this.otherTheme)}}
data-test-mox-theme-switch
/>
<div class="mox-theme-switch-icon pointer-events-none {{if this.isChecked "is-checked"}}">
{{#if (eq this.currentTheme "dark")}}
<div class="text-white" data-test-mox-theme-switch-current="dark">
{{yield to="dark-icon"}}
</div>
{{else}}
<div class="text-gray-900" data-test-mox-theme-switch-current="light">
{{yield to="light-icon"}}
</div>
{{/if}}
</div>
</div>
</div>
42 changes: 42 additions & 0 deletions addon/components/mox/theme-switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { dasherize } from '@ember/string';
import { tracked } from '@glimmer/tracking';

export default class MoxThemeSwitchComponent extends Component {
@tracked
isChecked = false;

allThemes = ['dark', 'light'];

constructor() {
super(...arguments);
this.isChecked = this.args.isChecked || false;
}

get currentTheme() {
return this.isChecked ? 'dark' : 'light';
}

get otherTheme() {
return this.allThemes.filter(theme => theme !== this.currentTheme)[0];
}

get label() {
if (this.args.label) {
return this.args.label;
}

return `Use ${this.otherTheme} mode`;
}

get fieldId() {
return this.args.id ? this.args.id : dasherize(this.label);
}

@action
switchTheme(newTheme, event) {
this.isChecked = !this.isChecked;
this.args.toggleAction(newTheme, event);
}
}
55 changes: 53 additions & 2 deletions addon/styles/mx-ui-components.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
.mxa-toggle input:checked,
.mox-toggle input:checked {
.mox-toggle input:checked,
.mox-theme-switch input:checked {
background-image: none;
}

.mxa-toggle input:after,
.mox-toggle input:after {
.mox-toggle input:after,
.mox-theme-switch input:after {
content: "";
height: 20px;
width: 20px;
Expand Down Expand Up @@ -38,6 +40,55 @@
left: 2px;
}

.mox-theme-switch input {
height: 1.5rem;
width: 2.5rem;
outline: 2px solid #e5e7eb; /* bg-gray-200 */
background-color: #e5e7eb; /* bg-gray-200 */
outline-offset: 0;
border: none;
}

.mox-theme-switch input:checked {
background-color: #1f2937; /* bg-gray-800 */
outline: 2px solid #1f2937; /* bg-gray-800 */
color: #1f2937; /* bg-gray-800 */
}

.mox-theme-switch input:focus {
outline: 2px solid #06B6D4;
box-shadow: none;
outline-offset: 0;
}

.mox-theme-switch input:after {
height: 18px;
width: 18px;
top: 3px;
left: 3px;
}

.mox-theme-switch-icon {
position: absolute;
left: -4px;
top: 4px;
right: auto;
display: block;
outline: none;
transform: translateX(0);
transition: transform 0.15s;
transition-timing-function: ease-in-out;
}

.mox-theme-switch input:checked:after,
.mox-theme-switch-icon.is-checked {
transform: translateX(15px);
}

.mox-theme-switch input:checked:after {
background-color: transparent;
}

.mxa-toggle input:checked:after,
.mox-toggle input:checked:after {
transform: translateX(20px);
Expand Down
1 change: 1 addition & 0 deletions app/components/mox/theme-switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'mx-ui-components/components/mox/theme-switch';
35 changes: 35 additions & 0 deletions stories/mox-theme-switch.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { hbs } from 'ember-cli-htmlbars';

export default {
title: 'Mox Dark/Mox::ThemeSwitch',
parameters: {
backgrounds: {
default: 'Dark',
},
},
};

const Template = (args) => ({
template: hbs`
<Mox::ThemeSwitch @isChecked={{this.isChecked}}>
<:light-icon>
<Mox::Icon @iconName="sun" @size="small" />
</:light-icon>
<:dark-icon>
<Mox::Icon @iconName="moon" @size="small" />
</:dark-icon>
</Mox::ThemeSwitch>`,
context: args,
});

export const DefaultTheme = Template.bind({});
DefaultTheme.args = {
message: '',
isChecked: false,
};

export const Checked = Template.bind({});
Checked.args = {
message: '',
isChecked: true,
};
9 changes: 9 additions & 0 deletions tests/dummy/public/svg-defs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions tests/integration/components/mox/theme-switch-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { click, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

import sinon from 'sinon';

import { a11yAudit } from 'ember-a11y-testing/test-support';

module('Integration | Component | mox/theme-switch', function (hooks) {
setupRenderingTest(hooks);

hooks.beforeEach(async function() {
this.set('toggleAction', sinon.spy());
this.set('label', 'Paradis');
});

test('it displays a label for screenreader users (set via argument)', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} @label={{this.label}} />`);
assert.dom('[data-test-mox-theme-switch]').hasAttribute('aria-label', 'Paradis');
});

test('it displays a label for screenreader users (fallback)', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} />`);
assert.dom('[data-test-mox-theme-switch]').hasAttribute('aria-label', 'Use dark mode');
});

test('it is accessible with an external label', async function(assert) {
await render(hbs`
<label for="my-field-id">Toggle 1</label>
<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} @id="my-field-id" />
`);
await a11yAudit();
assert.ok(true, 'no a11y detected');
});

test('it is accessible with the default label', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} />`);
await a11yAudit();
assert.ok(true, 'no a11y detected');
});

test('it allows unchecking the component externally', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} @isChecked={{false}} />`);

assert.dom('[data-test-mox-theme-switch-current="dark"]').doesNotExist();
assert.dom('[data-test-mox-theme-switch-current="light"]').exists();
assert.dom('[data-test-mox-theme-switch]').hasAttribute('aria-label', 'Use dark mode');
});

test('it allows checking the component externally', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} @isChecked={{true}} />`);

assert.dom('[data-test-mox-theme-switch-current="light"]').doesNotExist();
assert.dom('[data-test-mox-theme-switch-current="dark"]').exists();
assert.dom('[data-test-mox-theme-switch]').hasAttribute('aria-label', 'Use light mode');
});

test('it triggers the external action when switching the toggle', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} />`);
await click('[data-test-mox-theme-switch]');

assert.true(this.toggleAction.calledOnce);
});

test('it updates the UI when switching the toggle', async function(assert) {
await render(hbs`<Mox::ThemeSwitch @toggleAction={{this.toggleAction}} />`);

assert.dom('[data-test-mox-theme-switch-current="dark"]').doesNotExist();
assert.dom('[data-test-mox-theme-switch-current="light"]').exists();
assert.dom('[data-test-mox-theme-switch]').hasAttribute('aria-label', 'Use dark mode');

await click('[data-test-mox-theme-switch]');

assert.dom('[data-test-mox-theme-switch-current="light"]').doesNotExist();
assert.dom('[data-test-mox-theme-switch-current="dark"]').exists();
assert.dom('[data-test-mox-theme-switch]').hasAttribute('aria-label', 'Use light mode');
});
});

0 comments on commit c4ef6f3

Please sign in to comment.