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/sortable-timeline-mcp-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sanring/cli": minor
---

Add `sortable` for pointer and keyboard reordering. Timeline separators now draw the rail and default marker. MCP adds `get_component_spec` so agents get authoring contracts instead of inventing APIs.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Components are copied directly into your source code — you own them and can mo

## Available Components

accordion · alert · alert-dialog · aspect-ratio · avatar · badge · breadcrumb · button · calendar · card · carousel · checkbox · collapsible · combobox · command · context-menu · date-picker · dialog · divider · dropdown-menu · field · file-upload · hover-card · input · label · link · navigation-menu · otp-input · pagination · popover · progress · radio · resizable · scroll-area · select · sheet · sidebar · skeleton · slider · spinner · stepper · switch · table · tabs · tag · textarea · timeline · toast · toggle · tooltip · transfer · tree
accordion · alert · alert-dialog · aspect-ratio · avatar · badge · breadcrumb · button · calendar · card · carousel · checkbox · collapsible · combobox · command · context-menu · date-picker · dialog · divider · dropdown-menu · field · file-upload · hover-card · input · label · link · navigation-menu · otp-input · pagination · popover · progress · radio · resizable · scroll-area · select · sheet · sidebar · skeleton · slider · sortable · spinner · stepper · switch · table · tabs · tag · textarea · timeline · toast · toggle · tooltip · transfer · tree

---

Expand Down
7 changes: 7 additions & 0 deletions apps/docs/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,13 @@ export const routes: Routes = [
(m) => m.SliderPageComponent,
),
},
{
path: 'sortable',
loadComponent: () =>
import('./pages/components/sortable/sortable-page.component').then(
(m) => m.SortablePageComponent,
),
},
{
path: 'spinner',
loadComponent: () =>
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/app/i18n/locales/en/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const commonTranslations = {
'component.sidebar': 'Sidebar',
'component.skeleton': 'Skeleton',
'component.slider': 'Slider',
'component.sortable': 'Sortable',
'component.stepper': 'Stepper',
'component.switch': 'Switch',
'component.table': 'Table',
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/app/i18n/locales/en/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { sheetTranslations } from './sheet';
import { sidebarTranslations } from './sidebar';
import { skeletonTranslations } from './skeleton';
import { sliderTranslations } from './slider';
import { sortableTranslations } from './sortable';
import { spinnerTranslations } from './spinner';
import { stepperTranslations } from './stepper';
import { switchTranslations } from './switch';
Expand Down Expand Up @@ -91,6 +92,7 @@ export const componentTranslations = {
...sidebarTranslations,
...skeletonTranslations,
...sliderTranslations,
...sortableTranslations,
...spinnerTranslations,
...stepperTranslations,
...switchTranslations,
Expand Down
40 changes: 40 additions & 0 deletions apps/docs/src/app/i18n/locales/en/components/sortable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const sortableTranslations = {
'sortable.description':
'A single-list reorder primitive. Pointer drag uses Angular CDK; keyboard arrows move the focused item. Cards and other content compose through the item directive.',
'sortable.demo.handle': 'Drag handle',
'sortable.demo.horizontal': 'Horizontal',
'sortable.demo.alpha': 'Inbox digest',
'sortable.demo.alphaDescription': 'Summarize unread threads each morning.',
'sortable.demo.beta': 'Release checklist',
'sortable.demo.betaDescription': 'Gate publish on review and smoke tests.',
'sortable.demo.gamma': 'Weekly report',
'sortable.demo.gammaDescription': 'Ship a compact status note on Fridays.',
'sortable.demo.reorder': 'Reorder',
'sortable.installation.description':
'Add the component with the CLI, then import the list, item, and optional handle directives.',
'sortable.usage.description':
'Bind the same array to data and @for. On sorted, replace the array so Angular can reconcile the new order.',
'sortable.composition.description':
'The list owns order. Items are any host element. An optional handle limits pointer dragging to that control.',
'sortable.api.description': 'Inputs, outputs, and directives for a single sortable list.',
'sortable.api.class.description': 'Additional classes merged with the list host.',
'sortable.api.data.description':
'Required array to reorder in place. sorted emits a shallow copy after each move.',
'sortable.api.orientation.description':
'Layout and lock axis for pointer dragging. vertical uses Arrow Up/Down; horizontal uses Arrow Left/Right.',
'sortable.api.disabled.description': 'Disables pointer and keyboard reordering for the whole list.',
'sortable.api.sorted.description': 'Emits a new array after a successful reorder.',
'sortable.api.itemDisabled.description': 'Disables one item without locking the rest of the list.',
'sortable.api.handle.description':
'Optional drag handle. When present, pointer dragging starts from the handle instead of the whole item.',
'sortable.accessibility.description':
'The list uses role=list and items use role=listitem. Disabled items leave the tab order. Prefer a named handle button when the item is a dense card.',
'sortable.keyboard.description': 'Focus an item, then move it with arrows. Tab walks the list.',
'sortable.keyboard.arrowsVertical':
'Moves the focused item up or down in a vertical list.',
'sortable.keyboard.arrowsHorizontal':
'Moves the focused item left or right in a horizontal list.',
'sortable.keyboard.tabShiftTab': 'Moves focus between sortable items in document order.',
'sortable.stateModel.description':
'data is the source of order. Reorder mutates that array and emits sorted with a copy. disabled on the list or an item blocks moves without clearing data.',
} as const;
9 changes: 6 additions & 3 deletions apps/docs/src/app/i18n/locales/en/components/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ export const timelineTranslations = {
'timeline.description':
'Composable timeline primitives for chronological events, activity feeds, and process milestones.',
'timeline.examples.basic.description':
'Use sanringTimeline with native list markup, then compose separators, markers, connectors, and content.',
'A vertical list with a built-in rail. Leave the separator empty for a default marker, or project an icon or avatar.',
'timeline.usage.description':
'Import the timeline directives and apply them to list or div-based activity markup.',
'Each item is a separator plus content. The separator draws the connector; project a node only when the default marker is not enough.',
'timeline.installation.description':
'Install the timeline primitives and compose item, separator, and content directives where each event renders.',
'timeline.demo.horizontal': 'Horizontal',
'timeline.demo.divBased': 'Div-based timeline',
'timeline.demo.reorder': 'Reorder',
'timeline.demo.releaseActivity': 'Release activity',
'timeline.demo.releaseActivityDescription':
'A compact activity trail for release notes and registry updates.',
Expand Down Expand Up @@ -37,6 +38,8 @@ export const timelineTranslations = {
'timeline.demo.qaTitle': 'Quality review',
'timeline.demo.qaMeta': 'QA pass',
'timeline.demo.qaDescription': 'Visual spacing and empty states were reviewed before publishing.',
'timeline.demo.reorder.description':
'Compose with sortable and drag the grip to reorder. Timeline stays layout; sortable owns order. Also install sortable.',
'timeline.api.description': 'Inputs supported by the Timeline directives.',
'timeline.api.orientation.description':
'Controls whether items stack vertically or horizontally.',
Expand All @@ -46,7 +49,7 @@ export const timelineTranslations = {
'Additional classes merged with each separator wrapper.',
'timeline.api.contentClass.description': 'Additional classes merged with each content container.',
'timeline.accessibility.description':
'Timeline is a visual layout primitive with no built-in ARIA role. When timeline items represent an ordered sequence, wrap the content in a native <ol> or <ul> in your template so screen readers understand the list structure.',
'The root sets role=list and each item sets role=listitem so Tailwind Preflight does not strip list semantics. The separator is aria-hidden. Prefer native ul or ol when the sequence is ordered.',
'timeline.keyboard.description': 'Not focusable unless interactive children are present.',
'timeline.stateModel.description':
'Stateless layout component — no value, selection, or event state.',
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/app/i18n/locales/en/pages/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const mcpTranslations = {
'Starts an MCP server over stdio so AI coding agents — Claude Code, Cursor, Windsurf — can query and install Sanring UI components directly, without shelling out.',
'mcp.overview.title': 'Overview',
'mcp.overview.body':
'The sanring mcp command starts an MCP server over stdio. It reads from the same component registry as the sanring CLI and this documentation site, and exposes five tools an AI agent can call to inspect, plan, and install components in your Angular project.',
'The sanring mcp command starts an MCP server over stdio. It reads from the same component registry as the sanring CLI and this documentation site, and exposes tools an AI agent can call to look up authoring contracts, then plan and install components in your Angular project.',
'mcp.tools.title': 'Tools',
'mcp.tools.body':
'All tool handlers validate their input at runtime and return an MCP error result — rather than throwing — when a component name is not found or a required argument is missing.',
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/app/i18n/locales/zh/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const commonTranslations = {
'component.sidebar': 'Sidebar',
'component.skeleton': '骨架屏',
'component.slider': '滑桿',
'component.sortable': '可排序列表',
'component.stepper': '步驟器',
'component.switch': '開關',
'component.table': '表格',
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/app/i18n/locales/zh/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { sheetTranslations } from './sheet';
import { sidebarTranslations } from './sidebar';
import { skeletonTranslations } from './skeleton';
import { sliderTranslations } from './slider';
import { sortableTranslations } from './sortable';
import { spinnerTranslations } from './spinner';
import { stepperTranslations } from './stepper';
import { switchTranslations } from './switch';
Expand Down Expand Up @@ -91,6 +92,7 @@ export const componentTranslations = {
...sidebarTranslations,
...skeletonTranslations,
...sliderTranslations,
...sortableTranslations,
...spinnerTranslations,
...stepperTranslations,
...switchTranslations,
Expand Down
38 changes: 38 additions & 0 deletions apps/docs/src/app/i18n/locales/zh/components/sortable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const sortableTranslations = {
'sortable.description':
'單一列表重排 primitive。指標拖曳走 Angular CDK,鍵盤方向鍵移動目前焦點項目。卡片與其他內容透過 item directive 組合。',
'sortable.demo.handle': '拖曳手柄',
'sortable.demo.horizontal': '水平',
'sortable.demo.alpha': '收件摘要',
'sortable.demo.alphaDescription': '每天早上彙整未讀討論串。',
'sortable.demo.beta': '發布檢查清單',
'sortable.demo.betaDescription': '發布前必須通過審核與煙霧測試。',
'sortable.demo.gamma': '週報',
'sortable.demo.gammaDescription': '每週五送出精簡狀態說明。',
'sortable.demo.reorder': '調整順序',
'sortable.installation.description':
'用 CLI 加入這個元件,再匯入 list、item,以及可選的 handle directive。',
'sortable.usage.description':
'把同一個陣列綁到 data 與 @for。在 sorted 時換成新陣列,讓 Angular 對齊新順序。',
'sortable.composition.description':
'列表負責順序。Item 可以是任何 host 元素。可選 handle 會把指標拖曳限制在該控制項上。',
'sortable.api.description': '單一 sortable 列表的 Inputs、Outputs 與 directives。',
'sortable.api.class.description': '與 list host 合併的額外 class。',
'sortable.api.data.description':
'必填陣列,會就地重排。每次移動後 sorted 會送出淺拷貝。',
'sortable.api.orientation.description':
'版面與指標拖曳的鎖定軸。vertical 用方向鍵上/下;horizontal 用左/右。',
'sortable.api.disabled.description': '停用整份列表的指標與鍵盤重排。',
'sortable.api.sorted.description': '成功重排後送出新陣列。',
'sortable.api.itemDisabled.description': '停用單一項目,不鎖定整份列表。',
'sortable.api.handle.description':
'可選拖曳手柄。有 handle 時,指標拖曳從手柄開始,而不是整個 item。',
'sortable.accessibility.description':
'列表使用 role=list,項目使用 role=listitem。停用的項目會離開 Tab 順序。內容是密集卡片時,建議用有名稱的 handle 按鈕。',
'sortable.keyboard.description': '先聚焦一個項目,再用方向鍵移動。Tab 會依序走過列表。',
'sortable.keyboard.arrowsVertical': '在垂直列表中,把焦點項目往上或往下移。',
'sortable.keyboard.arrowsHorizontal': '在水平列表中,把焦點項目往左或往右移。',
'sortable.keyboard.tabShiftTab': '依文件順序在 sortable 項目之間移動焦點。',
'sortable.stateModel.description':
'data 是順序來源。重排會改這個陣列,並用拷貝觸發 sorted。list 或 item 的 disabled 只阻止移動,不會清空 data。',
} as const;
10 changes: 7 additions & 3 deletions apps/docs/src/app/i18n/locales/zh/components/timeline.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export const timelineTranslations = {
'timeline.description': '可組合的時間軸 primitives,適合時間事件、活動紀錄與流程里程碑。',
'timeline.examples.basic.description':
'使用 sanringTimeline 搭配原生清單標記,再自行組合分隔、節點、連接線與內容。',
'timeline.usage.description': '匯入 Timeline directives,並套用到清單或 div 型活動資料標記。',
'垂直列表,軌道由元件自己畫。Separator 留空就是預設圓點;需要時再投影 icon 或 avatar。',
'timeline.usage.description':
'每個 item 是 separator 加 content。連接線由 separator 負責;只有預設圓點不夠時才投影節點。',
'timeline.installation.description':
'安裝 Timeline primitives,並在每個事件中組合 item、separator 與 content directives。',
'timeline.demo.horizontal': '水平',
'timeline.demo.divBased': 'Div 型時間軸',
'timeline.demo.reorder': '調整順序',
'timeline.demo.releaseActivity': '發布活動',
'timeline.demo.releaseActivityDescription': '適合發布紀錄與 registry 更新的精簡活動軌跡。',
'timeline.demo.today': '今天',
Expand All @@ -31,14 +33,16 @@ export const timelineTranslations = {
'timeline.demo.qaTitle': '品質檢查',
'timeline.demo.qaMeta': 'QA 通過',
'timeline.demo.qaDescription': '發布前已檢查視覺間距與空狀態。',
'timeline.demo.reorder.description':
'跟 sortable 組合,拖右側握把即可改順序。Timeline 只管排版,順序由 sortable 負責。這個範例另外需要安裝 sortable。',
'timeline.api.description': 'Timeline directives 支援的 Inputs。',
'timeline.api.orientation.description': '控制項目垂直堆疊或水平排列。',
'timeline.api.class.description': '與 timeline 根元素合併的額外 class。',
'timeline.api.itemClass.description': '與每個 timeline item 合併的額外 class。',
'timeline.api.separatorClass.description': '與每個 separator wrapper 合併的額外 class。',
'timeline.api.contentClass.description': '與每個 content container 合併的額外 class。',
'timeline.accessibility.description':
'Timeline 是視覺版面 primitive,沒有內建 ARIA 角色。當時間軸項目代表有序序列時,請在模板中用原生 <ol> 或 <ul> 包裹內容,讓螢幕閱讀器理解清單結構。',
'根元素會設 role=list,每個 item 設 role=listitem,避免 Tailwind Preflight 拿掉清單語意。Separator 為 aria-hidden。有序序列請優先用原生 ul 或 ol。',
'timeline.keyboard.description': '除非內部有互動子元素,否則不可聚焦。',
'timeline.stateModel.description': '無狀態版面元件——沒有值、選取或事件狀態。',
} as const;
2 changes: 1 addition & 1 deletion apps/docs/src/app/i18n/locales/zh/pages/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const mcpTranslations = {
'以 stdio transport 啟動 MCP server,讓 Claude Code、Cursor、Windsurf 等 AI coding agent 能直接查詢、安裝 Sanring UI 元件,不用手動下 shell 指令。',
'mcp.overview.title': '概覽',
'mcp.overview.body':
'sanring mcp 指令會以 stdio transport 啟動 MCP server,讀取跟 sanring CLI 及這個文件站相同的元件 registry,並曝露五個 tool,讓 AI agent 能查詢、預覽、安裝你 Angular 專案裡的元件。',
'sanring mcp 指令會以 stdio transport 啟動 MCP server,讀取跟 sanring CLI 及這個文件站相同的元件 registry,並曝露 tools 讓 AI agent 先拿到寫程式用的 API 契約,再預覽、安裝你 Angular 專案裡的元件。',
'mcp.tools.title': '可用的 Tool',
'mcp.tools.body':
'所有 tool 都有 runtime input validation;找不到元件或缺少必要參數時,會回傳 MCP error result,而不是直接丟出例外。',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
template: `
<div class="mt-9 grid gap-4">
<section
class="docs-panel-lg min-w-0 overflow-hidden"
class="docs-panel-lg min-w-0"
role="group"
[attr.aria-label]="i18n.t('component.previewer.preview')"
>
Expand Down
8 changes: 8 additions & 0 deletions apps/docs/src/app/navigation/docs-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type DocsComponentId =
| 'sidebar'
| 'skeleton'
| 'slider'
| 'sortable'
| 'spinner'
| 'stepper'
| 'switch'
Expand Down Expand Up @@ -371,6 +372,13 @@ export const docsComponentItems: DocsComponentNavItem[] = [
path: '/components/slider',
active: true,
},
{
id: 'sortable',
labelKey: 'component.sortable',
descriptionKey: 'sortable.description',
path: '/components/sortable',
active: true,
},
{
id: 'spinner',
labelKey: 'component.spinner',
Expand Down
28 changes: 28 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,34 @@ function isPatch(version: string): boolean {
* - Keep each change to one sentence. Wrap identifiers in backticks.
*/
export const cliVersionChangelog: readonly CliVersionEntry[] = [
{
version: '0.26.0',
date: '2026-09-27',
changes: [
{
type: 'added',
notable: true,
componentIds: ['sortable'],
text: 'Add `sortable` for pointer and keyboard reordering of a single list. Cards and other content compose with `[sanringSortableItem]`.',
},
{
type: 'added',
notable: true,
text: '`sanring mcp` adds `get_component_spec` for selectors, anatomy, API, accessibility, and a canonical example. `get_component_info` stays install metadata; `search_components` also matches aliases such as modal → dialog.',
},
{
type: 'changed',
notable: true,
componentIds: ['timeline'],
text: '`sanringTimelineSeparator` now draws the rail and a default marker, so items no longer need hand-rolled dots, cards, or connector spans.',
},
{
type: 'changed',
componentIds: ['sortable'],
text: '`[sanringSortableItem]` no longer binds a host `class` string, so it can sit on the same node as `[sanringTimelineItem]`.',
},
],
},
{
version: '0.25.2',
date: '2026-09-23',
Expand Down
Loading
Loading