@teamblind-chorus/ui 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/agents/AGENTS.md +4 -6
  2. package/agents/DESIGN.md +2 -0
  3. package/agents/LOVABLE.md +167 -373
  4. package/agents/anti-patterns.md +2 -2
  5. package/agents/catalog.md +12 -6
  6. package/agents/components/avatar-rail/avatar-rail.md +2 -0
  7. package/agents/components/avatar-rail/avatar-rail.spec.json +19 -0
  8. package/agents/components/badge/badge.md +2 -0
  9. package/agents/components/badge/role.md +2 -0
  10. package/agents/components/badge/update.md +2 -0
  11. package/agents/components/banner/banner.family.json +3 -1
  12. package/agents/components/banner/banner.md +125 -9
  13. package/agents/components/banner/banner.spec.json +64 -3
  14. package/agents/components/bottom-sheet/bottom-sheet.md +2 -0
  15. package/agents/components/bubble/bubble.md +2 -0
  16. package/agents/components/button/button.family.json +8 -2
  17. package/agents/components/button/button.md +2 -0
  18. package/agents/components/button/check.md +2 -0
  19. package/agents/components/button/check.spec.json +19 -0
  20. package/agents/components/button/fab.md +2 -0
  21. package/agents/components/button/fab.spec.json +19 -0
  22. package/agents/components/button/group.spec.json +65 -0
  23. package/agents/components/button/icon.md +2 -0
  24. package/agents/components/button/icon.spec.json +19 -0
  25. package/agents/components/button/standard.md +45 -19
  26. package/agents/components/button/standard.spec.json +19 -0
  27. package/agents/components/button/text.md +2 -0
  28. package/agents/components/button/text.spec.json +19 -0
  29. package/agents/components/button/toggle.md +2 -0
  30. package/agents/components/button/toggle.spec.json +19 -0
  31. package/agents/components/button/toolbar.md +2 -0
  32. package/agents/components/carousel/carousel.md +2 -0
  33. package/agents/components/carousel/post.md +5 -3
  34. package/agents/components/carousel/post.spec.json +4 -6
  35. package/agents/components/carousel/profile.md +4 -2
  36. package/agents/components/carousel/profile.spec.json +4 -6
  37. package/agents/components/chip/chip.md +2 -0
  38. package/agents/components/chip/filter.md +2 -0
  39. package/agents/components/chip/filter.spec.json +19 -0
  40. package/agents/components/chip/tag.md +2 -0
  41. package/agents/components/chip/tag.spec.json +19 -0
  42. package/agents/components/dialog/dialog.md +2 -0
  43. package/agents/components/directory-list/directory-list.md +2 -0
  44. package/agents/components/divider/divider.md +2 -0
  45. package/agents/components/empty-state/empty-state.family.json +28 -0
  46. package/agents/components/empty-state/empty-state.md +69 -0
  47. package/agents/components/empty-state/empty-state.spec.json +87 -0
  48. package/agents/components/feed/ad.md +2 -0
  49. package/agents/components/feed/feed.md +2 -0
  50. package/agents/components/feed/post.md +2 -0
  51. package/agents/components/form-field/form-field.md +3 -1
  52. package/agents/components/form-field/input.md +2 -0
  53. package/agents/components/form-field/input.spec.json +10 -2
  54. package/agents/components/form-field/search.md +2 -0
  55. package/agents/components/form-field/search.spec.json +10 -2
  56. package/agents/components/form-field/select.md +2 -0
  57. package/agents/components/form-field/select.spec.json +9 -1
  58. package/agents/components/form-field/textarea.md +2 -0
  59. package/agents/components/form-field/textarea.spec.json +10 -2
  60. package/agents/components/header/header.md +2 -0
  61. package/agents/components/header/main.md +2 -0
  62. package/agents/components/header/sub.md +2 -0
  63. package/agents/components/list/accordion.md +2 -0
  64. package/agents/components/list/accordion.spec.json +9 -0
  65. package/agents/components/list/entry.md +2 -0
  66. package/agents/components/list/entry.spec.json +21 -1
  67. package/agents/components/list/list.md +3 -1
  68. package/agents/components/list/radio.md +2 -0
  69. package/agents/components/list/radio.spec.json +19 -0
  70. package/agents/components/list/standard.md +48 -0
  71. package/agents/components/list/standard.spec.json +39 -3
  72. package/agents/components/metadata/compact.md +13 -7
  73. package/agents/components/metadata/compact.spec.json +19 -6
  74. package/agents/components/metadata/metadata.family.json +3 -3
  75. package/agents/components/metadata/metadata.md +4 -2
  76. package/agents/components/metadata/standard.md +24 -0
  77. package/agents/components/nav-card/nav-card.md +2 -0
  78. package/agents/components/nav-card/nav-card.spec.json +9 -0
  79. package/agents/components/nav-list/nav-list.md +2 -0
  80. package/agents/components/navigation-bar/main.md +2 -0
  81. package/agents/components/navigation-bar/navigation-bar.md +2 -0
  82. package/agents/components/navigation-bar/search.md +2 -0
  83. package/agents/components/navigation-bar/sub.md +2 -0
  84. package/agents/components/page-shell/page-shell.family.json +1 -1
  85. package/agents/components/page-shell/page-shell.md +35 -0
  86. package/agents/components/page-shell/page-shell.spec.json +85 -0
  87. package/agents/components/pagination/pagination.family.json +26 -0
  88. package/agents/components/pagination/pagination.md +40 -0
  89. package/agents/components/pagination/pagination.spec.json +54 -0
  90. package/agents/components/profile-header/profile-header.md +2 -0
  91. package/agents/components/progress/progress.md +2 -0
  92. package/agents/components/side-sheet/side-sheet.md +2 -0
  93. package/agents/components/skeleton/skeleton.md +2 -0
  94. package/agents/components/spinner/spinner.family.json +27 -0
  95. package/agents/components/spinner/spinner.md +98 -0
  96. package/agents/components/spinner/spinner.spec.json +82 -0
  97. package/agents/components/status-tag/status-tag.md +2 -0
  98. package/agents/components/suggestion-list/suggestion-list.md +2 -0
  99. package/agents/components/switch/switch.md +2 -0
  100. package/agents/components/switch/switch.spec.json +9 -0
  101. package/agents/components/tab-bar/tab-bar.md +2 -0
  102. package/agents/components/tab-bar/tab-bar.spec.json +16 -0
  103. package/agents/components/tabs/rounded.md +2 -0
  104. package/agents/components/tabs/rounded.spec.json +19 -0
  105. package/agents/components/tabs/segmented.md +2 -0
  106. package/agents/components/tabs/tabs.md +2 -0
  107. package/agents/components/tabs/underline.md +2 -0
  108. package/agents/components/tabs/underline.spec.json +19 -0
  109. package/agents/components/thumbnail/thumbnail.md +2 -0
  110. package/agents/components/toast/toast.md +2 -0
  111. package/agents/components/tooltip/tooltip.md +2 -0
  112. package/agents/compose.md +3 -3
  113. package/agents/manifest.json +9 -6
  114. package/agents/patterns/README.md +2 -0
  115. package/agents/patterns/actions.md +2 -0
  116. package/agents/patterns/browsing.md +2 -0
  117. package/agents/patterns/communications.md +2 -0
  118. package/agents/patterns/layout.md +2 -0
  119. package/agents/patterns/modals.md +2 -0
  120. package/agents/patterns/visual.md +2 -0
  121. package/agents/usage.json +27 -3
  122. package/dist/index.cjs +433 -97
  123. package/dist/index.cjs.map +1 -1
  124. package/dist/index.d.cts +74 -3
  125. package/dist/index.d.ts +74 -3
  126. package/dist/index.js +430 -98
  127. package/dist/index.js.map +1 -1
  128. package/dist/styles.css +365 -41
  129. package/package.json +1 -2
  130. package/agents/reconstruct.md +0 -55
  131. package/agents/scoped-adoption.md +0 -111
package/dist/index.d.cts CHANGED
@@ -32,12 +32,22 @@ export type BadgeProps =
32
32
  // ── Banner (banner/banner) ──
33
33
  interface BannerPropsOwn {
34
34
  appearance?: "default" | "accent" | "destructive";
35
+ /** Paints a `sys.borderWidth.hairline` (1) inset stroke around the container, toned to the appearance's color family and kept deliberately faint so it reads as a soft edge of the same tint, not a frame — the subtle gray hairline (`sys.color.outlineVariant`) on `default`'s gray-tinted fill, `primary` at 40% (`color-mix(sys.color.primary, 40%)`) on `accent`'s blue-tinted fill, `error` at 40% on `destructive`. Rendered as an inset box-shadow, never a real border, so toggling it cannot change the banner's footprint (see DESIGN.md → Border & Stroke). Reach for it when the tinted fill alone doesn't separate the banner from its host surface. */
36
+ outlined?: boolean;
37
+ /** On `accent`, paints the title + body in the neutral default foreground (`sys.color.onSurface`) and steps the action to `sys.color.primary` — i.e. the **Default appearance's** foreground treatment laid over the accent fill, decoupling the background tone from the text tone. Reach for it when the `primaryContainer` tint should still pull the eye but the copy should read as quiet, high-legibility body text rather than tonal `onPrimaryContainer` primary-family text (long-form explainers, dense asides). No effect on `default` (already `onSurface`) or `destructive` (the warning tone must carry through the copy). */
38
+ neutralBody?: boolean;
39
+ /** Optional heading line above the body. label.md (14 / Semibold 600) in the container's foreground, separated from the body by `sys.layout.stack.2xs` (4). Reach for it when the aside needs a scannable lead-in; omit for single-thought asides where the body carries itself. */
40
+ title?: React.ReactNode;
35
41
  /** A 16 × 16 (`sys.icon.md`) glyph at the container's leading edge. Inherits the banner's foreground (`currentColor`) so the mark reads as part of the body copy. The slot occupies the body.sm line-box height so the glyph centers on the **first line** of the body — multi-line bodies keep the icon anchored to the first-line cap, not the block center. Ignored when `thumbnail` is also passed. */
36
42
  icon?: React.ReactNode;
43
+ /** A 16 × 16 (`sys.icon.md`) glyph at the container's trailing edge, vertically centered against the whole block (`align-self: center`). Paints in `currentColor`. Reach for it when the banner leads somewhere — a forward affordance such as `ForwardCircleFillIcon` signaling the whole aside opens a destination. */
44
+ trailingIcon?: React.ReactNode;
37
45
  /** A leading visual rendered by [Thumbnail](../thumbnail/thumbnail.md) — used when the aside is anchored to a channel, author, or sub-brand image rather than to a glyph. Takes precedence over `icon`. */
38
46
  thumbnail?: React.ReactNode;
39
47
  /** { label, href? , onClick? } — a follow-through link rendered as a block child below the body. */
40
48
  action?: Record<string, unknown>;
49
+ /** A [Text Button](../button/text.md) (`<Button variant="text">`) rendered at the container's trailing edge, vertically centered against the whole block (`align-self: center`). Distinct from `action` (a follow-through link below the body): `trailingAction` is a compact inline commit that sits beside the copy — Dismiss, Enable, Undo. The button keeps full control of its own `size` and `appearance` per the button/text spec; **by default pick the appearance whose color family matches the banner fill** so the commit reads as part of the tinted block — `accent` banner → `appearance="accent"`, `default` banner → `appearance="default"`, `destructive` banner → the Text Button `destructive` flavor. Override only when a denser rung (`size="small"` / `"xsmall"`) or a different emphasis is deliberately wanted. The button also keeps its own `leadingIcon` / `trailingIcon` slots, so the commit can carry an in-button glyph (e.g. a trailing `ChevronRightIcon` on an *Enable* / *Continue* commit). Takes precedence over the banner-level `trailingIcon` when both are passed. */
50
+ trailingAction?: React.ReactNode;
41
51
  /** Body text — the explanation copy. */
42
52
  children: React.ReactNode;
43
53
  }
@@ -152,6 +162,14 @@ export type ButtonProps =
152
162
  | ButtonToggleProps
153
163
  | ButtonToolbarProps;
154
164
 
165
+ // ── ButtonGroup (button/group) ──
166
+ interface ButtonGroupPropsOwn {
167
+ variant?: "inline" | "docked";
168
+ orientation?: "horizontal" | "vertical";
169
+ label?: React.ReactNode;
170
+ }
171
+ export interface ButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ButtonGroupPropsOwn>, ButtonGroupPropsOwn {}
172
+
155
173
  // ── Fab (button/fab) ──
156
174
  interface FabPropsOwn {
157
175
  variant?: "fab";
@@ -249,6 +267,19 @@ interface DirectoryListPropsOwn {
249
267
  }
250
268
  export interface DirectoryListProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof DirectoryListPropsOwn>, DirectoryListPropsOwn {}
251
269
 
270
+ // ── EmptyState (empty-state/empty-state) ──
271
+ interface EmptyStatePropsOwn {
272
+ /** Optional leading glyph or illustration, centered above the headline. Sized to a `ref.space.600` (48) box — larger than `sys.icon.lg` (24), realizing DESIGN.md's `icon.xl` or larger intent (no `icon.xl` icon-size rung exists; the icon scale stops at `lg`). Painted in `sys.color.onSurfaceVariant` via `currentColor` so it reads as quiet, monochrome chrome — illustrations stay monochrome unless they carry deliberate brand-moment intent. Separated from the headline by `sys.layout.stack.sm` (12). */
273
+ illustration?: React.ReactNode;
274
+ /** The required lead line. `sys.typo.heading.sm` in `sys.color.onSurface`. Names what the surface is for / why it is empty in one short line (e.g. 'No posts yet'). */
275
+ headline: React.ReactNode;
276
+ /** Optional supporting line below the headline. `sys.typo.body.sm` in `sys.color.onSurfaceVariant`, separated from the headline by `sys.layout.stack.2xs` (4). One sentence — the second of the three lines (e.g. 'Conversations you start or join will appear here'). */
277
+ body?: React.ReactNode;
278
+ /** { label, href?, onClick? } — the primary CTA. Renders a default-size primary `Button` (the surface's primary action — the one thing that fills the empty surface). Placed below the body with a `sys.layout.stack.md` (16) gap. There is NO `cta` slot to fill with a custom button; pass the action object so the primary Button is composed for you. */
279
+ action?: Record<string, unknown>;
280
+ }
281
+ export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof EmptyStatePropsOwn>, EmptyStatePropsOwn {}
282
+
252
283
  // ── Feed (feed/post) ──
253
284
  interface FeedPropsOwn {
254
285
  /** Editorial label — 'HOT', 'NEW', 'PINNED'. Opt-in; most posts render without one. */
@@ -491,9 +522,11 @@ export interface MetadataStandardProps extends Omit<React.HTMLAttributes<HTMLDiv
491
522
 
492
523
  interface MetadataCompactPropsOwn {
493
524
  variant: "compact";
525
+ /** Leading [Thumbnail](../thumbnail/thumbnail.md) at `size={32}` — same prop and rung as the [Standard](standard.md) head. Forwards every Thumbnail prop verbatim (`{ src, alt }`). When omitted, the Thumbnail renders its image-area fallback over `surfaceContainerHigh`. */
526
+ avatar?: Record<string, unknown>;
494
527
  /** Array of independently-linked identity items — canonical fill is `[company name, nickname]`, the nickname canonically last, displayed bare (no @ prefix). Each entry is either a string (renders as a stub-href link) or a `{ label, href, badge }` object — `badge` is an optional SINGLE presentational mark node rendered AFTER the item's link, outside the <a> (canonical fill: Badge variant="role" on the trailing nickname item). Items separate by middot. Same grammar as the standard sub's `meta`; here it is the whole cluster, so it is required. */
495
528
  meta: React.ReactNode;
496
- /** Posting time at the line's trailing edge — plain text (never a link), preceded by a middot, in `label.sm` / `sys.color.outline` so it recedes behind the identity links. Required: the timestamp is what distinguishes a compact attribution from a bare identity row. */
529
+ /** Posting time at the line's trailing edge — plain text (never a link), in `label.sm` / `sys.color.outline` so it recedes behind the identity links. NOT preceded by a middot: the time is separated from the identity cluster by an `inline.md` (8) gap so it reads as a distinct trailing element rather than another identity item (mirrors the Standard head's name↔time treatment). Required: the timestamp is what distinguishes a compact attribution from a bare identity row. */
497
530
  timestamp: string;
498
531
  }
499
532
  export interface MetadataCompactProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof MetadataCompactPropsOwn>, MetadataCompactPropsOwn {}
@@ -594,6 +627,30 @@ export type NavigationBarProps =
594
627
  | NavigationBarSubProps
595
628
  | NavigationBarSearchProps;
596
629
 
630
+ // ── PageShell (page-shell/page-shell) ──
631
+ interface PageShellPropsOwn {
632
+ /** Rendered in flow at the top — a `NavigationBar`. Pays its own `safe-area-inset-top`; the shell does NOT re-pay it. Pinned by the flex column, never by `position: sticky` / `fixed`. */
633
+ nav?: React.ReactNode;
634
+ /** Rendered in flow at the bottom — a `TabBar`. Pays its own `safe-area-inset-bottom`; the shell does NOT re-pay it. Pinned by the flex column, never by `position: sticky` / `fixed`. */
635
+ tabBar?: React.ReactNode;
636
+ /** The scrolling body content — the sole scroll region. Rendered inside `<main class="chorus-page-shell__body">`. */
637
+ children: React.ReactNode;
638
+ /** Spread onto `<main>` — use to add a page gutter (`style={{ paddingInline: 'var(--sys-layout-page-md)' }}`) when the screen carries inline (non-full-bleed) content. `className` composes with `chorus-page-shell__body`; the rest spread as-is. Do NOT use it to re-pay a bar's safe-area inset. */
639
+ bodyProps?: Record<string, unknown>;
640
+ /** Composes with the shell root's own `chorus-page-shell` class. Use for placement only; never to override the pin/scroll mechanics. */
641
+ className?: string;
642
+ }
643
+ export interface PageShellProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof PageShellPropsOwn>, PageShellPropsOwn {}
644
+
645
+ // ── Pagination (pagination/pagination) ──
646
+ interface PaginationPropsOwn {
647
+ /** Total number of pages — one dot renders per page. Below 2 the component renders nothing. */
648
+ count?: number;
649
+ /** Zero-based index of the active page. Clamped to `0..count-1`. Owned by the host pager (e.g. updated from an IntersectionObserver on its scroll-snap targets). */
650
+ activeIndex?: number;
651
+ }
652
+ export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof PaginationPropsOwn>, PaginationPropsOwn {}
653
+
597
654
  // ── ProfileHeader (profile-header/profile-header) ──
598
655
  interface ProfileHeaderPropsOwn {
599
656
  /** Entity name — channel topic, person, or company. Renders as the page-level `<h1>` at `sys.typo.heading.lg` / Semibold / `onSurface`. Single line; truncates with ellipsis at narrow widths. */
@@ -688,6 +745,17 @@ interface SkeletonPropsOwn {
688
745
  }
689
746
  export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SkeletonPropsOwn>, SkeletonPropsOwn {}
690
747
 
748
+ // ── Spinner (spinner/spinner) ──
749
+ interface SpinnerPropsOwn {
750
+ /** Selects the arc diameter off the `icon.*` ladder. `medium` paints at `sys.icon.lg` (24px); `small` at `sys.icon.md` (16px). */
751
+ size?: "medium" | "small";
752
+ /** Optional loading copy rendered beside the arc in `sys.typo.body.sm` / `sys.color.onSurfaceVariant`. When present it also supplies the accessible name, so `aria-label` is not required. */
753
+ label?: React.ReactNode;
754
+ /** Accessible label announced by screen readers. Defaults to `'Loading'`. Supply a more specific name (e.g. `'Signing in'`) when the wait scope is meaningful. Redundant when a visible `label` is passed. */
755
+ "aria-label"?: string;
756
+ }
757
+ export interface SpinnerProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SpinnerPropsOwn>, SpinnerPropsOwn {}
758
+
691
759
  // ── StatusTag (status-tag/status-tag) ──
692
760
  interface StatusTagPropsOwn {
693
761
  /** Tonal fill / foreground pair. `neutral` is the quiet informational default; `error` is the rejection / blocked / failed state. */
@@ -813,7 +881,6 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
813
881
  export interface TabProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
814
882
  export interface FeedGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
815
883
  export interface FormFieldGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
816
- export interface PageShellProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
817
884
  export interface NavCardGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
818
885
  export interface CarouselProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
819
886
  export interface SideSheetGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
@@ -825,6 +892,7 @@ export const Banner: React.ForwardRefExoticComponent<BannerProps & React.RefAttr
825
892
  export const BottomSheet: React.ForwardRefExoticComponent<BottomSheetProps & React.RefAttributes<HTMLDivElement>>;
826
893
  export const Bubble: React.ForwardRefExoticComponent<BubbleProps & React.RefAttributes<HTMLDivElement>>;
827
894
  export const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
895
+ export const ButtonGroup: React.ForwardRefExoticComponent<ButtonGroupProps & React.RefAttributes<HTMLDivElement>>;
828
896
  export const Fab: React.ForwardRefExoticComponent<FabProps & React.RefAttributes<HTMLButtonElement>>;
829
897
  export const SuggestionList: React.ForwardRefExoticComponent<SuggestionListProps & React.RefAttributes<HTMLDivElement>>;
830
898
  export const AvatarRail: React.ForwardRefExoticComponent<AvatarRailProps & React.RefAttributes<HTMLDivElement>>;
@@ -832,6 +900,7 @@ export const Chip: React.ForwardRefExoticComponent<ChipProps & React.RefAttribut
832
900
  export const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
833
901
  export const Divider: React.ForwardRefExoticComponent<DividerProps & React.RefAttributes<HTMLElement>>;
834
902
  export const DirectoryList: React.ForwardRefExoticComponent<DirectoryListProps & React.RefAttributes<HTMLElement>>;
903
+ export const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
835
904
  export const Feed: React.ForwardRefExoticComponent<FeedProps & React.RefAttributes<HTMLElement>>;
836
905
  export const FeedAd: React.ForwardRefExoticComponent<FeedAdProps & React.RefAttributes<HTMLElement>>;
837
906
  export const FormField: React.ForwardRefExoticComponent<FormFieldProps & React.RefAttributes<HTMLElement>>;
@@ -842,12 +911,15 @@ export const Metadata: React.ForwardRefExoticComponent<MetadataProps & React.Ref
842
911
  export const NavCard: React.ForwardRefExoticComponent<NavCardProps & React.RefAttributes<HTMLButtonElement>>;
843
912
  export const NavList: React.ForwardRefExoticComponent<NavListProps & React.RefAttributes<HTMLElement>>;
844
913
  export const NavigationBar: React.ForwardRefExoticComponent<NavigationBarProps & React.RefAttributes<HTMLElement>>;
914
+ export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLDivElement>>;
915
+ export const Pagination: React.ForwardRefExoticComponent<PaginationProps & React.RefAttributes<HTMLSpanElement>>;
845
916
  export const ProfileHeader: React.ForwardRefExoticComponent<ProfileHeaderProps & React.RefAttributes<HTMLElement>>;
846
917
  export const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
847
918
  export const PostCarousel: React.ForwardRefExoticComponent<PostCarouselProps & React.RefAttributes<HTMLDivElement>>;
848
919
  export const ProfileCarousel: React.ForwardRefExoticComponent<ProfileCarouselProps & React.RefAttributes<HTMLDivElement>>;
849
920
  export const SideSheet: React.ForwardRefExoticComponent<SideSheetProps & React.RefAttributes<HTMLElement>>;
850
921
  export const Skeleton: React.ForwardRefExoticComponent<SkeletonProps & React.RefAttributes<HTMLSpanElement>>;
922
+ export const Spinner: React.ForwardRefExoticComponent<SpinnerProps & React.RefAttributes<HTMLSpanElement>>;
851
923
  export const StatusTag: React.ForwardRefExoticComponent<StatusTagProps & React.RefAttributes<HTMLSpanElement>>;
852
924
  export const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
853
925
  export const TabBar: React.ForwardRefExoticComponent<TabBarProps & React.RefAttributes<HTMLElement>>;
@@ -858,7 +930,6 @@ export const Tooltip: React.ForwardRefExoticComponent<TooltipProps & React.RefAt
858
930
  export const Tab: React.ForwardRefExoticComponent<TabProps & React.RefAttributes<HTMLElement>>;
859
931
  export const FeedGroup: React.ForwardRefExoticComponent<FeedGroupProps & React.RefAttributes<HTMLElement>>;
860
932
  export const FormFieldGroup: React.ForwardRefExoticComponent<FormFieldGroupProps & React.RefAttributes<HTMLElement>>;
861
- export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLElement>>;
862
933
  export const NavCardGroup: React.ForwardRefExoticComponent<NavCardGroupProps & React.RefAttributes<HTMLElement>>;
863
934
  export const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<HTMLElement>>;
864
935
  export const SideSheetGroup: React.ForwardRefExoticComponent<SideSheetGroupProps & React.RefAttributes<HTMLElement>>;
package/dist/index.d.ts CHANGED
@@ -32,12 +32,22 @@ export type BadgeProps =
32
32
  // ── Banner (banner/banner) ──
33
33
  interface BannerPropsOwn {
34
34
  appearance?: "default" | "accent" | "destructive";
35
+ /** Paints a `sys.borderWidth.hairline` (1) inset stroke around the container, toned to the appearance's color family and kept deliberately faint so it reads as a soft edge of the same tint, not a frame — the subtle gray hairline (`sys.color.outlineVariant`) on `default`'s gray-tinted fill, `primary` at 40% (`color-mix(sys.color.primary, 40%)`) on `accent`'s blue-tinted fill, `error` at 40% on `destructive`. Rendered as an inset box-shadow, never a real border, so toggling it cannot change the banner's footprint (see DESIGN.md → Border & Stroke). Reach for it when the tinted fill alone doesn't separate the banner from its host surface. */
36
+ outlined?: boolean;
37
+ /** On `accent`, paints the title + body in the neutral default foreground (`sys.color.onSurface`) and steps the action to `sys.color.primary` — i.e. the **Default appearance's** foreground treatment laid over the accent fill, decoupling the background tone from the text tone. Reach for it when the `primaryContainer` tint should still pull the eye but the copy should read as quiet, high-legibility body text rather than tonal `onPrimaryContainer` primary-family text (long-form explainers, dense asides). No effect on `default` (already `onSurface`) or `destructive` (the warning tone must carry through the copy). */
38
+ neutralBody?: boolean;
39
+ /** Optional heading line above the body. label.md (14 / Semibold 600) in the container's foreground, separated from the body by `sys.layout.stack.2xs` (4). Reach for it when the aside needs a scannable lead-in; omit for single-thought asides where the body carries itself. */
40
+ title?: React.ReactNode;
35
41
  /** A 16 × 16 (`sys.icon.md`) glyph at the container's leading edge. Inherits the banner's foreground (`currentColor`) so the mark reads as part of the body copy. The slot occupies the body.sm line-box height so the glyph centers on the **first line** of the body — multi-line bodies keep the icon anchored to the first-line cap, not the block center. Ignored when `thumbnail` is also passed. */
36
42
  icon?: React.ReactNode;
43
+ /** A 16 × 16 (`sys.icon.md`) glyph at the container's trailing edge, vertically centered against the whole block (`align-self: center`). Paints in `currentColor`. Reach for it when the banner leads somewhere — a forward affordance such as `ForwardCircleFillIcon` signaling the whole aside opens a destination. */
44
+ trailingIcon?: React.ReactNode;
37
45
  /** A leading visual rendered by [Thumbnail](../thumbnail/thumbnail.md) — used when the aside is anchored to a channel, author, or sub-brand image rather than to a glyph. Takes precedence over `icon`. */
38
46
  thumbnail?: React.ReactNode;
39
47
  /** { label, href? , onClick? } — a follow-through link rendered as a block child below the body. */
40
48
  action?: Record<string, unknown>;
49
+ /** A [Text Button](../button/text.md) (`<Button variant="text">`) rendered at the container's trailing edge, vertically centered against the whole block (`align-self: center`). Distinct from `action` (a follow-through link below the body): `trailingAction` is a compact inline commit that sits beside the copy — Dismiss, Enable, Undo. The button keeps full control of its own `size` and `appearance` per the button/text spec; **by default pick the appearance whose color family matches the banner fill** so the commit reads as part of the tinted block — `accent` banner → `appearance="accent"`, `default` banner → `appearance="default"`, `destructive` banner → the Text Button `destructive` flavor. Override only when a denser rung (`size="small"` / `"xsmall"`) or a different emphasis is deliberately wanted. The button also keeps its own `leadingIcon` / `trailingIcon` slots, so the commit can carry an in-button glyph (e.g. a trailing `ChevronRightIcon` on an *Enable* / *Continue* commit). Takes precedence over the banner-level `trailingIcon` when both are passed. */
50
+ trailingAction?: React.ReactNode;
41
51
  /** Body text — the explanation copy. */
42
52
  children: React.ReactNode;
43
53
  }
@@ -152,6 +162,14 @@ export type ButtonProps =
152
162
  | ButtonToggleProps
153
163
  | ButtonToolbarProps;
154
164
 
165
+ // ── ButtonGroup (button/group) ──
166
+ interface ButtonGroupPropsOwn {
167
+ variant?: "inline" | "docked";
168
+ orientation?: "horizontal" | "vertical";
169
+ label?: React.ReactNode;
170
+ }
171
+ export interface ButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof ButtonGroupPropsOwn>, ButtonGroupPropsOwn {}
172
+
155
173
  // ── Fab (button/fab) ──
156
174
  interface FabPropsOwn {
157
175
  variant?: "fab";
@@ -249,6 +267,19 @@ interface DirectoryListPropsOwn {
249
267
  }
250
268
  export interface DirectoryListProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof DirectoryListPropsOwn>, DirectoryListPropsOwn {}
251
269
 
270
+ // ── EmptyState (empty-state/empty-state) ──
271
+ interface EmptyStatePropsOwn {
272
+ /** Optional leading glyph or illustration, centered above the headline. Sized to a `ref.space.600` (48) box — larger than `sys.icon.lg` (24), realizing DESIGN.md's `icon.xl` or larger intent (no `icon.xl` icon-size rung exists; the icon scale stops at `lg`). Painted in `sys.color.onSurfaceVariant` via `currentColor` so it reads as quiet, monochrome chrome — illustrations stay monochrome unless they carry deliberate brand-moment intent. Separated from the headline by `sys.layout.stack.sm` (12). */
273
+ illustration?: React.ReactNode;
274
+ /** The required lead line. `sys.typo.heading.sm` in `sys.color.onSurface`. Names what the surface is for / why it is empty in one short line (e.g. 'No posts yet'). */
275
+ headline: React.ReactNode;
276
+ /** Optional supporting line below the headline. `sys.typo.body.sm` in `sys.color.onSurfaceVariant`, separated from the headline by `sys.layout.stack.2xs` (4). One sentence — the second of the three lines (e.g. 'Conversations you start or join will appear here'). */
277
+ body?: React.ReactNode;
278
+ /** { label, href?, onClick? } — the primary CTA. Renders a default-size primary `Button` (the surface's primary action — the one thing that fills the empty surface). Placed below the body with a `sys.layout.stack.md` (16) gap. There is NO `cta` slot to fill with a custom button; pass the action object so the primary Button is composed for you. */
279
+ action?: Record<string, unknown>;
280
+ }
281
+ export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof EmptyStatePropsOwn>, EmptyStatePropsOwn {}
282
+
252
283
  // ── Feed (feed/post) ──
253
284
  interface FeedPropsOwn {
254
285
  /** Editorial label — 'HOT', 'NEW', 'PINNED'. Opt-in; most posts render without one. */
@@ -491,9 +522,11 @@ export interface MetadataStandardProps extends Omit<React.HTMLAttributes<HTMLDiv
491
522
 
492
523
  interface MetadataCompactPropsOwn {
493
524
  variant: "compact";
525
+ /** Leading [Thumbnail](../thumbnail/thumbnail.md) at `size={32}` — same prop and rung as the [Standard](standard.md) head. Forwards every Thumbnail prop verbatim (`{ src, alt }`). When omitted, the Thumbnail renders its image-area fallback over `surfaceContainerHigh`. */
526
+ avatar?: Record<string, unknown>;
494
527
  /** Array of independently-linked identity items — canonical fill is `[company name, nickname]`, the nickname canonically last, displayed bare (no @ prefix). Each entry is either a string (renders as a stub-href link) or a `{ label, href, badge }` object — `badge` is an optional SINGLE presentational mark node rendered AFTER the item's link, outside the <a> (canonical fill: Badge variant="role" on the trailing nickname item). Items separate by middot. Same grammar as the standard sub's `meta`; here it is the whole cluster, so it is required. */
495
528
  meta: React.ReactNode;
496
- /** Posting time at the line's trailing edge — plain text (never a link), preceded by a middot, in `label.sm` / `sys.color.outline` so it recedes behind the identity links. Required: the timestamp is what distinguishes a compact attribution from a bare identity row. */
529
+ /** Posting time at the line's trailing edge — plain text (never a link), in `label.sm` / `sys.color.outline` so it recedes behind the identity links. NOT preceded by a middot: the time is separated from the identity cluster by an `inline.md` (8) gap so it reads as a distinct trailing element rather than another identity item (mirrors the Standard head's name↔time treatment). Required: the timestamp is what distinguishes a compact attribution from a bare identity row. */
497
530
  timestamp: string;
498
531
  }
499
532
  export interface MetadataCompactProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof MetadataCompactPropsOwn>, MetadataCompactPropsOwn {}
@@ -594,6 +627,30 @@ export type NavigationBarProps =
594
627
  | NavigationBarSubProps
595
628
  | NavigationBarSearchProps;
596
629
 
630
+ // ── PageShell (page-shell/page-shell) ──
631
+ interface PageShellPropsOwn {
632
+ /** Rendered in flow at the top — a `NavigationBar`. Pays its own `safe-area-inset-top`; the shell does NOT re-pay it. Pinned by the flex column, never by `position: sticky` / `fixed`. */
633
+ nav?: React.ReactNode;
634
+ /** Rendered in flow at the bottom — a `TabBar`. Pays its own `safe-area-inset-bottom`; the shell does NOT re-pay it. Pinned by the flex column, never by `position: sticky` / `fixed`. */
635
+ tabBar?: React.ReactNode;
636
+ /** The scrolling body content — the sole scroll region. Rendered inside `<main class="chorus-page-shell__body">`. */
637
+ children: React.ReactNode;
638
+ /** Spread onto `<main>` — use to add a page gutter (`style={{ paddingInline: 'var(--sys-layout-page-md)' }}`) when the screen carries inline (non-full-bleed) content. `className` composes with `chorus-page-shell__body`; the rest spread as-is. Do NOT use it to re-pay a bar's safe-area inset. */
639
+ bodyProps?: Record<string, unknown>;
640
+ /** Composes with the shell root's own `chorus-page-shell` class. Use for placement only; never to override the pin/scroll mechanics. */
641
+ className?: string;
642
+ }
643
+ export interface PageShellProps extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof PageShellPropsOwn>, PageShellPropsOwn {}
644
+
645
+ // ── Pagination (pagination/pagination) ──
646
+ interface PaginationPropsOwn {
647
+ /** Total number of pages — one dot renders per page. Below 2 the component renders nothing. */
648
+ count?: number;
649
+ /** Zero-based index of the active page. Clamped to `0..count-1`. Owned by the host pager (e.g. updated from an IntersectionObserver on its scroll-snap targets). */
650
+ activeIndex?: number;
651
+ }
652
+ export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof PaginationPropsOwn>, PaginationPropsOwn {}
653
+
597
654
  // ── ProfileHeader (profile-header/profile-header) ──
598
655
  interface ProfileHeaderPropsOwn {
599
656
  /** Entity name — channel topic, person, or company. Renders as the page-level `<h1>` at `sys.typo.heading.lg` / Semibold / `onSurface`. Single line; truncates with ellipsis at narrow widths. */
@@ -688,6 +745,17 @@ interface SkeletonPropsOwn {
688
745
  }
689
746
  export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SkeletonPropsOwn>, SkeletonPropsOwn {}
690
747
 
748
+ // ── Spinner (spinner/spinner) ──
749
+ interface SpinnerPropsOwn {
750
+ /** Selects the arc diameter off the `icon.*` ladder. `medium` paints at `sys.icon.lg` (24px); `small` at `sys.icon.md` (16px). */
751
+ size?: "medium" | "small";
752
+ /** Optional loading copy rendered beside the arc in `sys.typo.body.sm` / `sys.color.onSurfaceVariant`. When present it also supplies the accessible name, so `aria-label` is not required. */
753
+ label?: React.ReactNode;
754
+ /** Accessible label announced by screen readers. Defaults to `'Loading'`. Supply a more specific name (e.g. `'Signing in'`) when the wait scope is meaningful. Redundant when a visible `label` is passed. */
755
+ "aria-label"?: string;
756
+ }
757
+ export interface SpinnerProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SpinnerPropsOwn>, SpinnerPropsOwn {}
758
+
691
759
  // ── StatusTag (status-tag/status-tag) ──
692
760
  interface StatusTagPropsOwn {
693
761
  /** Tonal fill / foreground pair. `neutral` is the quiet informational default; `error` is the rejection / blocked / failed state. */
@@ -813,7 +881,6 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
813
881
  export interface TabProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
814
882
  export interface FeedGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
815
883
  export interface FormFieldGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
816
- export interface PageShellProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
817
884
  export interface NavCardGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
818
885
  export interface CarouselProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
819
886
  export interface SideSheetGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
@@ -825,6 +892,7 @@ export const Banner: React.ForwardRefExoticComponent<BannerProps & React.RefAttr
825
892
  export const BottomSheet: React.ForwardRefExoticComponent<BottomSheetProps & React.RefAttributes<HTMLDivElement>>;
826
893
  export const Bubble: React.ForwardRefExoticComponent<BubbleProps & React.RefAttributes<HTMLDivElement>>;
827
894
  export const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
895
+ export const ButtonGroup: React.ForwardRefExoticComponent<ButtonGroupProps & React.RefAttributes<HTMLDivElement>>;
828
896
  export const Fab: React.ForwardRefExoticComponent<FabProps & React.RefAttributes<HTMLButtonElement>>;
829
897
  export const SuggestionList: React.ForwardRefExoticComponent<SuggestionListProps & React.RefAttributes<HTMLDivElement>>;
830
898
  export const AvatarRail: React.ForwardRefExoticComponent<AvatarRailProps & React.RefAttributes<HTMLDivElement>>;
@@ -832,6 +900,7 @@ export const Chip: React.ForwardRefExoticComponent<ChipProps & React.RefAttribut
832
900
  export const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
833
901
  export const Divider: React.ForwardRefExoticComponent<DividerProps & React.RefAttributes<HTMLElement>>;
834
902
  export const DirectoryList: React.ForwardRefExoticComponent<DirectoryListProps & React.RefAttributes<HTMLElement>>;
903
+ export const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
835
904
  export const Feed: React.ForwardRefExoticComponent<FeedProps & React.RefAttributes<HTMLElement>>;
836
905
  export const FeedAd: React.ForwardRefExoticComponent<FeedAdProps & React.RefAttributes<HTMLElement>>;
837
906
  export const FormField: React.ForwardRefExoticComponent<FormFieldProps & React.RefAttributes<HTMLElement>>;
@@ -842,12 +911,15 @@ export const Metadata: React.ForwardRefExoticComponent<MetadataProps & React.Ref
842
911
  export const NavCard: React.ForwardRefExoticComponent<NavCardProps & React.RefAttributes<HTMLButtonElement>>;
843
912
  export const NavList: React.ForwardRefExoticComponent<NavListProps & React.RefAttributes<HTMLElement>>;
844
913
  export const NavigationBar: React.ForwardRefExoticComponent<NavigationBarProps & React.RefAttributes<HTMLElement>>;
914
+ export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLDivElement>>;
915
+ export const Pagination: React.ForwardRefExoticComponent<PaginationProps & React.RefAttributes<HTMLSpanElement>>;
845
916
  export const ProfileHeader: React.ForwardRefExoticComponent<ProfileHeaderProps & React.RefAttributes<HTMLElement>>;
846
917
  export const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
847
918
  export const PostCarousel: React.ForwardRefExoticComponent<PostCarouselProps & React.RefAttributes<HTMLDivElement>>;
848
919
  export const ProfileCarousel: React.ForwardRefExoticComponent<ProfileCarouselProps & React.RefAttributes<HTMLDivElement>>;
849
920
  export const SideSheet: React.ForwardRefExoticComponent<SideSheetProps & React.RefAttributes<HTMLElement>>;
850
921
  export const Skeleton: React.ForwardRefExoticComponent<SkeletonProps & React.RefAttributes<HTMLSpanElement>>;
922
+ export const Spinner: React.ForwardRefExoticComponent<SpinnerProps & React.RefAttributes<HTMLSpanElement>>;
851
923
  export const StatusTag: React.ForwardRefExoticComponent<StatusTagProps & React.RefAttributes<HTMLSpanElement>>;
852
924
  export const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
853
925
  export const TabBar: React.ForwardRefExoticComponent<TabBarProps & React.RefAttributes<HTMLElement>>;
@@ -858,7 +930,6 @@ export const Tooltip: React.ForwardRefExoticComponent<TooltipProps & React.RefAt
858
930
  export const Tab: React.ForwardRefExoticComponent<TabProps & React.RefAttributes<HTMLElement>>;
859
931
  export const FeedGroup: React.ForwardRefExoticComponent<FeedGroupProps & React.RefAttributes<HTMLElement>>;
860
932
  export const FormFieldGroup: React.ForwardRefExoticComponent<FormFieldGroupProps & React.RefAttributes<HTMLElement>>;
861
- export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLElement>>;
862
933
  export const NavCardGroup: React.ForwardRefExoticComponent<NavCardGroupProps & React.RefAttributes<HTMLElement>>;
863
934
  export const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<HTMLElement>>;
864
935
  export const SideSheetGroup: React.ForwardRefExoticComponent<SideSheetGroupProps & React.RefAttributes<HTMLElement>>;