@teamblind-chorus/ui 1.1.0 → 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 (46) hide show
  1. package/agents/catalog.md +6 -4
  2. package/agents/components/avatar-rail/avatar-rail.spec.json +19 -0
  3. package/agents/components/banner/banner.family.json +3 -1
  4. package/agents/components/banner/banner.md +54 -1
  5. package/agents/components/banner/banner.spec.json +24 -1
  6. package/agents/components/button/check.spec.json +19 -0
  7. package/agents/components/button/fab.spec.json +19 -0
  8. package/agents/components/button/icon.spec.json +19 -0
  9. package/agents/components/button/standard.spec.json +19 -0
  10. package/agents/components/button/text.spec.json +19 -0
  11. package/agents/components/button/toggle.spec.json +19 -0
  12. package/agents/components/chip/filter.spec.json +19 -0
  13. package/agents/components/chip/tag.spec.json +19 -0
  14. package/agents/components/empty-state/empty-state.family.json +28 -0
  15. package/agents/components/empty-state/empty-state.md +69 -0
  16. package/agents/components/empty-state/empty-state.spec.json +87 -0
  17. package/agents/components/form-field/input.spec.json +8 -1
  18. package/agents/components/form-field/search.spec.json +8 -1
  19. package/agents/components/form-field/select.spec.json +9 -1
  20. package/agents/components/form-field/textarea.spec.json +8 -1
  21. package/agents/components/list/accordion.spec.json +9 -0
  22. package/agents/components/list/entry.spec.json +19 -0
  23. package/agents/components/list/radio.spec.json +19 -0
  24. package/agents/components/list/standard.md +46 -0
  25. package/agents/components/list/standard.spec.json +37 -2
  26. package/agents/components/nav-card/nav-card.spec.json +9 -0
  27. package/agents/components/page-shell/page-shell.family.json +1 -1
  28. package/agents/components/page-shell/page-shell.md +33 -0
  29. package/agents/components/page-shell/page-shell.spec.json +85 -0
  30. package/agents/components/spinner/spinner.family.json +27 -0
  31. package/agents/components/spinner/spinner.md +98 -0
  32. package/agents/components/spinner/spinner.spec.json +82 -0
  33. package/agents/components/switch/switch.spec.json +9 -0
  34. package/agents/components/tab-bar/tab-bar.spec.json +16 -0
  35. package/agents/components/tabs/rounded.spec.json +19 -0
  36. package/agents/components/tabs/underline.spec.json +19 -0
  37. package/agents/manifest.json +8 -6
  38. package/agents/usage.json +12 -0
  39. package/dist/index.cjs +340 -60
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +46 -2
  42. package/dist/index.d.ts +46 -2
  43. package/dist/index.js +339 -61
  44. package/dist/index.js.map +1 -1
  45. package/dist/styles.css +182 -0
  46. package/package.json +1 -1
package/dist/index.d.cts CHANGED
@@ -34,6 +34,8 @@ interface BannerPropsOwn {
34
34
  appearance?: "default" | "accent" | "destructive";
35
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
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;
37
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. */
38
40
  title?: React.ReactNode;
39
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. */
@@ -44,6 +46,8 @@ interface BannerPropsOwn {
44
46
  thumbnail?: React.ReactNode;
45
47
  /** { label, href? , onClick? } — a follow-through link rendered as a block child below the body. */
46
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;
47
51
  /** Body text — the explanation copy. */
48
52
  children: React.ReactNode;
49
53
  }
@@ -263,6 +267,19 @@ interface DirectoryListPropsOwn {
263
267
  }
264
268
  export interface DirectoryListProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof DirectoryListPropsOwn>, DirectoryListPropsOwn {}
265
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
+
266
283
  // ── Feed (feed/post) ──
267
284
  interface FeedPropsOwn {
268
285
  /** Editorial label — 'HOT', 'NEW', 'PINNED'. Opt-in; most posts render without one. */
@@ -610,6 +627,21 @@ export type NavigationBarProps =
610
627
  | NavigationBarSubProps
611
628
  | NavigationBarSearchProps;
612
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
+
613
645
  // ── Pagination (pagination/pagination) ──
614
646
  interface PaginationPropsOwn {
615
647
  /** Total number of pages — one dot renders per page. Below 2 the component renders nothing. */
@@ -713,6 +745,17 @@ interface SkeletonPropsOwn {
713
745
  }
714
746
  export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SkeletonPropsOwn>, SkeletonPropsOwn {}
715
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
+
716
759
  // ── StatusTag (status-tag/status-tag) ──
717
760
  interface StatusTagPropsOwn {
718
761
  /** Tonal fill / foreground pair. `neutral` is the quiet informational default; `error` is the rejection / blocked / failed state. */
@@ -838,7 +881,6 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
838
881
  export interface TabProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
839
882
  export interface FeedGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
840
883
  export interface FormFieldGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
841
- export interface PageShellProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
842
884
  export interface NavCardGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
843
885
  export interface CarouselProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
844
886
  export interface SideSheetGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
@@ -858,6 +900,7 @@ export const Chip: React.ForwardRefExoticComponent<ChipProps & React.RefAttribut
858
900
  export const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
859
901
  export const Divider: React.ForwardRefExoticComponent<DividerProps & React.RefAttributes<HTMLElement>>;
860
902
  export const DirectoryList: React.ForwardRefExoticComponent<DirectoryListProps & React.RefAttributes<HTMLElement>>;
903
+ export const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
861
904
  export const Feed: React.ForwardRefExoticComponent<FeedProps & React.RefAttributes<HTMLElement>>;
862
905
  export const FeedAd: React.ForwardRefExoticComponent<FeedAdProps & React.RefAttributes<HTMLElement>>;
863
906
  export const FormField: React.ForwardRefExoticComponent<FormFieldProps & React.RefAttributes<HTMLElement>>;
@@ -868,6 +911,7 @@ export const Metadata: React.ForwardRefExoticComponent<MetadataProps & React.Ref
868
911
  export const NavCard: React.ForwardRefExoticComponent<NavCardProps & React.RefAttributes<HTMLButtonElement>>;
869
912
  export const NavList: React.ForwardRefExoticComponent<NavListProps & React.RefAttributes<HTMLElement>>;
870
913
  export const NavigationBar: React.ForwardRefExoticComponent<NavigationBarProps & React.RefAttributes<HTMLElement>>;
914
+ export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLDivElement>>;
871
915
  export const Pagination: React.ForwardRefExoticComponent<PaginationProps & React.RefAttributes<HTMLSpanElement>>;
872
916
  export const ProfileHeader: React.ForwardRefExoticComponent<ProfileHeaderProps & React.RefAttributes<HTMLElement>>;
873
917
  export const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
@@ -875,6 +919,7 @@ export const PostCarousel: React.ForwardRefExoticComponent<PostCarouselProps & R
875
919
  export const ProfileCarousel: React.ForwardRefExoticComponent<ProfileCarouselProps & React.RefAttributes<HTMLDivElement>>;
876
920
  export const SideSheet: React.ForwardRefExoticComponent<SideSheetProps & React.RefAttributes<HTMLElement>>;
877
921
  export const Skeleton: React.ForwardRefExoticComponent<SkeletonProps & React.RefAttributes<HTMLSpanElement>>;
922
+ export const Spinner: React.ForwardRefExoticComponent<SpinnerProps & React.RefAttributes<HTMLSpanElement>>;
878
923
  export const StatusTag: React.ForwardRefExoticComponent<StatusTagProps & React.RefAttributes<HTMLSpanElement>>;
879
924
  export const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
880
925
  export const TabBar: React.ForwardRefExoticComponent<TabBarProps & React.RefAttributes<HTMLElement>>;
@@ -885,7 +930,6 @@ export const Tooltip: React.ForwardRefExoticComponent<TooltipProps & React.RefAt
885
930
  export const Tab: React.ForwardRefExoticComponent<TabProps & React.RefAttributes<HTMLElement>>;
886
931
  export const FeedGroup: React.ForwardRefExoticComponent<FeedGroupProps & React.RefAttributes<HTMLElement>>;
887
932
  export const FormFieldGroup: React.ForwardRefExoticComponent<FormFieldGroupProps & React.RefAttributes<HTMLElement>>;
888
- export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLElement>>;
889
933
  export const NavCardGroup: React.ForwardRefExoticComponent<NavCardGroupProps & React.RefAttributes<HTMLElement>>;
890
934
  export const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<HTMLElement>>;
891
935
  export const SideSheetGroup: React.ForwardRefExoticComponent<SideSheetGroupProps & React.RefAttributes<HTMLElement>>;
package/dist/index.d.ts CHANGED
@@ -34,6 +34,8 @@ interface BannerPropsOwn {
34
34
  appearance?: "default" | "accent" | "destructive";
35
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
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;
37
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. */
38
40
  title?: React.ReactNode;
39
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. */
@@ -44,6 +46,8 @@ interface BannerPropsOwn {
44
46
  thumbnail?: React.ReactNode;
45
47
  /** { label, href? , onClick? } — a follow-through link rendered as a block child below the body. */
46
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;
47
51
  /** Body text — the explanation copy. */
48
52
  children: React.ReactNode;
49
53
  }
@@ -263,6 +267,19 @@ interface DirectoryListPropsOwn {
263
267
  }
264
268
  export interface DirectoryListProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof DirectoryListPropsOwn>, DirectoryListPropsOwn {}
265
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
+
266
283
  // ── Feed (feed/post) ──
267
284
  interface FeedPropsOwn {
268
285
  /** Editorial label — 'HOT', 'NEW', 'PINNED'. Opt-in; most posts render without one. */
@@ -610,6 +627,21 @@ export type NavigationBarProps =
610
627
  | NavigationBarSubProps
611
628
  | NavigationBarSearchProps;
612
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
+
613
645
  // ── Pagination (pagination/pagination) ──
614
646
  interface PaginationPropsOwn {
615
647
  /** Total number of pages — one dot renders per page. Below 2 the component renders nothing. */
@@ -713,6 +745,17 @@ interface SkeletonPropsOwn {
713
745
  }
714
746
  export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, keyof SkeletonPropsOwn>, SkeletonPropsOwn {}
715
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
+
716
759
  // ── StatusTag (status-tag/status-tag) ──
717
760
  interface StatusTagPropsOwn {
718
761
  /** Tonal fill / foreground pair. `neutral` is the quiet informational default; `error` is the rejection / blocked / failed state. */
@@ -838,7 +881,6 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
838
881
  export interface TabProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
839
882
  export interface FeedGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
840
883
  export interface FormFieldGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
841
- export interface PageShellProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
842
884
  export interface NavCardGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
843
885
  export interface CarouselProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
844
886
  export interface SideSheetGroupProps extends React.HTMLAttributes<HTMLElement> { children?: React.ReactNode; [key: string]: unknown; }
@@ -858,6 +900,7 @@ export const Chip: React.ForwardRefExoticComponent<ChipProps & React.RefAttribut
858
900
  export const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
859
901
  export const Divider: React.ForwardRefExoticComponent<DividerProps & React.RefAttributes<HTMLElement>>;
860
902
  export const DirectoryList: React.ForwardRefExoticComponent<DirectoryListProps & React.RefAttributes<HTMLElement>>;
903
+ export const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
861
904
  export const Feed: React.ForwardRefExoticComponent<FeedProps & React.RefAttributes<HTMLElement>>;
862
905
  export const FeedAd: React.ForwardRefExoticComponent<FeedAdProps & React.RefAttributes<HTMLElement>>;
863
906
  export const FormField: React.ForwardRefExoticComponent<FormFieldProps & React.RefAttributes<HTMLElement>>;
@@ -868,6 +911,7 @@ export const Metadata: React.ForwardRefExoticComponent<MetadataProps & React.Ref
868
911
  export const NavCard: React.ForwardRefExoticComponent<NavCardProps & React.RefAttributes<HTMLButtonElement>>;
869
912
  export const NavList: React.ForwardRefExoticComponent<NavListProps & React.RefAttributes<HTMLElement>>;
870
913
  export const NavigationBar: React.ForwardRefExoticComponent<NavigationBarProps & React.RefAttributes<HTMLElement>>;
914
+ export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLDivElement>>;
871
915
  export const Pagination: React.ForwardRefExoticComponent<PaginationProps & React.RefAttributes<HTMLSpanElement>>;
872
916
  export const ProfileHeader: React.ForwardRefExoticComponent<ProfileHeaderProps & React.RefAttributes<HTMLElement>>;
873
917
  export const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
@@ -875,6 +919,7 @@ export const PostCarousel: React.ForwardRefExoticComponent<PostCarouselProps & R
875
919
  export const ProfileCarousel: React.ForwardRefExoticComponent<ProfileCarouselProps & React.RefAttributes<HTMLDivElement>>;
876
920
  export const SideSheet: React.ForwardRefExoticComponent<SideSheetProps & React.RefAttributes<HTMLElement>>;
877
921
  export const Skeleton: React.ForwardRefExoticComponent<SkeletonProps & React.RefAttributes<HTMLSpanElement>>;
922
+ export const Spinner: React.ForwardRefExoticComponent<SpinnerProps & React.RefAttributes<HTMLSpanElement>>;
878
923
  export const StatusTag: React.ForwardRefExoticComponent<StatusTagProps & React.RefAttributes<HTMLSpanElement>>;
879
924
  export const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
880
925
  export const TabBar: React.ForwardRefExoticComponent<TabBarProps & React.RefAttributes<HTMLElement>>;
@@ -885,7 +930,6 @@ export const Tooltip: React.ForwardRefExoticComponent<TooltipProps & React.RefAt
885
930
  export const Tab: React.ForwardRefExoticComponent<TabProps & React.RefAttributes<HTMLElement>>;
886
931
  export const FeedGroup: React.ForwardRefExoticComponent<FeedGroupProps & React.RefAttributes<HTMLElement>>;
887
932
  export const FormFieldGroup: React.ForwardRefExoticComponent<FormFieldGroupProps & React.RefAttributes<HTMLElement>>;
888
- export const PageShell: React.ForwardRefExoticComponent<PageShellProps & React.RefAttributes<HTMLElement>>;
889
933
  export const NavCardGroup: React.ForwardRefExoticComponent<NavCardGroupProps & React.RefAttributes<HTMLElement>>;
890
934
  export const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<HTMLElement>>;
891
935
  export const SideSheetGroup: React.ForwardRefExoticComponent<SideSheetGroupProps & React.RefAttributes<HTMLElement>>;