@starci/grammar 0.12.3 → 0.12.5

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 (173) hide show
  1. package/README.md +69 -21
  2. package/dist/.build-stamp.json +4 -4
  3. package/dist/common/components-avatar-group.css +7 -0
  4. package/dist/common/components-forms.css +7 -74
  5. package/dist/common/components-list-panel.css +36 -7
  6. package/dist/common/components-navigation.css +46 -157
  7. package/dist/common/components-overlays.css +1 -85
  8. package/dist/common/registry.d.ts +4 -4
  9. package/dist/common/renderers-navigation.d.ts +6 -5
  10. package/dist/common/renderers-navigation.d.ts.map +1 -1
  11. package/dist/common/renderers-navigation.js.map +1 -1
  12. package/dist/common/renderers.d.ts +4 -0
  13. package/dist/common/renderers.d.ts.map +1 -1
  14. package/dist/common/renderers.js +3 -0
  15. package/dist/common/renderers.js.map +1 -1
  16. package/dist/common/styles.css +7 -57
  17. package/dist/core/branch/AlertDialog/index.d.ts.map +1 -1
  18. package/dist/core/branch/AlertDialog/index.js +4 -1
  19. package/dist/core/branch/AlertDialog/index.js.map +1 -1
  20. package/dist/core/branch/DataTable/index.d.ts +0 -2
  21. package/dist/core/branch/DataTable/index.d.ts.map +1 -1
  22. package/dist/core/branch/DataTable/index.js +3 -2
  23. package/dist/core/branch/DataTable/index.js.map +1 -1
  24. package/dist/core/branch/Dialog/index.d.ts.map +1 -1
  25. package/dist/core/branch/Dialog/index.js +2 -1
  26. package/dist/core/branch/Dialog/index.js.map +1 -1
  27. package/dist/core/branch/DragItem/index.d.ts +2 -1
  28. package/dist/core/branch/DragItem/index.d.ts.map +1 -1
  29. package/dist/core/branch/DragItem/index.js +1 -1
  30. package/dist/core/branch/DragItem/index.js.map +1 -1
  31. package/dist/core/branch/Drawer/index.d.ts.map +1 -1
  32. package/dist/core/branch/Drawer/index.js +2 -1
  33. package/dist/core/branch/Drawer/index.js.map +1 -1
  34. package/dist/core/branch/DropdownMenu/index.d.ts +3 -0
  35. package/dist/core/branch/DropdownMenu/index.d.ts.map +1 -1
  36. package/dist/core/branch/DropdownMenu/index.js +4 -2
  37. package/dist/core/branch/DropdownMenu/index.js.map +1 -1
  38. package/dist/core/branch/FlowGraph/index.d.ts +4 -1
  39. package/dist/core/branch/FlowGraph/index.d.ts.map +1 -1
  40. package/dist/core/branch/FlowGraph/index.js +1 -1
  41. package/dist/core/branch/FlowGraph/index.js.map +1 -1
  42. package/dist/core/branch/ListBox/index.d.ts +22 -3
  43. package/dist/core/branch/ListBox/index.d.ts.map +1 -1
  44. package/dist/core/branch/ListBox/index.js +9 -5
  45. package/dist/core/branch/ListBox/index.js.map +1 -1
  46. package/dist/core/branch/Select/index.d.ts +3 -0
  47. package/dist/core/branch/Select/index.d.ts.map +1 -1
  48. package/dist/core/branch/Select/index.js +5 -1
  49. package/dist/core/branch/Select/index.js.map +1 -1
  50. package/dist/core/branch/SurfaceCard/index.d.ts.map +1 -1
  51. package/dist/core/branch/SurfaceCard/index.js +2 -1
  52. package/dist/core/branch/SurfaceCard/index.js.map +1 -1
  53. package/dist/core/branch/SurfaceListCard/index.d.ts.map +1 -1
  54. package/dist/core/branch/SurfaceListCard/index.js +2 -1
  55. package/dist/core/branch/SurfaceListCard/index.js.map +1 -1
  56. package/dist/core/branch/Tabs/classNames.d.ts +1 -3
  57. package/dist/core/branch/Tabs/classNames.d.ts.map +1 -1
  58. package/dist/core/branch/Tabs/classNames.js +1 -4
  59. package/dist/core/branch/Tabs/classNames.js.map +1 -1
  60. package/dist/core/branch/Tabs/index.d.ts +2 -6
  61. package/dist/core/branch/Tabs/index.d.ts.map +1 -1
  62. package/dist/core/branch/Tabs/index.js +5 -3
  63. package/dist/core/branch/Tabs/index.js.map +1 -1
  64. package/dist/core/buttonIntent.d.ts +27 -0
  65. package/dist/core/buttonIntent.d.ts.map +1 -0
  66. package/dist/core/buttonIntent.js +29 -0
  67. package/dist/core/buttonIntent.js.map +1 -0
  68. package/dist/core/collectionItem.d.ts +16 -0
  69. package/dist/core/collectionItem.d.ts.map +1 -0
  70. package/dist/core/collectionItem.js +12 -0
  71. package/dist/core/collectionItem.js.map +1 -0
  72. package/dist/core/composite/AvatarButton/index.d.ts +5 -4
  73. package/dist/core/composite/AvatarButton/index.d.ts.map +1 -1
  74. package/dist/core/composite/AvatarButton/index.js +2 -2
  75. package/dist/core/composite/AvatarButton/index.js.map +1 -1
  76. package/dist/core/composite/AvatarGroup/index.d.ts +6 -4
  77. package/dist/core/composite/AvatarGroup/index.d.ts.map +1 -1
  78. package/dist/core/composite/AvatarGroup/index.js +4 -3
  79. package/dist/core/composite/AvatarGroup/index.js.map +1 -1
  80. package/dist/core/composite/CollectionItem/index.d.ts +8 -0
  81. package/dist/core/composite/CollectionItem/index.d.ts.map +1 -0
  82. package/dist/core/composite/CollectionItem/index.js +5 -0
  83. package/dist/core/composite/CollectionItem/index.js.map +1 -0
  84. package/dist/core/composite/Fieldset/index.js +2 -2
  85. package/dist/core/composite/Fieldset/index.js.map +1 -1
  86. package/dist/core/composite/IdentityLockup/index.d.ts +2 -2
  87. package/dist/core/composite/IdentityLockup/index.d.ts.map +1 -1
  88. package/dist/core/composite/IdentityLockup/index.js +1 -1
  89. package/dist/core/composite/ItemActions/index.d.ts +8 -0
  90. package/dist/core/composite/ItemActions/index.d.ts.map +1 -0
  91. package/dist/core/composite/ItemActions/index.js +5 -0
  92. package/dist/core/composite/ItemActions/index.js.map +1 -0
  93. package/dist/core/composite/ListRow/index.d.ts.map +1 -1
  94. package/dist/core/composite/ListRow/index.js +2 -1
  95. package/dist/core/composite/ListRow/index.js.map +1 -1
  96. package/dist/core/composite/SegmentedControl/index.d.ts.map +1 -1
  97. package/dist/core/composite/SegmentedControl/index.js +2 -2
  98. package/dist/core/composite/SegmentedControl/index.js.map +1 -1
  99. package/dist/core/composite/TagGroup/index.d.ts +3 -3
  100. package/dist/core/composite/TagGroup/index.d.ts.map +1 -1
  101. package/dist/core/composite/TagGroup/index.js +1 -1
  102. package/dist/core/composite/TagGroup/index.js.map +1 -1
  103. package/dist/core/composite/ToolbarActions/index.d.ts +8 -0
  104. package/dist/core/composite/ToolbarActions/index.d.ts.map +1 -0
  105. package/dist/core/composite/ToolbarActions/index.js +5 -0
  106. package/dist/core/composite/ToolbarActions/index.js.map +1 -0
  107. package/dist/core/composition/ChatWorkspace/index.d.ts.map +1 -1
  108. package/dist/core/composition/ChatWorkspace/index.js +2 -1
  109. package/dist/core/composition/ChatWorkspace/index.js.map +1 -1
  110. package/dist/core/composition/NavigationFeatureNav/index.d.ts.map +1 -1
  111. package/dist/core/composition/NavigationFeatureNav/index.js +2 -1
  112. package/dist/core/composition/NavigationFeatureNav/index.js.map +1 -1
  113. package/dist/core/composition/Sidebar/index.d.ts +4 -0
  114. package/dist/core/composition/Sidebar/index.d.ts.map +1 -1
  115. package/dist/core/composition/Sidebar/index.js +5 -3
  116. package/dist/core/composition/Sidebar/index.js.map +1 -1
  117. package/dist/core/composition/TopBar/index.d.ts.map +1 -1
  118. package/dist/core/composition/TopBar/index.js +2 -1
  119. package/dist/core/composition/TopBar/index.js.map +1 -1
  120. package/dist/core/composition/WorkspaceShell/index.d.ts.map +1 -1
  121. package/dist/core/composition/WorkspaceShell/index.js +3 -2
  122. package/dist/core/composition/WorkspaceShell/index.js.map +1 -1
  123. package/dist/core/dna.d.ts +59 -2
  124. package/dist/core/dna.d.ts.map +1 -1
  125. package/dist/core/dna.js +34 -2
  126. package/dist/core/dna.js.map +1 -1
  127. package/dist/core/primitive/Avatar/index.d.ts +32 -24
  128. package/dist/core/primitive/Avatar/index.d.ts.map +1 -1
  129. package/dist/core/primitive/Avatar/index.js +57 -30
  130. package/dist/core/primitive/Avatar/index.js.map +1 -1
  131. package/dist/core/primitive/Button/index.d.ts +3 -1
  132. package/dist/core/primitive/Button/index.d.ts.map +1 -1
  133. package/dist/core/primitive/Button/index.js +4 -1
  134. package/dist/core/primitive/Button/index.js.map +1 -1
  135. package/dist/core/primitive/Field/index.d.ts +10 -1
  136. package/dist/core/primitive/Field/index.d.ts.map +1 -1
  137. package/dist/core/primitive/Field/index.js +16 -1
  138. package/dist/core/primitive/Field/index.js.map +1 -1
  139. package/dist/core/primitive/FileDropzone/index.d.ts.map +1 -1
  140. package/dist/core/primitive/FileDropzone/index.js +2 -2
  141. package/dist/core/primitive/FileDropzone/index.js.map +1 -1
  142. package/dist/core/primitive/GrammarRoot/index.d.ts.map +1 -1
  143. package/dist/core/primitive/GrammarRoot/index.js +3 -1
  144. package/dist/core/primitive/GrammarRoot/index.js.map +1 -1
  145. package/dist/core/primitive/IconButton/index.d.ts +6 -3
  146. package/dist/core/primitive/IconButton/index.d.ts.map +1 -1
  147. package/dist/core/primitive/IconButton/index.js +4 -1
  148. package/dist/core/primitive/IconButton/index.js.map +1 -1
  149. package/dist/core/primitive/Input/index.d.ts.map +1 -1
  150. package/dist/core/primitive/Input/index.js +3 -18
  151. package/dist/core/primitive/Input/index.js.map +1 -1
  152. package/dist/core/primitive/ListItem/index.d.ts.map +1 -1
  153. package/dist/core/primitive/ListItem/index.js +2 -1
  154. package/dist/core/primitive/ListItem/index.js.map +1 -1
  155. package/dist/core/primitive/Skeleton/index.js +1 -1
  156. package/dist/core/primitive/Skeleton/index.js.map +1 -1
  157. package/dist/core/primitive/Slider/index.d.ts.map +1 -1
  158. package/dist/core/primitive/Slider/index.js +2 -2
  159. package/dist/core/primitive/Slider/index.js.map +1 -1
  160. package/dist/core/primitive/Switch/index.d.ts +3 -6
  161. package/dist/core/primitive/Switch/index.d.ts.map +1 -1
  162. package/dist/core/primitive/Switch/index.js +1 -1
  163. package/dist/core/primitive/Switch/index.js.map +1 -1
  164. package/dist/core/styles.css +0 -2
  165. package/dist/heritage/dna.d.ts +62 -0
  166. package/dist/heritage/dna.d.ts.map +1 -0
  167. package/dist/heritage/dna.js +37 -0
  168. package/dist/heritage/dna.js.map +1 -0
  169. package/dist/offset-pop/dna.d.ts +56 -0
  170. package/dist/offset-pop/dna.d.ts.map +1 -1
  171. package/dist/offset-pop/dna.js +31 -0
  172. package/dist/offset-pop/dna.js.map +1 -1
  173. package/package.json +3 -2
package/README.md CHANGED
@@ -36,6 +36,25 @@ its root from one place. Heritage does not re-export Common. It exports `heritag
36
36
 
37
37
  ## Usage
38
38
 
39
+ ### Button intent
40
+
41
+ Applications declare `intent` on `Button` and `IconButton`: `commit`, `alternative`, `dismiss`,
42
+ `neutral` or `destructive`. A destructive action may supply `step="entry" | "confirm"`; omitted
43
+ step means entry. The type permits `step` only for destructive intent. The family DNA's `intent`
44
+ mapping derives the variant. Explicit `variant` remains available to Grammar internals and stories.
45
+ The [generated button props catalog](docs/button-intent-props.generated.md) lists the published values.
46
+
47
+ Collection rows supply item placement automatically. Wrap a custom repeated card in
48
+ `CollectionItem`, or its action slot in `ItemActions`; both preserve the existing DOM. A repeated
49
+ next action resolves to secondary. `ToolbarActions` supplies chrome placement for custom persistent
50
+ toolbars without adding markup. Bars and panel headers supply chrome placement; neutral commands
51
+ are ghost there, while navbar icon actions stay tertiary. The default decision placement makes a
52
+ neutral command outline. Dialogs and drawers restore decision placement inside their surfaces.
53
+
54
+ Use a `Button` for final destructive confirmation. `AlertDialog` renders dismiss first, focuses it
55
+ on entry, then renders its destructive confirm last. The `Primitives/Button intent` stories show
56
+ the complete placement mapping in light and dark, including destination, pending and skeleton states.
57
+
39
58
  ### App shell
40
59
 
41
60
  The [app-shell canon](../../knowledge/ui/composition/app-shell.yaml) owns `FE-APP-SHELL-1` through `FE-APP-SHELL-7`.
@@ -45,7 +64,7 @@ The `Compositions/App shell` stories show the fixed shell, grouped sidebar, icon
45
64
  - `SidebarItem.badge`, `badgeLabel` and `badgeMax` show and announce unread counts in both sidebar presentations. `SidebarGroup.placement="end"` places settings groups after the main groups and pushes them to the bottom.
46
65
  - The collapsed Sidebar uses Grammar's internal `AnchoredTooltip` on hover and focus. HeroUI Tooltip positions the annotation beside the selected collection item and portals it into the same family DOM scope, preserving HeroUI's collection anatomy. `onAction` runs for every activated destination, including the selected item, so a drawer can close consistently.
47
66
  - A fixed `WorkspaceShell` frames its `compactHeader` slot as the mobile opener band; pass a client `Drawer` with an ellipsis `IconButton` trigger. The `navigationDrawer` form owns the same band when it supplies the opener.
48
- - `AvatarButton seed` passes the stable identity id to Grammar's shared `Avatar`, which draws the deterministic local DiceBear image; an uploaded `src` takes precedence.
67
+ - `AvatarButton seed` passes the stable identity id to Grammar's shared `Avatar`, which draws local DiceBear initials whenever `src` is absent; an uploaded `src` takes precedence.
49
68
  - `DropdownMenuItem.mark` accepts a decorative identity mark and is mutually exclusive with `iconSource`. A destructive item uses `tone="danger"`.
50
69
 
51
70
  Compose the interactive shell in a Client Component, where its glyph functions, callbacks and
@@ -78,7 +97,7 @@ import "@starci/grammar/common/styles.css"
78
97
  export const App = () => (
79
98
  <GrammarRoot theme="system">
80
99
  <PageContainer>
81
- <SurfaceCard label="Welcome"><Button>Start</Button></SurfaceCard>
100
+ <SurfaceCard label="Welcome"><Button intent="commit">Start</Button></SurfaceCard>
82
101
  </PageContainer>
83
102
  </GrammarRoot>
84
103
  )
@@ -220,7 +239,7 @@ page of timestamps does not repeatedly interrupt assistive technology. Grammar o
220
239
  `Intl.RelativeTimeFormat` for this primitive; other application date display follows FE-DATE.
221
240
 
222
241
  **DropdownMenu entries.** `entries` is a list of items and sections; the app writes data and no markup, no separator, no
223
- className and no size. An item takes `id`, `label`, `description`, `shortcut`, `href`, `isDisabled`, `submenu` and:
242
+ className and no size. An item takes `id`, `label`, `description`, `descriptionLines`, `shortcut`, `href`, `isDisabled`, `submenu` and:
224
243
 
225
244
  - `iconSource`: a glyph component (the app's Heroicons). Grammar draws it in HeroUI's leading position at the canon icon size,
226
245
  20px (`size-5`, `Icon` `leading`; the Heroicons rule R93 / FE-ICON). HeroUI's own docs example draws its sample glyph at
@@ -257,7 +276,7 @@ and `Select` are HeroUI's own list-box items (a `Label`, a `Description`, the it
257
276
  DescriptionList, Disclosure, ListBox, Pagination, Stepper, Subnav, Tabs, TagGroup, Timeline.
258
277
 
259
278
  `DataTable.getCellInteraction(row, columnId)` optionally supplies `onAction`, `onDragOver`,
260
- `onDragLeave`, `onDrop`, `isHighlighted` and `tone` (`neutral` or `muted`). Cells remain semantic
279
+ `onDragLeave` and `onDrop`. Cells remain semantic
261
280
  table cells under the existing arrow-key navigation. An actionable cell takes focus itself,
262
281
  activates once with Enter/Space or a click on its plain content, and leaves nested controls to
263
282
  their own actions. Native drag callbacks receive the actual inner content-frame event (`HTMLDivElement`); the caller owns
@@ -305,21 +324,30 @@ dismissal and focus return.
305
324
  shape in any state, no hover effect, a zoom-out on press, a focus ring, and no zoom under
306
325
  `prefers-reduced-motion`. `AvatarButton` is a circle-only avatar built on it (an account-menu
307
326
  trigger) and `IdentityLockup` a mark beside a title and a muted subtitle with no caret (a workspace
308
- switcher trigger). All three work as the `trigger` of `DropdownMenu` and `Popover`. `IdentityLockup`'s mark and monogram fallback are transparent, with foreground-colored initials; the mark is an uncropped square by default (`markShape="rounded"` opts in to a
327
+ switcher trigger). All three work as the `trigger` of `DropdownMenu` and `Popover`. `IdentityLockup`'s uploaded mark has a transparent box; without an image it uses Avatar's local DiceBear initials. The mark is an uncropped square by default (`markShape="rounded"` opts in to a
309
328
  corner) and `Avatar` takes `shape="square" | "rounded"`.
310
329
 
311
- Personal avatars are circles. Pass the user or identity id as `seed` on `Avatar`, `AvatarButton`
312
- or each `AvatarGroup` member. Grammar renders the fixed Lorelei DiceBear style chosen by its DNA,
313
- memoized for that seed with identical server and client output. An uploaded `src` wins; with
314
- neither `src` nor `seed`, the existing initials fallback remains. Images have `alt=""`; the avatar
315
- shell carries its name, or `isDecorative` hides it beside a visible name. `AvatarButton` names the
316
- button and keeps its inner avatar decorative. Apps install and import Grammar; its pinned
317
- DiceBear runtime dependencies and avatar drawing stay inside the package.
330
+ Personal avatars are circles. `Avatar`, `AvatarButton` and each `AvatarGroup` member render the
331
+ bundled DiceBear `initials` style whenever `src` is absent. Initials use the first and last words
332
+ of `name`: `Tran Minh Khoa` becomes `TK`, and a single word contributes one letter. Pass the user
333
+ or entity id as `seed` for stable colours; otherwise the name determines the colour. Server and
334
+ client render the same local data URI, with no network request. The image follows the requested
335
+ Avatar shape and keeps contrasting text in light and dark themes. Images have `alt=""`; the
336
+ avatar shell carries its name, or `isDecorative` hides it beside a visible name. `AvatarButton`
337
+ names the button and keeps its inner avatar decorative. Apps install and import Grammar; its
338
+ pinned DiceBear runtime dependencies and avatar drawing stay inside the package.
339
+
340
+ `kind="person" | "agent"` defaults to `person` on `Avatar`, `AvatarButton` and `AvatarGroup` items.
341
+ Agent names are free text and use the same first-and-last-word initials: `Thao Van Mkt` becomes
342
+ `TM`. Agents add a bottom-right bot badge through HeroUI's Badge anchor, with a surface-colour
343
+ ring and `AI` in the accessible name. The badge is hidden below 32px, including group grid cells;
344
+ their initials and accessible identity remain available.
318
345
  The package requires Node 22 or newer, as does the pinned DiceBear engine.
319
346
 
320
347
  ```tsx
321
348
  <Avatar name={user.name} seed={user.id} isDecorative />
322
349
  <AvatarButton name={user.name} seed={user.id} src={user.avatarUrl} />
350
+ <Avatar name="Thao Van Mkt" seed="agent-1" kind="agent" />
323
351
  <AvatarGroup label="Team" items={members.map(user => ({ id: user.id, name: user.name, seed: user.id }))}
324
352
  overflowLabel={count => `${count} more members`} />
325
353
  ```
@@ -353,32 +381,52 @@ canon's `FE_NESTED_VARIANT` rejects static field variants throughout app code an
353
381
  Tabs inside a painted surface. `FE_GRAMMAR_LAYOUT_ONLY` rejects application paint utilities,
354
382
  arbitrary paint properties and inline style paint; application CSS carries layout only.
355
383
 
356
- ListBox defaults to `inset="panel"` inside the same context. The list container extends into the
357
- panel gutter so row content, loading content and section captions align with the header and search
358
- field without padding wrappers (PADDING-0). Items keep HeroUI's default padding and selection-indicator
359
- space; row backgrounds extend into the gutter. Outside a surface, the vendor list inset stays;
384
+ ListBox defaults to `inset="panel"` inside the same context. Its box aligns with the panel's search
385
+ field and tabs boxes without negative margins or application padding wrappers (PADDING-0).
386
+ Content keeps HeroUI's list inset and item padding, rounded hover/pressed/selected backgrounds,
387
+ and selection-indicator space, including inside a scrolling panel. Outside a surface, the vendor list inset stays;
360
388
  `inset="panel"` also selects the alignment explicitly. Pass flat `items` or named `sections`
361
389
  (`ListBoxSection`), with unique item ids across the whole list. `FE_LIST_PANEL_PADDING` rejects
362
390
  horizontal padding wrappers around a list or its section caption within the nearest painted Grammar surface.
363
391
  Sidebar's expanded and collapsed
364
392
  rows also retain HeroUI item padding.
365
393
 
394
+ A `ListBoxItem` is `label` (a string: the title, accessible name and typeahead text), optional `description`,
395
+ `leading` (a centred node in a fixed column before the copy, such as an avatar), `trailing` (after it, such as a count) and
396
+ `isDisabled`. Descriptions show one line with an ellipsis by default; `descriptionLines?: 1 | 2 | 3`
397
+ selects the visible line limit. The same prop and default apply to `DropdownMenuItem`, `SidebarItem`
398
+ and the `ListOption` descriptions used by Select and ComboBox. The packaged stylesheet clamps the
399
+ vendor `Description` slot; its full text stays available to assistive technology.
400
+ `ListBoxItem.emphasis="unread"` makes both the label and description strong using the active family's
401
+ DNA. Omit it for a read row, which keeps HeroUI's normal label and muted description.
402
+ Optional `labelLeading` puts a decorative glyph in the title line itself, inside HeroUI's `Label` before the
403
+ text and on its baseline, e.g. a status mark beside an item's name. Both glyph slots are
404
+ `aria-hidden` (A11Y-2), so the name stays `label`; pass an `Icon usage="chip"` to keep the title line's height.
405
+ `ListBox size="sm" | "md" | "lg"` defaults to `md` and reserves a leading column of 2rem, 2.5rem
406
+ or 3rem, respectively, so icons, avatars and group grids keep labels in one column. An item's
407
+ `size` overrides the list's size.
408
+
366
409
  ```tsx
367
410
  <SurfaceCard ariaLabel="Conversations">
368
411
  <Heading level={2}>Conversations</Heading>
369
412
  <SearchField label="Find a conversation" />
370
413
  <ListBox label="Conversations" sections={[
371
- { id: "recent", label: "Recent", items: [{ id: "team", label: "Design team" }] },
414
+ { id: "recent", label: "Recent", items: [
415
+ { id: "team", label: "Design team", description: "Sam: The draft is ready", emphasis: "unread" },
416
+ ] },
372
417
  ]} />
373
418
  </SurfaceCard>
374
419
  ```
375
420
 
376
421
  For a conversation or group identity, use `AvatarGroup layout="grid"` (COLLECTION-3, the Zalo
377
422
  reference). It is one round Avatar-sized mark at `sm`, `md`, `lg` or `control`: two members split
378
- the circle, three show one above two, and four use a 2 by 2 grid with a hairline gap. With more
379
- members, the fourth cell shows `+N` for everyone beyond the first three. Each member uses Avatar's
380
- uploaded image, `seed` and initials fallback, and accepts the same custom `fallback`. A single
381
- person uses Avatar. The default `layout="row"` keeps the overlapping member row.
423
+ the circle, three show one above two, and four use a 2 by 2 grid. With five or more
424
+ members, three avatars remain and the fourth cell shows the total member count, such as `5` or `30`.
425
+ Each cell has a ring in the surface colour. Each member uses Avatar's
426
+ uploaded image or bundled DiceBear initials, accepts `seed`, `kind` and a custom uploaded-image `fallback`,
427
+ and the count uses the shared avatar renderer. `overflowLabel` receives the number of hidden members
428
+ in both layouts and names them accessibly; the grid's visible digits show the total. A single
429
+ person uses Avatar. The default `layout="row"` keeps the overlapping member row and its `+N` overflow count.
382
430
 
383
431
  ## Theming
384
432
 
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "starci/grammar-build-stamp@1",
3
3
  "package": "@starci/grammar",
4
- "version": "0.12.3",
4
+ "version": "0.12.5",
5
5
  "algorithm": "starci/grammar-source-digest@1",
6
- "sourceDigest": "sha256:3aa873b44e3dde843d9ed4dee490afca7a32cefbaba6f9b023943b9b5e7b5ad5",
7
- "distDigest": "sha256:a0b7ed5f0ebeb59cc7e048c5fde00debef3c1bc496a70f823274dd20d079ded7",
8
- "builtAt": "2026-10-25T16:22:28.270Z"
6
+ "sourceDigest": "sha256:ffb4788560df9c19f27b314c79227eb27ffeab503001d9dfcef8412143ea8833",
7
+ "distDigest": "sha256:b3108e47d872028b103de19bdd2249105e1d355a294a22ec845b40e25bec3e70",
8
+ "builtAt": "2026-10-25T22:28:40.865Z"
9
9
  }
@@ -16,6 +16,13 @@
16
16
  background: var(--surface, Canvas);
17
17
  }
18
18
 
19
+ /* Each member and the total-count cell retain a surface ring over uploaded or generated images. */
20
+ .starci-core-avatar-group-grid > .starci-core-avatar-group-member,
21
+ .starci-core-avatar-group-grid > .starci-core-avatar-group-count {
22
+ outline: 1px solid var(--surface, Canvas);
23
+ outline-offset: -1px;
24
+ }
25
+
19
26
  .starci-core-avatar-group[data-grammar-avatar-cells="1"] > .starci-core-avatar-group-grid {
20
27
  grid-template-columns: minmax(0, 1fr);
21
28
  grid-template-rows: minmax(0, 1fr);
@@ -45,8 +45,6 @@
45
45
  }
46
46
 
47
47
  .starci-core-field-error {
48
- margin: 0;
49
- color: var(--danger, CanvasText);
50
48
  font-size: 0.75rem;
51
49
  font-weight: 600;
52
50
  line-height: 1.4;
@@ -142,7 +140,6 @@
142
140
  .starci-core-field-skeleton-control > .starci-core-skeleton {
143
141
  block-size: 100%;
144
142
  aspect-ratio: auto;
145
- border-radius: var(--field-radius, calc(var(--radius, 0.5rem) * 1.5));
146
143
  }
147
144
 
148
145
  .starci-core-field-skeleton-control:is([data-grammar-field-skeleton="input"]) { block-size: 2.5rem; }
@@ -160,11 +157,8 @@
160
157
 
161
158
  /* A checkbox box and a switch track beside their real labels. */
162
159
  .starci-core-field-skeleton-control[data-grammar-field-skeleton="choice"] { flex: none; block-size: 1rem; inline-size: 1rem; }
163
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="choice"] > .starci-core-skeleton { border-radius: 0.25rem; }
164
160
  .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"] { flex: none; block-size: 1.25rem; inline-size: 2rem; }
165
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"] > .starci-core-skeleton { border-radius: 999px; }
166
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"][data-grammar-switch-size="lg"] { block-size: 2.25rem; inline-size: 4rem; }
167
- .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"][data-grammar-switch-size="control"] { block-size: var(--starci-core-control-min-size, 2.75rem); inline-size: 4.875rem; }
161
+ .starci-core-field-skeleton-control[data-grammar-field-skeleton="switch"][data-grammar-switch-size="lg"] { block-size: 1.5rem; inline-size: 3rem; }
168
162
 
169
163
  .starci-core-field-skeleton-choice-label {
170
164
  color: var(--foreground, CanvasText);
@@ -210,8 +204,11 @@
210
204
  .starci-core-number-field-group,
211
205
  .starci-core-date-group {
212
206
  box-sizing: border-box;
207
+ }
208
+
209
+ .starci-core-input-field,
210
+ .starci-core-combo-box-group {
213
211
  min-block-size: var(--starci-core-field-min-height, 2.5rem);
214
- border-radius: var(--field-radius, calc(var(--radius, 0.5rem) * 1.5));
215
212
  }
216
213
 
217
214
  /* Owner 2026-10-25: Select has the same 2.5rem field height as Input. */
@@ -227,8 +224,7 @@
227
224
  * are never both outlined. The frame keeps the ring for every way focus arrives (`:focus-within`), so keyboard
228
225
  * focus stays visible; forced-colors draws it as a system outline on the same frame.
229
226
  */
230
- :is(.starci-core-search-field-input, .starci-core-number-field-input, .starci-core-combo-box-input):is(:focus, :focus-visible, [data-focused="true"], [data-focus-visible="true"]) {
231
- outline: none;
227
+ .starci-core-combo-box-input:is(:focus, :focus-visible, [data-focused="true"], [data-focus-visible="true"]) {
232
228
  box-shadow: none;
233
229
  }
234
230
 
@@ -250,13 +246,11 @@
250
246
  box-sizing: border-box;
251
247
  flex: 1 1 0;
252
248
  border-radius: inherit;
253
- padding-inline: 0.75rem 1.75rem;
254
249
  text-overflow: ellipsis;
255
250
  }
256
251
 
257
252
  .starci-core-combo-box-trigger {
258
253
  inset-inline-end: 0;
259
- padding-inline-end: 0.5rem;
260
254
  }
261
255
 
262
256
  .starci-core-date-input {
@@ -266,8 +260,6 @@
266
260
 
267
261
  .starci-core-date-range-separator {
268
262
  flex-shrink: 0;
269
- padding-inline: 0.25rem;
270
- color: var(--muted, GrayText);
271
263
  }
272
264
 
273
265
  /* --- List popover (Select, ComboBox) ---
@@ -315,55 +307,7 @@
315
307
  }
316
308
 
317
309
  /*
318
- * --- Switch sizes "lg" (the StarCi look) and "control" ---
319
- * `lg`: a 4rem by 2.25rem fully round track, a thumb the track height less 0.5rem inset 0.25rem, an optional glyph centred in the
320
- * thumb. `control`: the same shape at the shared control height (2.75rem), so a theme switch lines up with the IconButtons and the
321
- * avatar of a bar. Keyed on the stamps the component emits (`data-grammar-switch-size`, `data-grammar-switch-thumb`,
322
- * `data-grammar-switch-icon`), never on vendor class names. The geometry is `!important` because consumers import this sheet on
323
- * either side of `@heroui/styles`, so layer rank cannot decide against the vendor `.switch--*` size rules. The on position is a
324
- * margin (as HeroUI's own thumb), the percentage resolving against the track: 100% - thumb - inset.
325
- */
326
- [data-component="Switch"][data-grammar-switch-size="lg"] {
327
- --grammar-switch-height: 2.25rem;
328
- --grammar-switch-width: 4rem;
329
- }
330
-
331
- [data-component="Switch"][data-grammar-switch-size="control"] {
332
- --grammar-switch-height: var(--starci-core-control-min-size, 2.75rem);
333
- --grammar-switch-width: 4.875rem;
334
- }
335
-
336
- [data-component="Switch"]:is([data-grammar-switch-size="lg"], [data-grammar-switch-size="control"]) {
337
- --grammar-switch-thumb: calc(var(--grammar-switch-height) - 0.5rem);
338
- }
339
-
340
- [data-component="Switch"]:is([data-grammar-switch-size="lg"], [data-grammar-switch-size="control"]) [data-grammar-choice-control="switch"] {
341
- block-size: var(--grammar-switch-height) !important;
342
- inline-size: var(--grammar-switch-width) !important;
343
- border-radius: 9999px !important;
344
- }
345
-
346
- [data-component="Switch"]:is([data-grammar-switch-size="lg"], [data-grammar-switch-size="control"]) [data-grammar-switch-thumb] {
347
- block-size: var(--grammar-switch-thumb) !important;
348
- inline-size: var(--grammar-switch-thumb) !important;
349
- border-radius: 9999px !important;
350
- margin-inline-start: 0.25rem !important;
351
- align-items: center;
352
- justify-content: center;
353
- }
354
-
355
- [data-component="Switch"]:is([data-grammar-switch-size="lg"], [data-grammar-switch-size="control"]):is([data-selected="true"], [aria-checked="true"]) [data-grammar-switch-thumb] {
356
- margin-inline-start: calc(100% - var(--grammar-switch-thumb) - 0.25rem) !important;
357
- }
358
-
359
- /* The description and error align under the label, past the wider track. */
360
- [data-component="Switch"]:is([data-grammar-switch-size="lg"], [data-grammar-switch-size="control"]) > :is([data-slot="description"], [data-slot="field-error"]) {
361
- padding-inline-start: calc(var(--grammar-switch-width) + 0.75rem);
362
- }
363
-
364
- /*
365
- * The thumb glyph box is the canon icon size, not whatever the vendor's thumb container leaves: 20px (size-5, `Icon` `leading`) on `lg`
366
- * and `control`, 16px (`chip`) on `md`, whose thumb is 1rem tall. The glyph fills its box, centred in the thumb.
310
+ * The thumb glyph box is the canon icon size: 20px (size-5, `Icon` `leading`) on `lg`, 16px (`chip`) on `md`. The glyph fills its box, centred in the thumb.
367
311
  */
368
312
  .starci-core-switch-icon {
369
313
  display: inline-flex;
@@ -404,8 +348,6 @@
404
348
 
405
349
  .starci-core-slider-output {
406
350
  color: var(--muted, GrayText);
407
- font-size: 0.875rem;
408
- font-variant-numeric: tabular-nums;
409
351
  }
410
352
 
411
353
  /* --- SegmentedControl --- */
@@ -459,10 +401,6 @@
459
401
  width: 100%;
460
402
  margin: 0;
461
403
  padding: 0;
462
- color: var(--foreground, CanvasText);
463
- font-size: 1rem;
464
- font-weight: 700;
465
- line-height: 1.3;
466
404
  }
467
405
 
468
406
  .starci-core-fieldset-legend + * {
@@ -564,11 +502,6 @@
564
502
  overflow-wrap: anywhere;
565
503
  }
566
504
 
567
- /* --- Read-only reads as settled, not as unavailable --- */
568
- [data-grammar-field="true"][data-grammar-readonly="true"] :where(.starci-core-combo-box-group, .starci-core-search-field-group, .starci-core-number-field-group, .starci-core-date-group, .starci-core-textarea-control) {
569
- background: var(--surface-secondary, var(--field-background, Canvas));
570
- }
571
-
572
505
  /*
573
506
  * THE 44px TOUCH FLOOR.
574
507
  *
@@ -1,13 +1,42 @@
1
- /* PADDING-0: align panel content through the container, retaining HeroUI item and header padding. */
1
+ /* PADDING-0: panel list boxes share the field and tabs edge; HeroUI owns list and item padding. */
2
2
  @layer starci-grammar-common {
3
3
  .grammar-common-root .starci-core-list-box[data-grammar-list-inset="panel"] {
4
- /* Row backgrounds extend into the gutter; vendor padding returns their content to the panel edge. */
5
- inline-size: calc(100% + 1rem);
6
- margin-inline: -0.5rem;
7
- padding-inline: 0 !important;
4
+ inline-size: 100%;
5
+ margin-inline: 0;
8
6
  }
9
7
 
10
- .grammar-common-root .starci-core-list-box[data-grammar-list-inset="panel"] .starci-core-list-box-empty {
11
- padding-inline: 0.5rem;
8
+ /* owner:item-description-one-line: clamp the vendor Description slot, preserving its text and paint. */
9
+ .grammar-common-root [data-slot="description"][data-grammar-item-description="true"] {
10
+ display: -webkit-box;
11
+ -webkit-box-orient: vertical;
12
+ -webkit-line-clamp: 1;
13
+ line-clamp: 1;
14
+ overflow: hidden;
15
+ white-space: normal;
16
+ text-overflow: ellipsis;
17
+ }
18
+
19
+ .grammar-common-root [data-slot="description"][data-grammar-description-lines="2"] {
20
+ -webkit-line-clamp: 2;
21
+ line-clamp: 2;
22
+ }
23
+
24
+ .grammar-common-root [data-slot="description"][data-grammar-description-lines="3"] {
25
+ -webkit-line-clamp: 3;
26
+ line-clamp: 3;
27
+ }
28
+
29
+ .grammar-common-root [data-grammar-sidebar-copy="true"] {
30
+ display: flex;
31
+ flex: 1;
32
+ flex-direction: column;
33
+ min-width: 0;
34
+ }
35
+
36
+ .grammar-common-root [data-grammar-item-label="true"] {
37
+ max-inline-size: 100%;
38
+ overflow: hidden;
39
+ white-space: nowrap;
40
+ text-overflow: ellipsis;
12
41
  }
13
42
  }