Improve icons
This commit is contained in:
+20
-1
@@ -2,6 +2,16 @@ import Component from '@glimmer/component';
|
||||
import { htmlSafe } from '@ember/template';
|
||||
import { getIcon, isIconFilled } from '../utils/icons';
|
||||
|
||||
function formatDimension(dim) {
|
||||
if (typeof dim === 'number') {
|
||||
return `${dim}px`;
|
||||
}
|
||||
if (typeof dim === 'string' && /^\d+(\.\d+)?$/.test(dim.trim())) {
|
||||
return `${dim.trim()}px`;
|
||||
}
|
||||
return dim;
|
||||
}
|
||||
|
||||
export default class IconComponent extends Component {
|
||||
get svg() {
|
||||
return getIcon(this.args.name);
|
||||
@@ -11,13 +21,21 @@ export default class IconComponent extends Component {
|
||||
return this.args.size || 16;
|
||||
}
|
||||
|
||||
get width() {
|
||||
return this.args.width !== undefined ? this.args.width : this.size;
|
||||
}
|
||||
|
||||
get height() {
|
||||
return this.args.height !== undefined ? this.args.height : this.size;
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this.args.color || '#898989';
|
||||
}
|
||||
|
||||
get style() {
|
||||
return htmlSafe(
|
||||
`width:${this.size}px;height:${this.size}px;color:${this.color}`
|
||||
`width:${formatDimension(this.width)};height:${formatDimension(this.height)};color:${this.color}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +53,7 @@ export default class IconComponent extends Component {
|
||||
class="icon {{if this.isFilled 'icon-filled'}}"
|
||||
style={{this.style}}
|
||||
title={{this.title}}
|
||||
...attributes
|
||||
>
|
||||
{{htmlSafe this.svg}}
|
||||
</span>
|
||||
|
||||
@@ -1,90 +1,92 @@
|
||||
import Component from '@glimmer/component';
|
||||
import Icon from './icon';
|
||||
import { parsePaymentMethods } from '../utils/payment';
|
||||
import { eq } from 'ember-truth-helpers';
|
||||
import tooltip from '../modifiers/tooltip';
|
||||
|
||||
export default class PlacePaymentMethods extends Component {
|
||||
get payment() {
|
||||
return parsePaymentMethods(this.args.tags);
|
||||
}
|
||||
|
||||
get showCash() {
|
||||
return this.payment.cash !== null;
|
||||
}
|
||||
|
||||
get showCards() {
|
||||
return this.payment.cards !== null;
|
||||
}
|
||||
|
||||
get showBitcoin() {
|
||||
return this.payment.bitcoin.status !== null;
|
||||
}
|
||||
|
||||
get cashTitle() {
|
||||
return this.payment.cash === 'accepted' ? 'Cash accepted' : 'No cash';
|
||||
}
|
||||
|
||||
get cardsTitle() {
|
||||
return this.payment.cards === 'accepted' ? 'Cards accepted' : 'No cards';
|
||||
}
|
||||
|
||||
get bitcoinTitle() {
|
||||
if (this.payment.bitcoin.status === 'denied') {
|
||||
return 'No Bitcoin';
|
||||
get methods() {
|
||||
const list = [];
|
||||
if (this.payment.cash !== null) {
|
||||
const isDenied = this.payment.cash === 'denied';
|
||||
list.push({
|
||||
id: 'cash',
|
||||
isDenied,
|
||||
icon: 'banknote',
|
||||
color: 'currentColor',
|
||||
description: isDenied ? 'No cash' : 'Cash accepted',
|
||||
hasBadge: false,
|
||||
});
|
||||
}
|
||||
if (this.payment.bitcoin.lightning) {
|
||||
return 'Bitcoin (Lightning)';
|
||||
|
||||
if (this.payment.cards !== null) {
|
||||
const isDenied = this.payment.cards === 'denied';
|
||||
list.push({
|
||||
id: 'cards',
|
||||
isDenied,
|
||||
icon: 'payment-card',
|
||||
color: 'currentColor',
|
||||
description: isDenied ? 'No cards' : 'Cards accepted',
|
||||
hasBadge: false,
|
||||
});
|
||||
}
|
||||
return 'Bitcoin (On-chain)';
|
||||
|
||||
if (this.payment.bitcoin.status !== null) {
|
||||
const isDenied = this.payment.bitcoin.status === 'denied';
|
||||
let description = 'No Bitcoin';
|
||||
if (!isDenied) {
|
||||
description = this.payment.bitcoin.lightning
|
||||
? 'Bitcoin (Lightning)'
|
||||
: 'Bitcoin (On-chain)';
|
||||
}
|
||||
list.push({
|
||||
id: 'bitcoin',
|
||||
isDenied,
|
||||
icon: 'bitcoin',
|
||||
width: 17,
|
||||
height: 22,
|
||||
color: 'currentColor',
|
||||
description,
|
||||
hasBadge: !isDenied && this.payment.bitcoin.lightning,
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{! template-lint-disable no-unsupported-role-attributes }}
|
||||
{{#if this.payment.hasPaymentInfo}}
|
||||
<div class="place-payment-methods" aria-label="Payment methods">
|
||||
{{#if this.showCash}}
|
||||
{{#each this.methods as |method|}}
|
||||
<span
|
||||
class="payment-method
|
||||
{{if (eq this.payment.cash 'denied') 'is-denied'}}"
|
||||
data-test-payment-method="cash"
|
||||
title={{this.cashTitle}}
|
||||
aria-label={{this.cashTitle}}
|
||||
class="payment-method {{if method.isDenied 'is-denied'}}"
|
||||
data-test-payment-method={{method.id}}
|
||||
aria-label={{method.description}}
|
||||
aria-description={{method.description}}
|
||||
tabindex="0"
|
||||
{{tooltip}}
|
||||
>
|
||||
<Icon @name="banknote" @size={{22}} @color="currentColor" />
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showCards}}
|
||||
<span
|
||||
class="payment-method
|
||||
{{if (eq this.payment.cards 'denied') 'is-denied'}}"
|
||||
data-test-payment-method="cards"
|
||||
title={{this.cardsTitle}}
|
||||
aria-label={{this.cardsTitle}}
|
||||
>
|
||||
<Icon @name="payment-card" @size={{22}} @color="currentColor" />
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showBitcoin}}
|
||||
<span
|
||||
class="payment-method
|
||||
{{if (eq this.payment.bitcoin.status 'denied') 'is-denied'}}"
|
||||
data-test-payment-method="bitcoin"
|
||||
title={{this.bitcoinTitle}}
|
||||
aria-label={{this.bitcoinTitle}}
|
||||
>
|
||||
<Icon @name="bitcoin" @size={{22}} @color="currentColor" />
|
||||
{{#if this.payment.bitcoin.lightning}}
|
||||
<Icon
|
||||
@name={{method.icon}}
|
||||
@size={{22}}
|
||||
@width={{method.width}}
|
||||
@height={{method.height}}
|
||||
@color={{method.color}}
|
||||
/>
|
||||
{{#if method.hasBadge}}
|
||||
<span
|
||||
class="payment-method-badge"
|
||||
data-test-payment-badge="lightning"
|
||||
title="Lightning"
|
||||
>
|
||||
<Icon @name="zap" @size={{10}} @color="#f59e0b" />
|
||||
<Icon @name="zap" @size={{13}} @color="#fff" @filled={{true}} />
|
||||
</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M23.638 14.904c-1.602 6.43-8.09 10.34-14.52 8.736C2.686 22.04-1.224 15.55.38 9.12 1.984 2.69 8.472-1.22 14.902.384c6.432 1.604 10.34 8.09 8.736 14.52zm-6.273-5.247c.273-1.823-.974-2.805-2.633-3.46l.538-2.158-1.313-.327-.524 2.1c-.345-.086-.7-.168-1.055-.248l.528-2.116-1.313-.328-.538 2.158c-.286-.065-.566-.13-.837-.198l.002-.008-1.812-.452-.35 1.403s.974.223.953.237c.531.133.627.485.611.764l-.612 2.453c.037.01.084.023.136.045l-.14-.035-.856 3.435c-.065.161-.23.403-.601.31.014.02-.953-.238-.953-.238l-.652 1.503 1.71.426c.318.08.63.162.937.24l-.544 2.18 1.312.328.538-2.156c.36.097.708.188 1.05.274l-.535 2.146 1.313.328.544-2.18c2.24.424 3.924.253 4.632-1.773.57-1.63-.028-2.57-1.206-3.18.857-.198 1.503-.762 1.676-1.93zm-3 4.205c-.406 1.63-3.15.75-4.04.528l.721-2.89c.89.222 3.743.662 3.32 2.362zm.407-4.237c-.37 1.484-2.658.73-3.4.545l.654-2.62c.741.185 3.13.53 2.746 2.075z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="8.157 6 15.088 19.949">
|
||||
<path d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 964 B After Width: | Height: | Size: 872 B |
+25
-11
@@ -1190,8 +1190,9 @@ abbr[title] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
outline-offset: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
@@ -1203,27 +1204,40 @@ abbr[title] {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
left: 50%;
|
||||
width: 28px;
|
||||
height: 2px;
|
||||
background-color: var(--danger-color);
|
||||
transform: rotate(-45deg);
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
pointer-events: none;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.payment-method-badge {
|
||||
position: absolute;
|
||||
bottom: -3px;
|
||||
right: -5px;
|
||||
display: inline-flex;
|
||||
bottom: -2px;
|
||||
right: -6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--primary-background-color);
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 15%);
|
||||
background: var(--default-list-color);
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.payment-method-badge .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.payment-method-badge .icon svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'marco/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import Icon from 'marco/components/icon';
|
||||
|
||||
module('Integration | Component | icon', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders default 16px square dimensions', async function (assert) {
|
||||
await render(<template><Icon @name="zap" /></template>);
|
||||
|
||||
assert.dom('.icon').exists();
|
||||
assert.dom('.icon').hasAttribute('style', /width:16px;height:16px;/);
|
||||
});
|
||||
|
||||
test('it renders custom square size with @size', async function (assert) {
|
||||
await render(<template><Icon @name="zap" @size={{24}} /></template>);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:24px;height:24px;/);
|
||||
});
|
||||
|
||||
test('it supports custom @width and @height', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="bitcoin" @width={{17}} @height={{22}} /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:17px;height:22px;/);
|
||||
});
|
||||
|
||||
test('it allows @width to override while @height falls back to @size', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="bitcoin" @width={{17}} @size={{22}} /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:17px;height:22px;/);
|
||||
});
|
||||
|
||||
test('it allows @height to override while @width falls back to @size', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="bitcoin" @height={{22}} @size={{18}} /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:18px;height:22px;/);
|
||||
});
|
||||
|
||||
test('it supports explicit string units for width and height', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="zap" @width="100%" @height="2rem" /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:100%;height:2rem;/);
|
||||
});
|
||||
|
||||
test('it splats HTML attributes to the icon element', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="zap" data-test-custom-icon="true" /></template>
|
||||
);
|
||||
|
||||
assert.dom('[data-test-custom-icon="true"]').exists();
|
||||
});
|
||||
});
|
||||
@@ -432,6 +432,15 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
assert.dom('[data-test-payment-method="cards"]').hasClass('is-denied');
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').exists();
|
||||
assert.dom('[data-test-payment-badge="lightning"]').exists();
|
||||
|
||||
const methodNames = Array.from(
|
||||
this.element.querySelectorAll('[data-test-payment-method]')
|
||||
).map((el) => el.getAttribute('data-test-payment-method'));
|
||||
assert.deepEqual(
|
||||
methodNames,
|
||||
['cash', 'cards', 'bitcoin'],
|
||||
'renders in stable order: cash, cards, bitcoin'
|
||||
);
|
||||
});
|
||||
|
||||
test('it does not render payment methods when no payment tags are present on place', async function (assert) {
|
||||
|
||||
@@ -17,7 +17,7 @@ module('Integration | Component | place-payment-methods', function (hooks) {
|
||||
assert.dom('.place-payment-methods').doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders cash and card icons when accepted', async function (assert) {
|
||||
test('it renders cash and card icons when accepted with aria-description and no title attribute', async function (assert) {
|
||||
const tags = {
|
||||
'payment:cash': 'yes',
|
||||
'payment:cards': 'yes',
|
||||
@@ -30,10 +30,28 @@ module('Integration | Component | place-payment-methods', function (hooks) {
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.hasAttribute('aria-description', 'Cash accepted');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"] > .icon')
|
||||
.hasAttribute('style', /width:22px;height:22px;color:currentColor/);
|
||||
assert.dom('[data-test-payment-method="cards"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.hasAttribute('aria-description', 'Cards accepted');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"] > .icon')
|
||||
.hasAttribute('style', /width:22px;height:22px;color:currentColor/);
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').doesNotExist();
|
||||
});
|
||||
|
||||
@@ -95,7 +113,28 @@ module('Integration | Component | place-payment-methods', function (hooks) {
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.hasAttribute('aria-description', 'Bitcoin (Lightning)');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"] > .icon')
|
||||
.hasAttribute('style', /width:17px;height:22px;color:currentColor/);
|
||||
assert.dom('[data-test-payment-badge="lightning"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"] .icon')
|
||||
.hasClass('icon-filled');
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"] .icon')
|
||||
.hasAttribute('style', /width:13px/);
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"] .icon')
|
||||
.hasAttribute('style', /color:#fff/);
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
});
|
||||
|
||||
test('it renders bitcoin with strike-through when explicitly denied', async function (assert) {
|
||||
@@ -107,5 +146,32 @@ module('Integration | Component | place-payment-methods', function (hooks) {
|
||||
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').exists();
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').hasClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.hasAttribute('aria-description', 'No Bitcoin');
|
||||
});
|
||||
|
||||
test('it preserves stable order (cash, cards, bitcoin) regardless of whether denied or accepted', async function (assert) {
|
||||
const tags = {
|
||||
'payment:cash': 'no',
|
||||
'payment:cards': 'yes',
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
const methods = Array.from(
|
||||
this.element.querySelectorAll('[data-test-payment-method]')
|
||||
).map((el) => el.getAttribute('data-test-payment-method'));
|
||||
|
||||
assert.deepEqual(
|
||||
methods,
|
||||
['cash', 'cards', 'bitcoin'],
|
||||
'icons stay in fixed positions: cash, cards, bitcoin'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user