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
5 changes: 5 additions & 0 deletions .changeset/avatar-tab-notification-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sanring/cli": patch
---

Avatar badge accepts `count` (hidden at 0, capped at 99+) and `top`/`bottom` placement. Tabs docs add a Badge composition recipe for unread counts.
Binary file added apps/docs/public/avatars/lantern.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/avatars/night-lantern.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/avatars/path.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/avatars/stairs.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions apps/docs/src/app/i18n/locales/en/components/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export const avatarTranslations = {
'avatar.demo.sizes': 'Sizes',
'avatar.demo.statusBadge': 'Status badge',
'avatar.demo.badgeWithIcon': 'Badge with icon',
'avatar.demo.badgeCount': 'Notification count',
'avatar.examples.badgeCount.description':
'Pass count to render a numeric pill. It defaults to the top-end corner so it can sit with a status dot at the bottom. 0 hides the pill; values above 99 render as 99+.',
'avatar.demo.group': 'Avatar group',
'avatar.demo.groupWithIcon': 'Avatar group with icon',
'avatar.examples.description':
Expand All @@ -24,7 +27,9 @@ export const avatarTranslations = {
'avatar.api.delayMs.description': 'Delay in milliseconds before the fallback is shown.',
'avatar.api.status.description': 'Controls badge color: online, offline, away, busy, or default.',
'avatar.api.placement.description':
'Places the badge at the visual start or end edge, respecting RTL direction.',
'Places the badge at start, end, top, or bottom. Status badges default to end (bottom-end); count badges default to top (top-end). start/end follow RTL.',
'avatar.api.badgeCount.description':
'Unread count on [sanringAvatarBadge]. Hidden at 0 or below; values above 99 render as 99+.',
'avatar.api.overlap.description': 'Stack overlap amount in rem for avatar groups.',
'avatar.api.count.description': 'Number displayed by the avatar group count item.',
'avatar.api.clickable.description':
Expand All @@ -33,7 +38,7 @@ export const avatarTranslations = {
'Makes a clickable group count unavailable and removes it from the tab sequence.',
'avatar.api.clicked.description': 'Emitted when an enabled clickable group count is activated.',
'avatar.accessibility.description':
"role='img' on the host. Provide ariaLabel or ariaLabelledBy to name a non-decorative avatar. For purely decorative use — such as next to a user name already present in text — add aria-hidden='true' on <sanring-avatar> to suppress redundant announcements. A clickable group count exposes button semantics and reflects disabled state.",
"role='img' on the host. Provide ariaLabel or ariaLabelledBy to name a non-decorative avatar. For purely decorative use — such as next to a user name already present in text — add aria-hidden='true' on <sanring-avatar> to suppress redundant announcements. A count badge is role='status'; give it an ariaLabel such as '3 unread'. A clickable group count exposes button semantics and reflects disabled state.",
'avatar.keyboard.description':
'Avatars are not focusable by default. A clickable group count responds to Enter and Space.',
'avatar.stateModel.description':
Expand Down
10 changes: 9 additions & 1 deletion apps/docs/src/app/i18n/locales/en/components/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export const tabsTranslations = {
'tabs.demo.settings': 'Settings',
'tabs.demo.horizontal': 'Horizontal',
'tabs.demo.withIcon': 'With icon',
'tabs.demo.withCount': 'With count',
'tabs.demo.inbox': 'Inbox',
'tabs.demo.drafts': 'Drafts',
'tabs.demo.inboxCountLabel': 'Inbox, 3 unread',
'tabs.demo.inboxContent': 'Unread messages stay in the inbox.',
'tabs.demo.draftsContent': 'Drafts have no unread count, so the badge is omitted.',
'tabs.examples.withCount.description':
'Compose sanringBadge inside a trigger. Put the count in the trigger aria-label. Omit the badge when the count is 0.',
'tabs.demo.vertical': 'Vertical',
'tabs.demo.line': 'Line variant',
'tabs.demo.disabled': 'Disabled tab',
Expand All @@ -37,7 +45,7 @@ export const tabsTranslations = {
'tabs.api.disabled.description':
'Prevents a trigger from being selected or focused by keyboard navigation.',
'tabs.api.valueChange.description': 'Emits when the selected tab value changes.',
'tabs.accessibility.description': "WAI-ARIA Tabs pattern via @angular/aria/tabs. role='tablist' on sanring-tabs-list, role='tab' on each trigger, role='tabpanel' on each content panel, linked with aria-controls and aria-labelledby. aria-selected reflects the active tab.",
'tabs.accessibility.description': "WAI-ARIA Tabs pattern via @angular/aria/tabs. role='tablist' on sanring-tabs-list, role='tab' on each trigger, role='tabpanel' on each content panel, linked with aria-controls and aria-labelledby. aria-selected reflects the active tab. If a trigger shows a count, set aria-label on the trigger so the count is announced (for example 'Inbox, 3 unread').",
'tabs.keyboard.description': 'Arrow navigation within the trigger list; Tab moves into the active panel.',
'tabs.keyboard.arrowLeftRight': 'Navigate between tab triggers (horizontal orientation).',
'tabs.keyboard.arrowUpDown': 'Navigate between tab triggers (vertical orientation).',
Expand Down
10 changes: 8 additions & 2 deletions apps/docs/src/app/i18n/locales/zh/components/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export const avatarTranslations = {
'avatar.demo.sizes': '尺寸',
'avatar.demo.statusBadge': '狀態徽章',
'avatar.demo.badgeWithIcon': '包含圖示的徽章',
'avatar.demo.badgeCount': '通知計數',
'avatar.examples.badgeCount.description':
'傳入 count 會渲染數字 pill。預設在右上角,可與右下角狀態點同時存在。0 會隱藏 pill;超過 99 顯示為 99+。',
'avatar.demo.group': '頭像群組',
'avatar.demo.groupWithIcon': '包含圖示的頭像群組',
'avatar.examples.description': '常見頭像模式,包含 fallback、在線狀態與精簡成員群組。',
Expand All @@ -20,14 +23,17 @@ export const avatarTranslations = {
'avatar.api.ariaLabel.description': '無可見文字時,提供單一頭像或頭像群組的無障礙標籤。',
'avatar.api.delayMs.description': 'fallback 顯示前的延遲毫秒數。',
'avatar.api.status.description': '控制徽章顏色,可使用 online、offline、away、busy 或 default。',
'avatar.api.placement.description': '將徽章放在視覺起點或終點,並尊重 RTL 方向。',
'avatar.api.placement.description':
'將徽章放在 start、end、top 或 bottom。狀態徽章預設 end(右下);計數徽章預設 top(右上)。start/end 會跟隨 RTL。',
'avatar.api.badgeCount.description':
'[sanringAvatarBadge] 的未讀計數。小於等於 0 時隱藏;超過 99 顯示為 99+。',
'avatar.api.overlap.description': '頭像群組的堆疊重疊量,單位為 rem。',
'avatar.api.count.description': '頭像群組數量項目顯示的數字。',
'avatar.api.clickable.description': '讓群組數量項目具備按鈕語意,並可由滑鼠與鍵盤操作。',
'avatar.api.disabled.description': '停用可點擊的群組數量項目,並將它移出 Tab 序列。',
'avatar.api.clicked.description': '啟用中的可點擊群組數量項目被觸發時送出。',
'avatar.accessibility.description':
"宿主具有 role='img'。為非裝飾性的頭像提供 ariaLabel 或 ariaLabelledBy。若頭像純屬裝飾性用途(例如緊鄰已出現在文字中的使用者名稱),請在 <sanring-avatar> 上加 aria-hidden='true' 以避免重複播報。可點擊的群組數量項目會提供 button 語意並反映停用狀態。",
"宿主具有 role='img'。為非裝飾性的頭像提供 ariaLabel 或 ariaLabelledBy。若頭像純屬裝飾性用途(例如緊鄰已出現在文字中的使用者名稱),請在 <sanring-avatar> 上加 aria-hidden='true' 以避免重複播報。計數徽章為 role='status',請給它 ariaLabel,例如「3 則未讀」。可點擊的群組數量項目會提供 button 語意並反映停用狀態。",
'avatar.keyboard.description': '頭像預設不可聚焦;可點擊的群組數量項目支援 Enter 與 Space。',
'avatar.stateModel.description':
'無狀態。src 載入圖片;失敗時顯示 fallback 插槽;縮寫字母作為最後備援。沒有內部選取或值狀態。',
Expand Down
10 changes: 9 additions & 1 deletion apps/docs/src/app/i18n/locales/zh/components/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ export const tabsTranslations = {
'tabs.demo.settings': '設定',
'tabs.demo.horizontal': '水平',
'tabs.demo.withIcon': '包含圖示',
'tabs.demo.withCount': '包含計數',
'tabs.demo.inbox': '收件匣',
'tabs.demo.drafts': '草稿',
'tabs.demo.inboxCountLabel': '收件匣,3 則未讀',
'tabs.demo.inboxContent': '未讀訊息會留在收件匣。',
'tabs.demo.draftsContent': '草稿沒有未讀數,因此不放徽章。',
'tabs.examples.withCount.description':
'在 trigger 內組合 sanringBadge。把數字寫進 trigger 的 aria-label。計數為 0 時不要渲染徽章。',
'tabs.demo.vertical': '垂直',
'tabs.demo.line': '底線變體',
'tabs.demo.disabled': '停用頁籤',
Expand All @@ -33,7 +41,7 @@ export const tabsTranslations = {
'tabs.api.value.description': '用來配對 trigger 與 content panel 的必要值。',
'tabs.api.disabled.description': '避免 trigger 被選取,並從鍵盤導覽中略過。',
'tabs.api.valueChange.description': '選取的 tab value 變更時觸發。',
'tabs.accessibility.description': "透過 @angular/aria/tabs 實作 WAI-ARIA Tabs 模式。sanring-tabs-list 具有 role='tablist',每個觸發器具有 role='tab',每個內容面板具有 role='tabpanel',透過 aria-controls 與 aria-labelledby 互相關聯。aria-selected 反映目前作用中的分頁。",
'tabs.accessibility.description': "透過 @angular/aria/tabs 實作 WAI-ARIA Tabs 模式。sanring-tabs-list 具有 role='tablist',每個觸發器具有 role='tab',每個內容面板具有 role='tabpanel',透過 aria-controls 與 aria-labelledby 互相關聯。aria-selected 反映目前作用中的分頁。若 trigger 顯示計數,請在 trigger 上設 aria-label,讓螢幕閱讀器唸出數字(例如「收件匣,3 則未讀」)。",
'tabs.keyboard.description': '在觸發器清單中以方向鍵導覽;Tab 鍵移入作用中的面板。',
'tabs.keyboard.arrowLeftRight': '在分頁觸發器之間導覽(水平方向)。',
'tabs.keyboard.arrowUpDown': '在分頁觸發器之間導覽(垂直方向)。',
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/app/i18n/translations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { en } from './locales/en/index';
import { zh } from './locales/zh/index';

/** Docs locale catalogs. TranslationKey is keyof typeof en. */
/** Docs locale catalogs. TranslationKey is inferred from the English catalog. */

export const supportedLocales = ['en', 'zh'] as const;

Expand Down
16 changes: 16 additions & 0 deletions apps/docs/src/app/pages/changelog/component-changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ function isPatch(version: string): boolean {
* - Keep each change to one sentence. Wrap identifiers in backticks.
*/
export const cliVersionChangelog: readonly CliVersionEntry[] = [
{
version: '0.25.2',
date: '2026-09-23',
changes: [
{
type: 'added',
componentIds: ['avatar'],
text: '`[sanringAvatarBadge]` accepts `count` (hidden at 0, capped at `99+`) and `placement` `top` / `bottom`. Count defaults to top-end so it can sit with a status dot at the bottom.',
},
{
type: 'changed',
componentIds: ['tabs'],
text: 'Docs add a Badge-inside-trigger recipe for unread counts, including an `aria-label` that names the count.',
},
],
},
{
version: '0.25.1',
date: '2026-09-23',
Expand Down
28 changes: 25 additions & 3 deletions apps/docs/src/app/pages/components/avatar/avatar-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { avatarPage, avatarPageExamples } from './avatar.docs';
<app-component-page-code-previewer [code]="examples.basic" language="angular-html">
<div previewer class="flex items-center justify-center">
<sanring-avatar ariaLabel="Ada Lovelace">
<img sanringAvatarImage src="https://i.pravatar.cc/96?img=5" alt="Ada Lovelace" />
<img sanringAvatarImage src="/avatars/lantern.jpg" alt="Ada Lovelace" />
<sanring-avatar-fallback>AL</sanring-avatar-fallback>
</sanring-avatar>
</div>
Expand Down Expand Up @@ -97,7 +97,7 @@ import { avatarPage, avatarPageExamples } from './avatar.docs';
<app-component-page-code-previewer [code]="examples.badge" language="angular-html">
<div previewer class="flex items-center justify-center gap-4">
<sanring-avatar ariaLabel="Online user">
<img sanringAvatarImage src="https://i.pravatar.cc/96?img=12" alt="Online user" />
<img sanringAvatarImage src="/avatars/stairs.jpg" alt="Online user" />
<sanring-avatar-fallback>OU</sanring-avatar-fallback>
<span sanringAvatarBadge status="online" ariaLabel="Online"></span>
</sanring-avatar>
Expand All @@ -113,6 +113,23 @@ import { avatarPage, avatarPageExamples } from './avatar.docs';
</app-component-page-code-previewer>
</app-component-page-section>

<app-component-page-section [section]="section('example-badge-count')">
<app-component-page-code-previewer [code]="examples.badgeCount" language="angular-html">
<div previewer class="flex items-center justify-center gap-6">
<sanring-avatar ariaLabel="Ada Lovelace">
<img sanringAvatarImage src="/avatars/lantern.jpg" alt="Ada Lovelace" />
<sanring-avatar-fallback>AL</sanring-avatar-fallback>
<span sanringAvatarBadge [count]="3" ariaLabel="3 unread"></span>
<span sanringAvatarBadge status="online" ariaLabel="Online"></span>
</sanring-avatar>
<sanring-avatar ariaLabel="Unread overflow">
<sanring-avatar-fallback>99</sanring-avatar-fallback>
<span sanringAvatarBadge [count]="128" ariaLabel="128 unread"></span>
</sanring-avatar>
</div>
</app-component-page-code-previewer>
</app-component-page-section>

<app-component-page-section [section]="section('example-badge-with-icon')">
<app-component-page-code-previewer
[code]="examples.badgeWithIcon"
Expand All @@ -122,7 +139,7 @@ import { avatarPage, avatarPageExamples } from './avatar.docs';
<sanring-avatar ariaLabel="Verified user">
<img
sanringAvatarImage
src="https://i.pravatar.cc/96?img=21"
src="/avatars/night-lantern.jpg"
alt="Verified user"
/>
<sanring-avatar-fallback>VU</sanring-avatar-fallback>
Expand All @@ -139,12 +156,15 @@ import { avatarPage, avatarPageExamples } from './avatar.docs';
<div previewer class="flex items-center justify-center">
<sanring-avatar-group ariaLabel="Project members">
<sanring-avatar ariaLabel="Ada Lovelace">
<img sanringAvatarImage src="/avatars/lantern.jpg" alt="Ada Lovelace" />
<sanring-avatar-fallback>AL</sanring-avatar-fallback>
</sanring-avatar>
<sanring-avatar ariaLabel="Grace Hopper">
<img sanringAvatarImage src="/avatars/stairs.jpg" alt="Grace Hopper" />
<sanring-avatar-fallback>GH</sanring-avatar-fallback>
</sanring-avatar>
<sanring-avatar ariaLabel="Katherine Johnson">
<img sanringAvatarImage src="/avatars/path.jpg" alt="Katherine Johnson" />
<sanring-avatar-fallback>KJ</sanring-avatar-fallback>
</sanring-avatar>
<sanring-avatar-group-count [count]="3" ariaLabel="3 more members" />
Expand All @@ -161,9 +181,11 @@ import { avatarPage, avatarPageExamples } from './avatar.docs';
<div previewer class="flex items-center justify-center">
<sanring-avatar-group ariaLabel="Project members">
<sanring-avatar ariaLabel="Ada Lovelace">
<img sanringAvatarImage src="/avatars/lantern.jpg" alt="Ada Lovelace" />
<sanring-avatar-fallback>AL</sanring-avatar-fallback>
</sanring-avatar>
<sanring-avatar ariaLabel="Grace Hopper">
<img sanringAvatarImage src="/avatars/night-lantern.jpg" alt="Grace Hopper" />
<sanring-avatar-fallback>GH</sanring-avatar-fallback>
</sanring-avatar>
<sanring-avatar-group-count ariaLabel="Add member">
Expand Down
Loading
Loading