From 8b3929b986ac27dd3042306343bc6e283a1e5d9f Mon Sep 17 00:00:00 2001 From: Guadalupe Camacho Date: Mon, 26 Jan 2026 15:10:15 -0800 Subject: [PATCH 1/2] Start creating badge indicator component --- .../badge-indicator.stories.ts | 41 ++++++++++++++ .../badge-indicator/badge-indicator.styles.ts | 22 ++++++++ .../badge-indicator/badge-indicator.ts | 54 +++++++++++++++++++ .../src/components/indicator/indicator.ts | 4 +- .../components/nav-item/nav-item.stories.ts | 2 +- .../craftcms-cp/src/styles/shared/tokens.css | 1 + 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts create mode 100644 packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts create mode 100644 packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts diff --git a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts new file mode 100644 index 00000000000..059384243ca --- /dev/null +++ b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts @@ -0,0 +1,41 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; + +import {html} from 'lit'; + +import './badge-indicator.js'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories +const meta = { + title: 'Components/Badge Indicator', + component: 'craft-badge-indicator', + argTypes: {}, + render: (args) => html` + + `, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const WithNumber: Story = { + name: 'With Number', + args: { + number: 5, + itemType: 'updates' + }, + render: (args) => html` + + `, +} + +export const WithoutNumber: Story = { + name: 'Without Number', + args: { + number: 5, + itemType: 'updates' + }, + render: (args) => html` + + `, +} diff --git a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts new file mode 100644 index 00000000000..98f14ec2314 --- /dev/null +++ b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts @@ -0,0 +1,22 @@ +import {css} from 'lit'; + +export default css` + .badge-indicator { + display: inline-flex; + width: var(--c-size-icon-xs); + height: var(--c-size-icon-xs); + justify-content: center; + align-items: center; + background-color: var(--c-color-accent-bg-emphasis); + color: white; + border-radius: var(--c-radius-full); + } + + .badge-indicator--with-number { + font-size: var(--c-text-xs); + font-weight: 600; + line-height: 1; + height: calc(28rem / 16); + width: calc(28rem / 16); + } +`; diff --git a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts new file mode 100644 index 00000000000..713f6406265 --- /dev/null +++ b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts @@ -0,0 +1,54 @@ +import {html, css, LitElement} from 'lit'; +import {property} from 'lit/decorators.js'; +import styles from './badge-indicator.styles.js'; +import {classMap} from 'lit/directives/class-map.js'; +import '@shoelace-style/shoelace/dist/components/visually-hidden/visually-hidden.js'; + +/** + * @summary A badge indicator component. Used in various places to indicate that + * something is new or has been updated. The indicator can have an optional + * notification count. + */ + +export default class CraftBadgeIndicator extends LitElement { + static override styles = [styles]; + + /** Number of notifications */ + @property() number: number | null = null; + + /** Type of item being indicated (for screen reader users). Should take singular/plural into account. */ + @property() itemType: string | null = null; + + private truncatedNumber() { + if (!this.number) { + return null; + } + + if (this.number > 99) { + return '99+'; + } else { + return this.number.toString(); + } + } + override render() { + return html` +
+ ${this.truncatedNumber()} + ${this.itemType} +
+ `; + } +} + +if (!customElements.get('craft-badge-indicator')) { + customElements.define('craft-badge-indicator', CraftBadgeIndicator); +} + +declare global { + interface HTMLElementTagNameMap { + 'craft-badge-indicator': CraftBadgeIndicator; + } +} diff --git a/packages/craftcms-cp/src/components/indicator/indicator.ts b/packages/craftcms-cp/src/components/indicator/indicator.ts index 99f9e240c16..8d37645216f 100644 --- a/packages/craftcms-cp/src/components/indicator/indicator.ts +++ b/packages/craftcms-cp/src/components/indicator/indicator.ts @@ -36,7 +36,9 @@ export default class CraftIndicator extends LitElement { 'indicator--warning': this.variant === Variant.Warning, 'indicator--info': this.variant === Variant.Info, })}" - >`; + > + + `; } } diff --git a/packages/craftcms-cp/src/components/nav-item/nav-item.stories.ts b/packages/craftcms-cp/src/components/nav-item/nav-item.stories.ts index 52592b95062..d02801a7724 100644 --- a/packages/craftcms-cp/src/components/nav-item/nav-item.stories.ts +++ b/packages/craftcms-cp/src/components/nav-item/nav-item.stories.ts @@ -3,7 +3,7 @@ import type {Meta, StoryObj} from '@storybook/web-components-vite'; import {html} from 'lit'; import '../icon/icon.js'; -import '../navigation/navigation.js'; +import '../nav-list/nav-list.js'; import '../button/button.js'; import './nav-item.js'; diff --git a/packages/craftcms-cp/src/styles/shared/tokens.css b/packages/craftcms-cp/src/styles/shared/tokens.css index fa624db52f7..d34cbd69a30 100644 --- a/packages/craftcms-cp/src/styles/shared/tokens.css +++ b/packages/craftcms-cp/src/styles/shared/tokens.css @@ -3,6 +3,7 @@ --c-text-lg: calc(16rem / 16); --c-text-base: calc(14rem / 16); --c-text-sm: calc(11rem / 16); + --c-text-xs: calc(9rem / 16); --c-leading-normal: 1.42; From f85d257d673f9e1827ca5fb4f70698f2d9a01a20 Mon Sep 17 00:00:00 2001 From: Guadalupe Camacho Date: Thu, 29 Jan 2026 15:07:21 -0800 Subject: [PATCH 2/2] Change to numbered versus dot for story names, use img semantics for dot notification --- .../badge-indicator.stories.ts | 23 +++++++----- .../badge-indicator/badge-indicator.styles.ts | 8 +++- .../badge-indicator/badge-indicator.ts | 37 ++++++++++++++----- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts index 059384243ca..73dfcba5e84 100644 --- a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts +++ b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.stories.ts @@ -18,24 +18,29 @@ export default meta; type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -export const WithNumber: Story = { - name: 'With Number', +export const DotBadge: Story = { + name: 'Dot Badge', args: { - number: 5, - itemType: 'updates' + srText: 'Has Notifications' + }, + argTypes: { + number: { + control: { type: null } + }, }, render: (args) => html` - + `, } -export const WithoutNumber: Story = { - name: 'Without Number', +export const NumberedBadge: Story = { + name: 'Numbered Badge', args: { number: 5, - itemType: 'updates' + srText: 'updates' }, render: (args) => html` - + `, } + diff --git a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts index 98f14ec2314..7fbbd2ede58 100644 --- a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts +++ b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.styles.ts @@ -13,10 +13,14 @@ export default css` } .badge-indicator--with-number { + padding: 8px; + + } + + .number { font-size: var(--c-text-xs); font-weight: 600; line-height: 1; - height: calc(28rem / 16); - width: calc(28rem / 16); + display: inline-block; } `; diff --git a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts index 713f6406265..4023c56d0a0 100644 --- a/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts +++ b/packages/craftcms-cp/src/components/badge-indicator/badge-indicator.ts @@ -1,4 +1,4 @@ -import {html, css, LitElement} from 'lit'; +import {html, css, LitElement, nothing} from 'lit'; import {property} from 'lit/decorators.js'; import styles from './badge-indicator.styles.js'; import {classMap} from 'lit/directives/class-map.js'; @@ -16,8 +16,16 @@ export default class CraftBadgeIndicator extends LitElement { /** Number of notifications */ @property() number: number | null = null; - /** Type of item being indicated (for screen reader users). Should take singular/plural into account. */ - @property() itemType: string | null = null; + /** Accessible text for screen reader users */ + @property() srText: string | null = null; + + @property() + override id: string; + + constructor() { + super(); + this.id = this.id || Math.floor(Math.random() * 1000000000).toString(); + } private truncatedNumber() { if (!this.number) { @@ -31,13 +39,24 @@ export default class CraftBadgeIndicator extends LitElement { } } override render() { + const hasNumber = this.number !== null; + const badgeId = this.id ?? nothing; + const labelId = badgeId ? `${badgeId}-label` : nothing; + return html` -
- ${this.truncatedNumber()} - ${this.itemType} +
+ ${hasNumber + ? html`${this.truncatedNumber()}` + : nothing} + ${this.srText}
`; }