@sableui/react 0.1.1 → 0.3.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 (137) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +120 -0
  3. package/CONTRIBUTING.md +8 -0
  4. package/README.md +1 -0
  5. package/bin/sable-lint.mjs +0 -0
  6. package/bin/sable.mjs +68 -26
  7. package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
  8. package/dist/blocks/index.d.ts +396 -0
  9. package/dist/blocks/index.js +1997 -0
  10. package/dist/chunk-6LCIBMMG.js +4908 -0
  11. package/dist/{chunk-J23ELQQZ.js → chunk-CKNM7KSB.js} +1664 -4270
  12. package/dist/chunk-K6LII3OE.js +5093 -0
  13. package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
  14. package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
  15. package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
  16. package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
  17. package/dist/foundations/index.js +1 -1
  18. package/dist/icon/index.js +2 -2
  19. package/dist/icon-sets/reicon.js +1 -1
  20. package/dist/index.d.ts +624 -133
  21. package/dist/index.js +767 -4964
  22. package/dist/ios/index.d.ts +14 -2
  23. package/dist/ios/index.js +10 -9
  24. package/dist/ios/preview.d.ts +11 -1
  25. package/dist/ios/preview.js +14 -9
  26. package/dist/ios/styles.css +26 -6
  27. package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
  28. package/dist/patterns/index.d.ts +1 -1
  29. package/dist/patterns/index.js +1684 -471
  30. package/dist/presets/index.d.ts +1 -1
  31. package/dist/presets/index.js +1 -1
  32. package/dist/styles.css +238 -40
  33. package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
  34. package/dist/themes/atelier/atelier.css +3 -0
  35. package/dist/themes/atelier/index.d.ts +1 -1
  36. package/dist/themes/base/base.css +34 -4
  37. package/dist/themes/base/index.d.ts +1 -1
  38. package/dist/themes/ember/ember.css +3 -0
  39. package/dist/themes/ember/index.d.ts +1 -1
  40. package/dist/themes/index.d.ts +2 -2
  41. package/dist/themes/index.js +2 -2
  42. package/dist/themes/meridian/index.d.ts +1 -1
  43. package/dist/themes/meridian/meridian.css +10 -5
  44. package/dist/themes/poppy/index.d.ts +1 -1
  45. package/dist/themes/poppy/poppy.css +16 -0
  46. package/dist/themes/prism/index.d.ts +1 -1
  47. package/dist/themes/prism/prism.css +4 -1
  48. package/dist/themes/verdant/index.d.ts +1 -1
  49. package/dist/themes/verdant/verdant.css +7 -2
  50. package/llms/blocks/api-keys-block.md +79 -0
  51. package/llms/blocks/chat-thread-block.md +51 -0
  52. package/llms/blocks/command-palette-block.md +48 -0
  53. package/llms/blocks/cta-band-block.md +62 -0
  54. package/llms/blocks/danger-zone-block.md +69 -0
  55. package/llms/blocks/device-comparison-block.md +91 -0
  56. package/llms/blocks/event-card-block.md +84 -0
  57. package/llms/blocks/faq-block.md +72 -0
  58. package/llms/blocks/feature-grid-block.md +75 -0
  59. package/llms/blocks/feedback-form-block.md +66 -0
  60. package/llms/blocks/goal-progress-block.md +54 -0
  61. package/llms/blocks/hero-block.md +65 -0
  62. package/llms/blocks/invite-team-block.md +61 -0
  63. package/llms/blocks/invoice-history-block.md +84 -0
  64. package/llms/blocks/kpi-sparkline-block.md +57 -0
  65. package/llms/blocks/media-player-block.md +100 -0
  66. package/llms/blocks/newsletter-block.md +64 -0
  67. package/llms/blocks/not-found-block.md +56 -0
  68. package/llms/blocks/notification-list-block.md +63 -0
  69. package/llms/blocks/notification-settings-block.md +54 -0
  70. package/llms/blocks/order-tracking-block.md +62 -0
  71. package/llms/blocks/pricing-card-block.md +82 -0
  72. package/llms/blocks/pricing-table-block.md +129 -0
  73. package/llms/blocks/product-card-block.md +69 -0
  74. package/llms/blocks/profile-card-block.md +103 -0
  75. package/llms/blocks/profile-form-block.md +114 -0
  76. package/llms/blocks/revenue-breakdown-block.md +70 -0
  77. package/llms/blocks/sign-in-block.md +64 -0
  78. package/llms/blocks/sign-up-block.md +78 -0
  79. package/llms/blocks/site-footer-block.md +96 -0
  80. package/llms/blocks/stat-row-block.md +59 -0
  81. package/llms/blocks/steps-block.md +73 -0
  82. package/llms/blocks/task-list-block.md +81 -0
  83. package/llms/blocks/team-members-block.md +91 -0
  84. package/llms/blocks/testimonial-block.md +78 -0
  85. package/llms/blocks/traffic-sources-block.md +63 -0
  86. package/llms/blocks/traffic-trend-block.md +95 -0
  87. package/llms/blocks/upload-list-block.md +66 -0
  88. package/llms/blocks/verify-code-block.md +59 -0
  89. package/llms/blocks/wallet-block.md +84 -0
  90. package/llms/components/area-chart.md +112 -0
  91. package/llms/components/bar-chart.md +131 -0
  92. package/llms/components/bubble.md +5 -5
  93. package/llms/components/button.md +1 -1
  94. package/llms/components/card.md +2 -2
  95. package/llms/components/checkbox-card.md +1 -1
  96. package/llms/components/context-card.md +2 -2
  97. package/llms/components/context-menu.md +1 -1
  98. package/llms/components/date-tile.md +79 -0
  99. package/llms/components/dialog.md +1 -1
  100. package/llms/components/empty-state.md +4 -4
  101. package/llms/components/entity.md +5 -3
  102. package/llms/components/fieldset.md +1 -1
  103. package/llms/components/kbd.md +18 -6
  104. package/llms/components/line-chart.md +101 -0
  105. package/llms/components/menu.md +1 -1
  106. package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
  107. package/llms/components/pie-chart.md +103 -0
  108. package/llms/components/pill.md +1 -1
  109. package/llms/components/radar-chart.md +106 -0
  110. package/llms/components/radial-chart.md +102 -0
  111. package/llms/components/segmented-bar.md +102 -0
  112. package/llms/components/settings-menu.md +2 -2
  113. package/llms/components/side-nav.md +1 -1
  114. package/llms/components/skeleton.md +1 -1
  115. package/llms/components/sparkline.md +80 -0
  116. package/llms/components/table.md +1 -1
  117. package/llms/components/theme-selector.md +1 -1
  118. package/llms/foundations/shape.md +1 -1
  119. package/llms/patterns/auth-page.md +132 -42
  120. package/llms/patterns/brand-landing-page.md +271 -175
  121. package/llms/patterns/chat-panel.md +120 -39
  122. package/llms/patterns/dashboard-page.md +276 -65
  123. package/llms/patterns/feed-page.md +245 -64
  124. package/llms/patterns/landing-page.md +473 -75
  125. package/llms/patterns/list-detail-page.md +377 -68
  126. package/llms/patterns/settings-page.md +268 -67
  127. package/llms/reference/token-intent.md +4 -2
  128. package/llms/reference/utilities.md +20 -5
  129. package/llms/tasks/building-an-ios-prototype.md +4 -1
  130. package/llms/tasks/choosing-a-component.md +2 -2
  131. package/llms-full.txt +6428 -892
  132. package/llms.txt +67 -16
  133. package/package.json +5 -1
  134. package/registry.json +3217 -552
  135. package/targets/ios.json +88 -18
  136. package/llms/components/description.md +0 -96
  137. package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
@@ -12,7 +12,11 @@ A full-height conversation surface: titled header, scrolling transcript, and a c
12
12
  - Region cap is `feed` (640px) — a conversation is a reading column, and the Bubble measure cap is tuned to the same assumption.
13
13
  - The composer sits outside the scroll region, in a bordered CardFooter, so it stays put while the transcript moves.
14
14
  - Grouping is derived from the data (`turns[i - 1].mine === turn.mine`) rather than hand-set, and the tail is dropped on continuations — that pairing is what makes a run read as one block.
15
- - The assistant's long-form answer uses `variant="plain"`. A tinted box around a code block reads as a quote rather than as a turn.
15
+ - What the assistant did before answering — a doc it read, a tool it ran — is a `Marker variant="note"` in that message's `header`, so it reads as part of the turn rather than as an event between turns.
16
+ - The assistant's long-form answer uses `variant="plain"` and continues the same grouped run. A tinted box around a code block reads as a quote rather than as a turn. A plain Bubble sizes to its content with no measure cap, so it takes `max-w-full` here: bounded by the column, a wide code line scrolls inside the CodeBlock instead of pushing the transcript sideways on a phone.
17
+ - A settled answer carries its actions (copy, regenerate) in the Message `footer` as a `Toolbar` of icon `ToolbarButton`s with tooltips — one tab stop, arrow keys between them. The answer still streaming has none: there is nothing to copy or regenerate until it settles, and Stop lives in the composer.
18
+ - One turn, one Message: the reader's question and the file it refers to stack inside a single end-aligned Message rather than being sent as two.
19
+ - Assistant turns share one Avatar with a glyph fallback and `alt="Assistant"` — initials like "AI" read as a person, and a fallback-only avatar is otherwise unnamed.
16
20
  - The answer in flight is a `StreamingText` in a `plain` Bubble, and ONE `pending` flag drives both it and the composer's `busy`. They are the same wait reported at the two ends of the panel; letting them drift gives you a composer still saying 'sending' under an answer that already finished.
17
21
  - A spinner `Marker` (or `ThinkingOrb`) belongs BEFORE the first token and is replaced once tokens land, not kept alongside. Two indicators for one wait read as two things happening — which is also why `StreamingText` takes `caret={false}` when something adjacent already reports it.
18
22
  - MessageScroller needs no configuring for this: it re-follows on "a message grew" (ResizeObserver) as well as "a message arrived", so the newest token stays pinned while a reader who has scrolled up is left alone.
@@ -22,12 +26,12 @@ A full-height conversation surface: titled header, scrolling transcript, and a c
22
26
 
23
27
  - The panel header's title is the page's <h1> (an h1 inside CardTitle, which carries the type); the transcript is a scroll region with native overflow, so keyboard scrolling works unchanged.
24
28
  - The answer in flight carries `aria-busy` rather than sitting in a live region. That is deliberate: a live region around streamed text announces every token, which is unusable. Announce once when the turn settles, from whatever owns the turn — the incomplete state is still exposed, just not narrated.
25
- - The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label.
29
+ - The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label — the answer actions also carry a tooltip, and the stop control is named for what it stops (`stopLabel="Stop answering"`).
26
30
 
27
31
  ## Source
28
32
 
29
33
  ```tsx
30
- import { Attachment, Avatar, Bubble, BubbleReaction, BubbleReactions, Button, Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle, ChatComposer, CodeBlock, Icon, Marker, MenuRadioGroup, MenuRadioItem, Message, MessageScroller, Page, SettingsMenu, SettingsRow, StreamingText } from "@sableui/react"
34
+ import { Attachment, Avatar, Bubble, Button, Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle, ChatComposer, CodeBlock, Icon, Marker, MenuRadioGroup, MenuRadioItem, Message, MessageScroller, Page, SettingsMenu, SettingsRow, SimpleTooltip, StreamingText, Toolbar, ToolbarButton } from "@sableui/react"
31
35
 
32
36
  /**
33
37
  * The request is in flight. In an app this is your send state — from `useChat`,
@@ -40,9 +44,14 @@ const pending = true
40
44
 
41
45
  /** The answer so far. Append tokens to this; `StreamingText` assumes it only grows. */
42
46
  const streamed =
43
- "Anchor on the oldest rendered message and add back however far it moved. Appends"
47
+ "Yes, appends are already safe: your layout effect measures before paint. The jump is on the prepend path, which reads scrollHeight after the new rows"
44
48
 
45
- const turns = [
49
+ /**
50
+ * The opening exchange. `step` is what the assistant did before answering — a
51
+ * doc it read, a tool it ran — and it rides in that message's header, so it
52
+ * reads as part of the turn rather than as an event between turns.
53
+ */
54
+ const turns: { id: string; mine: boolean; step?: string; body: string }[] = [
46
55
  {
47
56
  id: "t_01",
48
57
  mine: true,
@@ -51,6 +60,7 @@ const turns = [
51
60
  {
52
61
  id: "t_02",
53
62
  mine: false,
63
+ step: "Read message-scroller.md",
54
64
  body: "Almost always the anchor. If you preserve the scroll position by measuring the container, prepending history moves everything under the reader.",
55
65
  },
56
66
  {
@@ -61,6 +71,19 @@ const turns = [
61
71
  ]
62
72
 
63
73
  export function ChatPanel() {
74
+ // One identity for every assistant turn. A glyph rather than initials: "AI"
75
+ // in a circle reads as a person called A. I. The glyph is sized to the
76
+ // circle (the fallback's own size is tuned for two letters, and a lone icon
77
+ // at that size reads as a speck); `alt` names it for a screen reader, which
78
+ // otherwise gets nothing from a fallback-only avatar.
79
+ const assistant = (
80
+ <Avatar
81
+ size="sm"
82
+ alt="Assistant"
83
+ fallback={<Icon name="sparkles" className="text-base" />}
84
+ />
85
+ )
86
+
64
87
  return (
65
88
  <Page as="main" max="feed" gap={0} className="py-6">
66
89
  {/* h-dvh minus the page's own padding (py-6 = 12 spacing units, in
@@ -79,8 +102,12 @@ export function ChatPanel() {
79
102
  </CardTitle>
80
103
  <CardDescription>Assistant · answers from your team's docs</CardDescription>
81
104
  <CardAction>
82
- <Button variant="outline" size="icon-sm" aria-label="Start a new chat">
83
- <Icon name="plus" />
105
+ {/* Labelled, not a bare plus: the composer already has a plus
106
+ (attach), and two identical glyphs a panel apart that do
107
+ different things is a guessing game. */}
108
+ <Button variant="ghost" size="sm">
109
+ <Icon name="edit" />
110
+ New chat
84
111
  </Button>
85
112
  </CardAction>
86
113
  </CardHeader>
@@ -90,32 +117,67 @@ export function ChatPanel() {
90
117
  <MessageScroller className="min-h-0 flex-1 p-(--card-p)">
91
118
  <Marker variant="separator">Today</Marker>
92
119
 
93
- {turns.map((turn, i) => (
94
- <Message
95
- key={turn.id}
96
- messageId={turn.id}
97
- align={turn.mine ? "end" : "start"}
98
- grouped={i > 0 && turns[i - 1].mine === turn.mine}
99
- avatar={turn.mine ? undefined : <Avatar size="sm" fallback="AI" />}
100
- >
101
- <Bubble
102
- variant={turn.mine ? "primary" : "muted"}
120
+ {turns.map((turn, i) => {
121
+ // Derived, not hand-set: a turn continues a run when the one
122
+ // before it has the same author. A continuation drops its
123
+ // avatar (Message keeps the column) AND its tail (Bubble keeps
124
+ // the ear's space) — together, that makes the run one block.
125
+ const continues = i > 0 && turns[i - 1].mine === turn.mine
126
+ return (
127
+ <Message
128
+ key={turn.id}
129
+ messageId={turn.id}
103
130
  align={turn.mine ? "end" : "start"}
104
- tail={i === 0 || turns[i - 1].mine !== turn.mine}
131
+ grouped={continues}
132
+ avatar={turn.mine ? undefined : assistant}
133
+ header={
134
+ turn.step ? (
135
+ <Marker variant="note" icon="book" className="justify-start">
136
+ {turn.step}
137
+ </Marker>
138
+ ) : undefined
139
+ }
105
140
  >
106
- {turn.body}
107
- </Bubble>
108
- </Message>
109
- ))}
110
-
111
- <Marker variant="note" icon="sparkles">
112
- Read message-scroller.md
113
- </Marker>
114
-
115
- {/* An assistant's long-form answer: no bubble, because a tinted box
116
- around a code block reads as a quote rather than as a turn. */}
117
- <Message messageId="t_04" avatar={<Avatar size="sm" fallback="AI" />}>
118
- <Bubble variant="plain">
141
+ <Bubble
142
+ variant={turn.mine ? "primary" : "muted"}
143
+ align={turn.mine ? "end" : "start"}
144
+ tail={!continues}
145
+ >
146
+ {turn.body}
147
+ </Bubble>
148
+ </Message>
149
+ )
150
+ })}
151
+
152
+ {/* The same run, continued: long-form output with no bubble,
153
+ because a tinted box around a code block reads as a quote
154
+ rather than as a turn. Its footer carries what the reader does
155
+ WITH a settled answer, as a Toolbar: one tab stop, arrow keys
156
+ between the actions. */}
157
+ <Message
158
+ messageId="t_04"
159
+ grouped
160
+ avatar={assistant}
161
+ footer={
162
+ <Toolbar aria-label="Answer actions">
163
+ <SimpleTooltip content="Copy answer">
164
+ <ToolbarButton size="icon-sm" aria-label="Copy answer">
165
+ <Icon name="copy" />
166
+ </ToolbarButton>
167
+ </SimpleTooltip>
168
+ <SimpleTooltip content="Regenerate">
169
+ <ToolbarButton size="icon-sm" aria-label="Regenerate answer">
170
+ <Icon name="refresh" />
171
+ </ToolbarButton>
172
+ </SimpleTooltip>
173
+ </Toolbar>
174
+ }
175
+ >
176
+ {/* max-w-full: a plain Bubble sizes to its content with no
177
+ measure cap, and a code line is wider than a phone. Bounded
178
+ by the column, the CodeBlock scrolls inside itself instead of
179
+ pushing the whole transcript sideways. */}
180
+ <Bubble variant="plain" className="max-w-full">
119
181
  <p className="text-body-md text-foreground">
120
182
  Here is the shape of it:
121
183
  </p>
@@ -126,28 +188,46 @@ export function ChatPanel() {
126
188
  filename="preserve.ts"
127
189
  />
128
190
  </Bubble>
129
- <BubbleReactions>
130
- <BubbleReaction emoji="🎯" count={2} active />
131
- </BubbleReactions>
132
191
  </Message>
133
192
 
193
+ {/* The reader's follow-up is one turn: the question and the file
194
+ it refers to stack in a single Message, so they share a side, an
195
+ edge and a timestamp rather than reading as two sends. */}
134
196
  <Message messageId="t_05" align="end">
135
- <Attachment name="scroller.tsx" meta="4.2 KB · TSX" />
197
+ <Bubble variant="primary" align="end">
198
+ Here's ours. Does the same fix cover new messages arriving?
199
+ </Bubble>
200
+ <Attachment
201
+ name="scroller.tsx"
202
+ meta="4.2 KB · TSX"
203
+ onOpen={() => undefined}
204
+ />
136
205
  </Message>
137
206
 
138
207
  {/* The answer arriving. A `Marker` with a spinner — or ThinkingOrb —
139
208
  is what sits here BEFORE the first token; once tokens land it is
140
209
  replaced rather than kept, because two indicators for one wait
141
- read as two things happening.
210
+ read as two things happening. What stays is the step it took,
211
+ as a finished note in the header.
142
212
 
143
- No bubble, same as the long-form answer above: a tinted box around
144
- a growing paragraph makes the reflow look like the box breathing.
213
+ No bubble, same as the long-form answer above: a tinted box
214
+ around a growing paragraph makes the reflow look like the box
215
+ breathing. And no answer actions yet — there is nothing to copy
216
+ or regenerate until it settles; Stop lives in the composer.
145
217
 
146
218
  MessageScroller needs no help with this — it already re-follows on
147
219
  "a message grew" (ResizeObserver), not just on "a message arrived",
148
220
  so the newest token stays pinned while the reader can still scroll
149
221
  up without being yanked back. */}
150
- <Message messageId="t_06" avatar={<Avatar size="sm" fallback="AI" />}>
222
+ <Message
223
+ messageId="t_06"
224
+ avatar={assistant}
225
+ header={
226
+ <Marker variant="note" icon="file" className="justify-start">
227
+ Read scroller.tsx
228
+ </Marker>
229
+ }
230
+ >
151
231
  <Bubble variant="plain">
152
232
  <StreamingText text={streamed} streaming={pending} />
153
233
  </Bubble>
@@ -163,6 +243,7 @@ export function ChatPanel() {
163
243
  // second request on top of the one still running.
164
244
  busy={pending}
165
245
  onStop={() => undefined}
246
+ stopLabel="Stop answering"
166
247
  onAttach={() => undefined}
167
248
  footer="Enter to send · Shift+Enter for a new line"
168
249
  // Model and effort sit in `actions`, at the send end of the row: