UX: Introduce <DStatTiles /> component (#30238)

Introduces a new component used to show a grid of stats
on any page, mostly used for dashboards and config pages.
This component yields a hash with a `Tile` component property,
and the caller can loop through their stats and display them
using this component.

Each stat needs a @label and a @value at minimum, but can
also pass in a @tooltip and a @url.
This commit is contained in:
Martin Brennan 2024-12-13 11:32:46 +10:00 committed by GitHub
parent 54f0dd40ad
commit fae6ffcf06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 128 additions and 0 deletions

View File

@ -0,0 +1,33 @@
import { hash } from "@ember/helper";
import { number } from "discourse/lib/formatter";
import DTooltip from "float-kit/components/d-tooltip";
const DStatTile = <template>
<div class="d-stat-tile" role="group">
<div class="d-stat-tile__top">
<span class="d-stat-tile__label">{{@label}}</span>
{{#if @tooltip}}
<DTooltip
class="d-stat-tile__tooltip"
@icon="circle-question"
@content={{@tooltip}}
/>
{{/if}}
</div>
{{#if @url}}
<a href={{@url}} class="d-stat-tile__value" title={{@value}}>
{{number @value}}
</a>
{{else}}
<span class="d-stat-tile__value" title={{@value}}>{{number @value}}</span>
{{/if}}
</div>
</template>;
const DStatTiles = <template>
<div class="d-stat-tiles" ...attributes>
{{yield (hash Tile=DStatTile)}}
</div>
</template>;
export default DStatTiles;

View File

@ -0,0 +1,68 @@
import { render, triggerEvent } from "@ember/test-helpers";
import { module, test } from "qunit";
import DStatTiles from "discourse/components/d-stat-tiles";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { i18n } from "discourse-i18n";
module("Integration | Component | DStatTiles", function (hooks) {
setupRenderingTest(hooks);
test("formats the @value in a readable way with the raw number as the title attr", async function (assert) {
await render(<template>
<DStatTiles as |tiles|>
<tiles.Tile @value="12555999" @label={{i18n "bootstrap_mode"}} />
</DStatTiles>
</template>);
assert
.dom(".d-stat-tiles .d-stat-tile .d-stat-tile__value")
.hasText("12.6M");
assert
.dom(".d-stat-tiles .d-stat-tile .d-stat-tile__value")
.hasAttribute("title", "12555999");
});
test("renders the @label", async function (assert) {
await render(<template>
<DStatTiles as |tiles|>
<tiles.Tile @value="12555999" @label={{i18n "bootstrap_mode"}} />
</DStatTiles>
</template>);
assert
.dom(".d-stat-tiles .d-stat-tile .d-stat-tile__label")
.hasText(i18n("bootstrap_mode"));
});
test("renders the optional @tooltip", async function (assert) {
await render(<template>
<DStatTiles as |tiles|>
<tiles.Tile
@value="12555999"
@label={{i18n "bootstrap_mode"}}
@tooltip={{i18n "bootstrap_mode"}}
/>
</DStatTiles>
</template>);
assert.dom(".d-stat-tile__tooltip").exists();
await triggerEvent(".fk-d-tooltip__trigger", "mousemove");
assert.dom(".fk-d-tooltip__content").hasText(i18n("bootstrap_mode"));
});
test("wraps the value in a link if @url is provided", async function (assert) {
await render(<template>
<DStatTiles as |tiles|>
<tiles.Tile
@value="12555999"
@label={{i18n "bootstrap_mode"}}
@url="https://meta.discourse.org"
/>
</DStatTiles>
</template>);
assert
.dom(".d-stat-tiles .d-stat-tile a.d-stat-tile__value")
.hasAttribute("href", "https://meta.discourse.org");
});
});

View File

@ -1,6 +1,7 @@
@import "badges";
@import "banner";
@import "d-breadcrumbs";
@import "d-stat-tiles";
@import "bookmark-list";
@import "bookmark-modal";
@import "bookmark-menu";

View File

@ -0,0 +1,26 @@
.d-stat-tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1em;
margin-bottom: 2em;
.d-stat-tile {
display: flex;
flex-direction: column;
padding: 1em;
background: var(--primary-very-low);
border-radius: 0.25em;
&__label {
color: var(--primary-medium);
font-size: 0.875em;
margin-bottom: 0.5em;
}
&__value {
color: var(--primary);
font-size: 1.5em;
font-weight: bold;
}
}
}