@godxjp/ui 28.9.0 → 28.12.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 (244) hide show
  1. package/agent/START-HERE.md +193 -0
  2. package/agent/anti-ai-tells.json +158 -0
  3. package/agent/components/Accordion.json +60 -0
  4. package/agent/components/AccountChip.json +59 -0
  5. package/agent/components/Actions.json +78 -0
  6. package/agent/components/Activity.json +80 -0
  7. package/agent/components/Affix.json +78 -0
  8. package/agent/components/Alert.json +65 -0
  9. package/agent/components/AlertDialog.json +109 -0
  10. package/agent/components/AlertDialogRoot.json +52 -0
  11. package/agent/components/Anchor.json +119 -0
  12. package/agent/components/AppLauncher.json +95 -0
  13. package/agent/components/AppProvider.json +105 -0
  14. package/agent/components/AppSettingPicker.json +88 -0
  15. package/agent/components/AppSettingToggle.json +70 -0
  16. package/agent/components/AppShell.json +159 -0
  17. package/agent/components/AreaChart.json +105 -0
  18. package/agent/components/AspectRatio.json +38 -0
  19. package/agent/components/Attachments.json +77 -0
  20. package/agent/components/AuthAccountSummary.json +64 -0
  21. package/agent/components/AuthDivider.json +35 -0
  22. package/agent/components/AuthFooter.json +48 -0
  23. package/agent/components/AuthIdentity.json +42 -0
  24. package/agent/components/AuthShell.json +108 -0
  25. package/agent/components/AuthStack.json +21 -0
  26. package/agent/components/Avatar.json +89 -0
  27. package/agent/components/Badge.json +96 -0
  28. package/agent/components/Banner.json +48 -0
  29. package/agent/components/BarChart.json +108 -0
  30. package/agent/components/BranchScopePicker.json +89 -0
  31. package/agent/components/Breadcrumb.json +54 -0
  32. package/agent/components/Button.json +133 -0
  33. package/agent/components/Calendar.json +259 -0
  34. package/agent/components/Callout.json +46 -0
  35. package/agent/components/Card.json +112 -0
  36. package/agent/components/CardBar.json +49 -0
  37. package/agent/components/CardContent.json +51 -0
  38. package/agent/components/Carousel.json +50 -0
  39. package/agent/components/Cascader.json +209 -0
  40. package/agent/components/CenteredShell.json +66 -0
  41. package/agent/components/ChatBubble.json +100 -0
  42. package/agent/components/ChatBubbleList.json +64 -0
  43. package/agent/components/ChatComposer.json +160 -0
  44. package/agent/components/ChatSuggestion.json +86 -0
  45. package/agent/components/Checkbox.json +68 -0
  46. package/agent/components/CheckboxGroup.json +96 -0
  47. package/agent/components/CodeBlock.json +64 -0
  48. package/agent/components/Collapsible.json +74 -0
  49. package/agent/components/ColorPicker.json +87 -0
  50. package/agent/components/Command.json +168 -0
  51. package/agent/components/CommandPalette.json +84 -0
  52. package/agent/components/CompactBarTrend.json +101 -0
  53. package/agent/components/Conversations.json +82 -0
  54. package/agent/components/CredentialReveal.json +93 -0
  55. package/agent/components/DataState.json +79 -0
  56. package/agent/components/DataTable.json +268 -0
  57. package/agent/components/DatePicker.json +275 -0
  58. package/agent/components/Descriptions.json +67 -0
  59. package/agent/components/Dialog.json +78 -0
  60. package/agent/components/DraggablePanel.json +106 -0
  61. package/agent/components/DropdownMenu.json +102 -0
  62. package/agent/components/EmptyState.json +83 -0
  63. package/agent/components/ErrorSurface.json +128 -0
  64. package/agent/components/FeatureList.json +43 -0
  65. package/agent/components/Field.json +64 -0
  66. package/agent/components/FilterBar.json +99 -0
  67. package/agent/components/Flex.json +153 -0
  68. package/agent/components/FloatButton.json +91 -0
  69. package/agent/components/Form.json +87 -0
  70. package/agent/components/FormErrors.json +51 -0
  71. package/agent/components/FormField.json +137 -0
  72. package/agent/components/FormFieldArray.json +39 -0
  73. package/agent/components/FormFieldControl.json +129 -0
  74. package/agent/components/FormRoot.json +122 -0
  75. package/agent/components/Heading.json +61 -0
  76. package/agent/components/HoverCard.json +55 -0
  77. package/agent/components/Icon.json +60 -0
  78. package/agent/components/InfiniteQueryState.json +58 -0
  79. package/agent/components/Input.json +122 -0
  80. package/agent/components/InputOTP.json +106 -0
  81. package/agent/components/Label.json +43 -0
  82. package/agent/components/LegalDocumentShell.json +102 -0
  83. package/agent/components/Legend.json +42 -0
  84. package/agent/components/LineChart.json +103 -0
  85. package/agent/components/Link.json +41 -0
  86. package/agent/components/ListRow.json +92 -0
  87. package/agent/components/Logo.json +85 -0
  88. package/agent/components/Marquee.json +91 -0
  89. package/agent/components/Masonry.json +82 -0
  90. package/agent/components/MasterDetail.json +95 -0
  91. package/agent/components/MegaMenu.json +120 -0
  92. package/agent/components/MobileShell.json +73 -0
  93. package/agent/components/NavList.json +63 -0
  94. package/agent/components/NumberInput.json +158 -0
  95. package/agent/components/OrgSwitcher.json +89 -0
  96. package/agent/components/OverlayPortalProvider.json +42 -0
  97. package/agent/components/PageContainer.json +181 -0
  98. package/agent/components/Pagination.json +132 -0
  99. package/agent/components/Paragraph.json +40 -0
  100. package/agent/components/PasswordInput.json +79 -0
  101. package/agent/components/PasswordStrength.json +51 -0
  102. package/agent/components/PermissionMatrix.json +81 -0
  103. package/agent/components/PieChart.json +99 -0
  104. package/agent/components/Popover.json +110 -0
  105. package/agent/components/PrefetchLink.json +65 -0
  106. package/agent/components/Progress.json +79 -0
  107. package/agent/components/Prose.json +57 -0
  108. package/agent/components/QrCode.json +62 -0
  109. package/agent/components/Radio.json +98 -0
  110. package/agent/components/RadioGroup.json +91 -0
  111. package/agent/components/RangeTimeline.json +80 -0
  112. package/agent/components/Rating.json +92 -0
  113. package/agent/components/ResizablePanel.json +69 -0
  114. package/agent/components/ResponsiveGrid.json +77 -0
  115. package/agent/components/Reveal.json +70 -0
  116. package/agent/components/ScrollArea.json +104 -0
  117. package/agent/components/SearchInput.json +98 -0
  118. package/agent/components/Segmented.json +96 -0
  119. package/agent/components/Select.json +397 -0
  120. package/agent/components/Separator.json +86 -0
  121. package/agent/components/ServiceCatalogCta.json +46 -0
  122. package/agent/components/ServiceLauncherCard.json +93 -0
  123. package/agent/components/ServiceRolePanel.json +83 -0
  124. package/agent/components/Sheet.json +85 -0
  125. package/agent/components/Sidebar.json +118 -0
  126. package/agent/components/Skeleton.json +57 -0
  127. package/agent/components/SkeletonArticle.json +71 -0
  128. package/agent/components/SkeletonAvatar.json +50 -0
  129. package/agent/components/SkeletonButton.json +57 -0
  130. package/agent/components/SkeletonForm.json +52 -0
  131. package/agent/components/SkeletonImage.json +37 -0
  132. package/agent/components/SkeletonInput.json +51 -0
  133. package/agent/components/SkeletonNode.json +42 -0
  134. package/agent/components/SkeletonRows.json +49 -0
  135. package/agent/components/SkeletonTable.json +45 -0
  136. package/agent/components/Slider.json +160 -0
  137. package/agent/components/SplitPane.json +66 -0
  138. package/agent/components/StatCard.json +83 -0
  139. package/agent/components/Steps.json +95 -0
  140. package/agent/components/Swatch.json +41 -0
  141. package/agent/components/Switch.json +81 -0
  142. package/agent/components/Table.json +112 -0
  143. package/agent/components/Tabs.json +158 -0
  144. package/agent/components/TagInput.json +105 -0
  145. package/agent/components/Text.json +201 -0
  146. package/agent/components/Textarea.json +126 -0
  147. package/agent/components/ThoughtChain.json +76 -0
  148. package/agent/components/Thumbnail.json +70 -0
  149. package/agent/components/TimePicker.json +200 -0
  150. package/agent/components/TimeRangePicker.json +90 -0
  151. package/agent/components/Timeline.json +47 -0
  152. package/agent/components/TimelineGrid.json +92 -0
  153. package/agent/components/Title.json +67 -0
  154. package/agent/components/Toaster.json +42 -0
  155. package/agent/components/Toggle.json +90 -0
  156. package/agent/components/ToggleGroup.json +102 -0
  157. package/agent/components/Toolbar.json +120 -0
  158. package/agent/components/Tooltip.json +110 -0
  159. package/agent/components/Topbar.json +83 -0
  160. package/agent/components/TopbarItem.json +79 -0
  161. package/agent/components/Transfer.json +141 -0
  162. package/agent/components/Tree.json +185 -0
  163. package/agent/components/TreeSelect.json +232 -0
  164. package/agent/components/TwoFactorSetup.json +79 -0
  165. package/agent/components/Typography.json +42 -0
  166. package/agent/components/Upload.json +221 -0
  167. package/agent/components/UploadCropDialog.json +60 -0
  168. package/agent/components/VisuallyHidden.json +20 -0
  169. package/agent/components/Welcome.json +65 -0
  170. package/agent/components/formatDate.json +46 -0
  171. package/agent/components/inertiaUpload.json +32 -0
  172. package/agent/components/useZodForm.json +39 -0
  173. package/agent/components-index.json +884 -0
  174. package/agent/components.json +15515 -0
  175. package/agent/index.json +56 -0
  176. package/agent/llms.txt +32 -0
  177. package/agent/patterns/account-recovery-settings.json +19 -0
  178. package/agent/patterns/async-data-state.json +20 -0
  179. package/agent/patterns/auth-recovery-panels.json +29 -0
  180. package/agent/patterns/badge-coloring.json +14 -0
  181. package/agent/patterns/common-fixes.json +16 -0
  182. package/agent/patterns/confirm-destructive.json +11 -0
  183. package/agent/patterns/data-table-page.json +18 -0
  184. package/agent/patterns/deferred-loading.json +12 -0
  185. package/agent/patterns/error-pages.json +28 -0
  186. package/agent/patterns/inertia-detail-page.json +13 -0
  187. package/agent/patterns/inertia-list-page.json +15 -0
  188. package/agent/patterns/inertia-persistent-layout.json +14 -0
  189. package/agent/patterns/organization-memberships.json +19 -0
  190. package/agent/patterns/page-sections.json +18 -0
  191. package/agent/patterns/settings-page-responsive.json +18 -0
  192. package/agent/patterns/settings-section-rows.json +23 -0
  193. package/agent/patterns/signup-form.json +13 -0
  194. package/agent/patterns/topbar-account-chip.json +18 -0
  195. package/agent/patterns/transactional-email.json +22 -0
  196. package/agent/patterns-index.json +323 -0
  197. package/agent/patterns.json +342 -0
  198. package/agent/rules.json +237 -0
  199. package/agent/tokens.json +8427 -0
  200. package/agent/vocabulary.json +198 -0
  201. package/dist/components/data-display/service-launcher-card.d.ts +19 -0
  202. package/dist/components/data-display/service-launcher-card.js +14 -1
  203. package/dist/components/data-entry/attachments.js +77 -33
  204. package/dist/components/data-entry/input.js +8 -1
  205. package/dist/components/layout/flex.d.ts +2 -2
  206. package/dist/components/layout/flex.js +2 -0
  207. package/dist/components/ui/avatar.d.ts +1 -18
  208. package/dist/components/ui/avatar.js +1 -36
  209. package/dist/components/ui/tag-input.d.ts +10 -0
  210. package/dist/components/ui/tag-input.js +35 -2
  211. package/dist/contracts/measurement.json +1 -1
  212. package/dist/i18n/messages/en.json +190 -1
  213. package/dist/i18n/messages/ja.json +188 -1
  214. package/dist/i18n/messages/vi.json +188 -1
  215. package/dist/lib/image-loading-status.d.ts +25 -0
  216. package/dist/lib/image-loading-status.js +41 -0
  217. package/dist/props/components/data-entry.prop.d.ts +21 -2
  218. package/dist/props/components/layout.prop.d.ts +42 -0
  219. package/dist/props/registry.d.ts +14 -1
  220. package/dist/props/registry.js +18 -1
  221. package/dist/styles/card-layout.css +10 -4
  222. package/dist/styles/control.css +33 -4
  223. package/dist/styles/data-display-layout.css +1 -1
  224. package/dist/styles/data-entry-layout.css +245 -2
  225. package/dist/styles/layout.css +17 -0
  226. package/dist/styles/navigation-layout.css +3 -1
  227. package/dist/styles/shell-layout.css +2 -0
  228. package/dist/styles/table-layout.css +50 -9
  229. package/dist/tokens/components/attachments.css +18 -9
  230. package/dist/tokens/components/segmented.css +7 -3
  231. package/dist/tokens/components/table.css +2 -1
  232. package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
  233. package/docs/DESIGN-AUTHORITY.md +52 -0
  234. package/docs/DEVELOPMENT.md +81 -6
  235. package/docs/assets/service-mark-rose.svg +6 -0
  236. package/docs/assets/service-mark-teal.svg +5 -0
  237. package/docs/data-display/service-launcher-card.tsx +232 -92
  238. package/docs/data-entry/tag-input.tsx +37 -0
  239. package/docs/layout/flex.tsx +40 -0
  240. package/docs/roadmap/website-components.md +34 -0
  241. package/docs/showcase/marketing-page.tsx +54 -45
  242. package/docs/showcase/table-pagination.tsx +99 -18
  243. package/docs/showcase/theme-customization.tsx +25 -2
  244. package/package.json +8 -5
@@ -0,0 +1,198 @@
1
+ [
2
+ {
3
+ "concept": "Abstract controlled value.",
4
+ "name": "ValueProp<T = string>",
5
+ "usedBy": [
6
+ "CheckboxGroup",
7
+ "Upload",
8
+ "Cascader",
9
+ "TreeSelect",
10
+ "Tabs",
11
+ "SearchSelect"
12
+ ],
13
+ "values": [
14
+ "generic"
15
+ ]
16
+ },
17
+ {
18
+ "concept": "Abstract uncontrolled initial value.",
19
+ "name": "DefaultValueProp<T = string>",
20
+ "usedBy": [
21
+ "CheckboxGroup",
22
+ "Upload",
23
+ "Cascader",
24
+ "TreeSelect",
25
+ "Tabs"
26
+ ],
27
+ "values": [
28
+ "generic"
29
+ ]
30
+ },
31
+ {
32
+ "concept": "Callback for abstract value changes. DOM events continue to use onChange.",
33
+ "name": "OnValueChangeProp<T = string>",
34
+ "usedBy": [
35
+ "CheckboxGroup",
36
+ "Upload",
37
+ "Cascader",
38
+ "TreeSelect",
39
+ "Transfer",
40
+ "settings pickers"
41
+ ],
42
+ "values": [
43
+ "(value: T) => void"
44
+ ]
45
+ },
46
+ {
47
+ "concept": "Disclosure state.",
48
+ "name": "OpenProp / DefaultOpenProp / OnOpenChangeProp",
49
+ "usedBy": [
50
+ "Dialog",
51
+ "Sheet",
52
+ "Popover"
53
+ ],
54
+ "values": [
55
+ "boolean",
56
+ "(open: boolean) => void"
57
+ ]
58
+ },
59
+ {
60
+ "concept": "Shared public size names.",
61
+ "name": "SizeProp",
62
+ "notes": "Component-specific subsets must be documented. Old alias small is sm.",
63
+ "usedBy": [
64
+ "Button",
65
+ "Steps",
66
+ "Switch"
67
+ ],
68
+ "values": [
69
+ "xs",
70
+ "sm",
71
+ "md",
72
+ "lg"
73
+ ]
74
+ },
75
+ {
76
+ "concept": "Semantic status/color intent.",
77
+ "name": "ToneProp",
78
+ "notes": "Status values belong in tone, not variant.",
79
+ "usedBy": [
80
+ "Badge",
81
+ "Alert"
82
+ ],
83
+ "values": [
84
+ "default",
85
+ "success",
86
+ "warning",
87
+ "destructive",
88
+ "info",
89
+ "muted",
90
+ "neutral"
91
+ ]
92
+ },
93
+ {
94
+ "concept": "Shared layout gap scale.",
95
+ "name": "GapProp",
96
+ "notes": "The single shared gap scale on Flex — the one layout primitive (default direction=\"row\"; use direction=\"col\" for vertical rhythm; these replaced the removed Stack/Inline).",
97
+ "usedBy": [
98
+ "Flex"
99
+ ],
100
+ "values": [
101
+ "xs",
102
+ "sm",
103
+ "md",
104
+ "lg",
105
+ "xl"
106
+ ]
107
+ },
108
+ {
109
+ "concept": "Primary heading text.",
110
+ "name": "TitleProp",
111
+ "usedBy": [
112
+ "PageContainer",
113
+ "PageHeader",
114
+ "EmptyState",
115
+ "Dialog"
116
+ ],
117
+ "values": [
118
+ "React.ReactNode"
119
+ ]
120
+ },
121
+ {
122
+ "concept": "Page/subtree density.",
123
+ "name": "DensityProp",
124
+ "usedBy": [
125
+ "PageContainer"
126
+ ],
127
+ "values": [
128
+ "compact",
129
+ "default",
130
+ "comfortable"
131
+ ]
132
+ },
133
+ {
134
+ "concept": "Named whole-page SHELL contract for CenteredShell — the page measure, section rhythm, chrome flatness and hero heading tier, all owned by component tokens.",
135
+ "name": "CenteredShellPresetProp",
136
+ "notes": "public-landing is the PUBLIC marketing/product landing geometry: one 67.5rem measure shared by the header bar, the centred column and the footer, the section rhythm, flat elevation-free cards and the hero h1 tier. \"default\" emits NO data-preset at all, so the existing shell box is untouched. Pair with `Flex hideBelow`/`hideFrom` for header region visibility — a landing must never own a page-local media query or a max-width wrapper.",
137
+ "usedBy": [
138
+ "CenteredShell"
139
+ ],
140
+ "values": [
141
+ "default",
142
+ "public-landing"
143
+ ]
144
+ },
145
+ {
146
+ "concept": "Named collection contract for a table — how a dense collection behaves when its container gets narrow.",
147
+ "name": "TablePresetProp",
148
+ "notes": "action-collection swaps the desktop intrinsic column widths for table-layout: fixed + the token-owned column PRIORITY measures below `collapseBelow`, so a five-column approval queue keeps every column — including row actions — inside a 390px frame with no page-local CSS, no hidden column and no horizontal scroll. It changes the SIZING model only: no display change, no role rewriting, no card transformation, so table semantics and screen-reader navigation are identical at every width. ONE contract, two entry points: the `Table` primitive (mark `priority` on each TableHead/TableCell yourself) and the TanStack `DataTable` (mark `priority` on the ColumnDef; it stamps both cells for you, and additionally releases the surface's `--table-surface-min-inline-size` floor). Both read the same `--table-action-collection-*` tokens — there is no parallel DataTable family.",
149
+ "usedBy": [
150
+ "Table",
151
+ "DataTable"
152
+ ],
153
+ "values": [
154
+ "default",
155
+ "action-collection"
156
+ ]
157
+ },
158
+ {
159
+ "concept": "Relative importance of a table column, used by the action-collection preset to allocate the narrow-frame measure.",
160
+ "name": "TableColumnPriorityProp",
161
+ "notes": "On the raw `Table`, set it on BOTH the TableHead and the TableCell of a column; on `DataTable` set it once on the `ColumnDef` and it is stamped onto the <th> and every <td>. `actions` reserves its measure first so the row-action affordance can never be pushed outside the viewport; a column with no priority takes the remaining space (the free-text column).",
162
+ "usedBy": [
163
+ "Table",
164
+ "DataTable"
165
+ ],
166
+ "values": [
167
+ "primary",
168
+ "secondary",
169
+ "meta",
170
+ "actions"
171
+ ]
172
+ },
173
+ {
174
+ "concept": "Named hosted-identity FLOW MEASURE for AuthShell — the card max-width plus the desktop and mobile page gutters, all owned by component tokens.",
175
+ "name": "AuthShellPresetProp",
176
+ "notes": "Orthogonal to AuthShell's `variant` (which owns control density + heading size) and applied after it, so `variant=\"canonical\" preset=\"device-authorization\"` composes. device-authorization = 380px card / 5px inline gutter at 390; context-selection = 25rem card, edge-to-edge on mobile. Selecting a preset REPLACES any consumer-side --auth-shell-card-max-width override — a page-local width is the anti-pattern these presets exist to remove.",
177
+ "usedBy": [
178
+ "AuthShell"
179
+ ],
180
+ "values": [
181
+ "default",
182
+ "device-authorization",
183
+ "context-selection"
184
+ ]
185
+ },
186
+ {
187
+ "concept": "Avatar geometry — WHAT the identity mark represents: a person or an entity. Deliberately NOT the control ShapeProp (default|pill|sharp), whose `sharp` is --radius-sharp: 0 and cannot express the rounded rect an entity mark needs.",
188
+ "name": "AvatarShapeProp",
189
+ "notes": "circle (default, inert) = the round --radius-pill person avatar on the muted surface; square = the entity-header organization/service mark — a compact rounded square on the brand surface driven by --avatar-square-{radius,size,background,foreground}. Choose by meaning, never by look: a className=\"rounded-md bg-primary\" override on the call site is the anti-pattern this prop removes.",
190
+ "usedBy": [
191
+ "Avatar"
192
+ ],
193
+ "values": [
194
+ "circle",
195
+ "square"
196
+ ]
197
+ }
198
+ ]
@@ -28,7 +28,26 @@ import type { LucideIcon } from "lucide-react";
28
28
  import type { HeadingLevelProp, ToneProp } from "../../props/vocabulary/index.js";
29
29
  export type ServiceLauncherStatusTone = Extract<ToneProp, "success" | "warning" | "destructive" | "info" | "neutral" | "muted">;
30
30
  export interface ServiceLauncherCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
31
+ /**
32
+ * The generic glyph for this KIND of service. Stays REQUIRED and stays a `LucideIcon`: every
33
+ * service has one, including a service created ten seconds ago that has no logo yet, and it is
34
+ * the thing `logo` falls back to. See the `logo` note for why this was not widened to a union.
35
+ */
31
36
  icon: LucideIcon;
37
+ /**
38
+ * URL of the service's OWN uploaded mark — the PNG/WebP an administrator uploaded (gh#850).
39
+ * When it loads, it replaces `icon` in the medallion, in the glyph's exact box.
40
+ *
41
+ * It is a fallback chain, not a switch: the tile shows `icon` while the URL is in flight, and
42
+ * KEEPS showing `icon` if the URL 404s, is empty, or decodes to nothing. A launcher never
43
+ * degrades to an empty medallion or a torn-page glyph because a logo went missing — which is the
44
+ * only state that reaches production, since a logo path outlives the file it points at.
45
+ *
46
+ * Decorative, and deliberately has no `logoAlt` companion: the medallion is `aria-hidden` and the
47
+ * service NAME sits beside it. A screen reader must not hear "Attendance" twice (WCAG 2.2 · 1.1.1
48
+ * — an image adjacent to text conveying the same thing is decorative).
49
+ */
50
+ logo?: string;
32
51
  title: React.ReactNode;
33
52
  titleLevel?: HeadingLevelProp;
34
53
  statusLabel?: React.ReactNode;
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Plus } from "lucide-react";
5
+ import { useImageLoadingStatus } from "../../lib/image-loading-status.js";
5
6
  import { cn } from "../../lib/utils.js";
6
7
  import { Badge } from "./badge.js";
7
8
  import { Card, CardContent } from "./card.js";
@@ -9,6 +10,7 @@ const ServiceLauncherCard = React.forwardRef(
9
10
  ({
10
11
  className,
11
12
  icon: Icon,
13
+ logo,
12
14
  title,
13
15
  titleLevel = 2,
14
16
  statusLabel,
@@ -20,6 +22,11 @@ const ServiceLauncherCard = React.forwardRef(
20
22
  ...props
21
23
  }, ref) => {
22
24
  const Heading = `h${titleLevel}`;
25
+ const [logoStatus, setLogoStatus] = React.useState("idle");
26
+ const logoLoadingStatus = useImageLoadingStatus(logo || void 0, {
27
+ loadingStatus: logoStatus,
28
+ setLoadingStatus: setLogoStatus
29
+ });
23
30
  return /* @__PURE__ */ jsx(
24
31
  Card,
25
32
  {
@@ -32,7 +39,13 @@ const ServiceLauncherCard = React.forwardRef(
32
39
  children: /* @__PURE__ */ jsxs(CardContent, { solo: true, children: [
33
40
  /* @__PURE__ */ jsxs("div", { "data-slot": "service-launcher-heading", children: [
34
41
  /* @__PURE__ */ jsxs("div", { "data-slot": "service-launcher-identity", children: [
35
- /* @__PURE__ */ jsx("span", { "data-slot": "service-launcher-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, {}) }),
42
+ /* @__PURE__ */ jsx("span", { "data-slot": "service-launcher-icon", "aria-hidden": "true", children: logoLoadingStatus === "loaded" ? (
43
+ /* No `loading="lazy"`: the probe above has ALREADY fetched this URL by the time
44
+ * this element exists, so a lazy hint would describe a request that is over.
45
+ * `decoding="async"` is the hint that still means something here — it keeps the
46
+ * decode of a grid of logos off the main thread. */
47
+ /* @__PURE__ */ jsx("img", { "data-slot": "service-launcher-logo", src: logo, alt: "", decoding: "async" })
48
+ ) : /* @__PURE__ */ jsx(Icon, {}) }),
36
49
  /* @__PURE__ */ jsx(Heading, { "data-slot": "service-launcher-title", children: title })
37
50
  ] }),
38
51
  statusLabel != null ? /* @__PURE__ */ jsx(Badge, { tone: statusTone, "data-slot": "service-launcher-status", children: statusLabel }) : null
@@ -6,7 +6,6 @@ import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { formatBytes } from "../../lib/format.js";
7
7
  import { cn } from "../../lib/utils.js";
8
8
  import { Button } from "../general/button.js";
9
- import { Text } from "../general/typography.js";
10
9
  import { Progress } from "../data-display/progress.js";
11
10
  import { createUploadItem } from "./upload-types.js";
12
11
  import { readDroppedFiles } from "./upload-files.js";
@@ -30,6 +29,10 @@ function resolvePlaceholder(placeholder, type) {
30
29
  if (!placeholder) return {};
31
30
  return typeof placeholder === "function" ? placeholder(type) : placeholder;
32
31
  }
32
+ function splitFileName(name) {
33
+ const match = /^(.*)\.[^.]+$/.exec(name ?? "");
34
+ return match ? [match[1], (name ?? "").slice(match[1].length)] : [name ?? "", ""];
35
+ }
33
36
  function fileMatchesAccept(file, accept) {
34
37
  if (!accept) return true;
35
38
  return accept.split(",").some((rule) => {
@@ -60,6 +63,7 @@ function PlaceholderUploader({
60
63
  className: cn("ui-attachments-placeholder", className),
61
64
  style,
62
65
  "data-drag-active": dragActive ? "true" : void 0,
66
+ "data-disabled": disabled ? "true" : void 0,
63
67
  "aria-hidden": disabled ? true : void 0,
64
68
  onDragEnter: (event) => {
65
69
  event.preventDefault();
@@ -71,6 +75,7 @@ function PlaceholderUploader({
71
75
  },
72
76
  onDrop: async (event) => {
73
77
  event.preventDefault();
78
+ event.stopPropagation();
74
79
  setDragActive(false);
75
80
  if (disabled) return;
76
81
  const files = await readDroppedFiles(event.dataTransfer, false);
@@ -107,27 +112,55 @@ function AttachmentCard({
107
112
  const { t } = useTranslation();
108
113
  const { disabled } = React.useContext(AttachmentContext);
109
114
  const preview = item.thumbUrl ?? item.url;
115
+ const [namePrefix, nameSuffix] = splitFileName(item.name);
110
116
  const desc = description ?? item.description ?? (item.status === "uploading" ? `${item.percent ?? 0}%` : item.status === "error" ? typeof item.response === "string" ? item.response : t("dataEntry.attachments.error") : item.size ? formatBytes(item.size) : "");
111
- return /* @__PURE__ */ jsxs("li", { className: cn("ui-attachments-card", classNames?.card, classNames?.file), children: [
112
- preview ? /* @__PURE__ */ jsx("img", { src: preview, alt: "", className: "ui-attachments-card-preview", "aria-hidden": "true" }) : /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-placeholder", "aria-hidden": "true", children: /* @__PURE__ */ jsx(FileIcon, {}) }),
113
- (item.status === "uploading" || item.status === "error") && /* @__PURE__ */ jsx("div", { className: "ui-attachments-card-overlay", children: item.status === "uploading" && item.percent != null ? /* @__PURE__ */ jsx(Progress, { value: item.percent, "aria-label": t("dataEntry.attachments.uploading") }) : /* @__PURE__ */ jsx(Text, { size: "xs", children: String(desc) }) }),
114
- desc && item.status !== "uploading" && item.status !== "error" ? /* @__PURE__ */ jsx("div", { className: "ui-attachments-card-meta", children: desc }) : null,
115
- !disabled && onRemove ? /* @__PURE__ */ jsx(
116
- Button,
117
- {
118
- type: "button",
119
- variant: "ghost",
120
- size: "icon-xs",
121
- className: "ui-attachments-card-remove",
122
- "aria-label": t("dataEntry.attachments.remove", { name: item.name }),
123
- onClick: (event) => {
124
- event.stopPropagation();
125
- onRemove();
126
- },
127
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
128
- }
129
- ) : null
130
- ] });
117
+ return /* @__PURE__ */ jsxs(
118
+ "li",
119
+ {
120
+ className: cn("ui-attachments-card", classNames?.card, classNames?.file),
121
+ "data-status": item.status,
122
+ children: [
123
+ preview ? /* @__PURE__ */ jsx("img", { src: preview, alt: "", className: "ui-attachments-card-preview", "aria-hidden": "true" }) : /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-placeholder", "aria-hidden": "true", children: /* @__PURE__ */ jsx(FileIcon, {}) }),
124
+ /* @__PURE__ */ jsxs("span", { className: "ui-attachments-card-content", children: [
125
+ /* @__PURE__ */ jsxs("span", { className: "ui-attachments-card-name", title: item.name, children: [
126
+ /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-name-prefix", children: namePrefix }),
127
+ /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-name-suffix", children: nameSuffix })
128
+ ] }),
129
+ desc ? /* @__PURE__ */ jsx(
130
+ "span",
131
+ {
132
+ className: "ui-attachments-card-meta",
133
+ title: typeof desc === "string" ? desc : void 0,
134
+ children: desc
135
+ }
136
+ ) : null
137
+ ] }),
138
+ item.status === "uploading" && item.percent != null ? /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-progress", children: /* @__PURE__ */ jsx(
139
+ Progress,
140
+ {
141
+ size: "sm",
142
+ value: item.percent,
143
+ "aria-label": t("dataEntry.attachments.uploading")
144
+ }
145
+ ) }) : null,
146
+ !disabled && onRemove ? /* @__PURE__ */ jsx(
147
+ Button,
148
+ {
149
+ type: "button",
150
+ variant: "ghost",
151
+ size: "icon-xs",
152
+ className: "ui-attachments-card-remove",
153
+ "aria-label": t("dataEntry.attachments.remove", { name: item.name }),
154
+ onClick: (event) => {
155
+ event.stopPropagation();
156
+ onRemove();
157
+ },
158
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
159
+ }
160
+ ) : null
161
+ ]
162
+ }
163
+ );
131
164
  }
132
165
  const Attachments = React.forwardRef(
133
166
  ({
@@ -151,6 +184,7 @@ const Attachments = React.forwardRef(
151
184
  const containerRef = React.useRef(null);
152
185
  const inputRef = React.useRef(null);
153
186
  const [internalItems, setInternalItems] = React.useState([]);
187
+ const [dropActive, setDropActive] = React.useState(false);
154
188
  const items = controlledItems ?? internalItems;
155
189
  const setItems = React.useCallback(
156
190
  (next, file) => {
@@ -207,7 +241,7 @@ const Attachments = React.forwardRef(
207
241
  {
208
242
  ref: inputRef,
209
243
  type: "file",
210
- className: "ui-attachments-input",
244
+ className: cn("ui-attachments-input", "sr-only"),
211
245
  accept,
212
246
  multiple: multiple ?? (maxCount == null || maxCount > 1),
213
247
  disabled,
@@ -244,7 +278,7 @@ const Attachments = React.forwardRef(
244
278
  )
245
279
  ] });
246
280
  }
247
- const dropHost = getDropContainer?.() ?? containerRef.current;
281
+ const dropScope = getDropContainer?.() ? "viewport" : "control";
248
282
  return /* @__PURE__ */ jsx(AttachmentContext.Provider, { value: { disabled }, children: /* @__PURE__ */ jsxs(
249
283
  "div",
250
284
  {
@@ -252,6 +286,21 @@ const Attachments = React.forwardRef(
252
286
  className: cn("ui-attachments", className, rootClassName, classNames.root),
253
287
  style: styles.root,
254
288
  dir: "auto",
289
+ onDragEnter: (event) => {
290
+ event.preventDefault();
291
+ if (!disabled) setDropActive(true);
292
+ },
293
+ onDragOver: (event) => event.preventDefault(),
294
+ onDragLeave: (event) => {
295
+ if (!event.currentTarget.contains(event.relatedTarget)) setDropActive(false);
296
+ },
297
+ onDrop: async (event) => {
298
+ event.preventDefault();
299
+ setDropActive(false);
300
+ if (disabled) return;
301
+ const files = await readDroppedFiles(event.dataTransfer, false);
302
+ if (files.length) dispatchFiles(files);
303
+ },
255
304
  children: [
256
305
  fileInput,
257
306
  hasFiles ? /* @__PURE__ */ jsxs(
@@ -297,20 +346,15 @@ const Attachments = React.forwardRef(
297
346
  hidden: hasFiles
298
347
  }
299
348
  ),
300
- dropHost ? /* @__PURE__ */ jsx(
349
+ /* @__PURE__ */ jsx(
301
350
  "div",
302
351
  {
303
352
  className: "ui-attachments-drop-layer",
304
- "data-active": void 0,
305
- "aria-hidden": "true",
306
- onDragOver: (event) => event.preventDefault(),
307
- onDrop: async (event) => {
308
- event.preventDefault();
309
- const files = await readDroppedFiles(event.dataTransfer, false);
310
- dispatchFiles(files);
311
- }
353
+ "data-scope": dropScope,
354
+ "data-active": dropActive ? "true" : void 0,
355
+ "aria-hidden": "true"
312
356
  }
313
- ) : null
357
+ )
314
358
  ]
315
359
  }
316
360
  ) });
@@ -13,7 +13,14 @@ import {
13
13
  } from "./control-appearance.js";
14
14
  import { resolveAllowClear } from "./control-surface.js";
15
15
  const inputBaseClass = [
16
- "ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]",
16
+ // PER-EDGE RADIUS THROUGH A KNOB, because a joined control has to square one side (gh#841).
17
+ // `rounded-[var(--control-radius)]` is a UTILITY, and Tailwind v4 orders utilities after
18
+ // `@layer components` — so `.ui-input-group:has(…) .ui-input { border-start-start-radius: 0 }`
19
+ // resolved, was catalogued, and lost: the field kept 6px on its leading corners and drew a
20
+ // rounded edge exactly where the addon meets it. Fifth instance of the gh#366/#375/#819 trap.
21
+ // The repair is gh#366's: emit the value FROM a token as a utility, and let the components
22
+ // layer decide WHICH token — a custom property has no utility competing with it.
23
+ "ui-control ui-input w-full rounded-s-[var(--input-radius-start,var(--control-radius))] rounded-e-[var(--input-radius-end,var(--control-radius))] transition-[color,box-shadow]",
17
24
  "selection:bg-primary selection:text-primary-foreground",
18
25
  "placeholder:text-muted-foreground",
19
26
  "aria-invalid:border-destructive",
@@ -1,3 +1,3 @@
1
1
  import type { FlexProp } from "../../props/components/layout.prop.js";
2
- export type { FlexAlignProp, FlexDirectionProp, FlexJustifyProp, FlexMarkerProp, FlexProp, FlexProp as FlexProps, } from "../../props/components/layout.prop.js";
3
- export declare function Flex({ as: Element, direction, marker, grow, shrink, surface, bleed, reveal, gap, gapRaw, pad, padRaw, align, justify, wrap, hideBelow, hideFrom, hideBelowRaw, hideFromRaw, fill, width, className, style, children, ...props }: FlexProp): import("react").JSX.Element;
2
+ export type { FlexAlignProp, FlexDirectionProp, FlexJustifyProp, FlexMarkerProp, FlexMeasureProp, FlexProp, FlexProp as FlexProps, } from "../../props/components/layout.prop.js";
3
+ export declare function Flex({ as: Element, direction, marker, grow, shrink, surface, bleed, reveal, gap, gapRaw, pad, padRaw, align, justify, wrap, hideBelow, hideFrom, hideBelowRaw, hideFromRaw, fill, width, measure, className, style, children, ...props }: FlexProp): import("react").JSX.Element;
@@ -33,6 +33,7 @@ function Flex({
33
33
  hideFromRaw,
34
34
  fill = false,
35
35
  width,
36
+ measure,
36
37
  className,
37
38
  style,
38
39
  children,
@@ -81,6 +82,7 @@ function Flex({
81
82
  "data-hide-from-raw": fromRaw,
82
83
  "data-fill": fill ? "" : void 0,
83
84
  "data-width-raw": width === void 0 ? void 0 : "",
85
+ "data-measure": measure,
84
86
  "data-gap-raw": gapRaw,
85
87
  className: cn("ui-flex", gapRaw === void 0 ? flexGapClass[gap] : void 0, className),
86
88
  "data-pad-raw": padRaw === void 0 ? void 0 : "",
@@ -1,22 +1,6 @@
1
1
  import * as React from "react";
2
+ import { type ImageLoadingStatus } from "../../lib/image-loading-status.js";
2
3
  import type { AvatarProp } from "../../props/components/data-display.prop.js";
3
- /**
4
- * Trạng thái tải của ảnh trong avatar — chia sẻ giữa `AvatarImage` và
5
- * `AvatarFallback` qua context, đúng như Radix làm.
6
- *
7
- * React Aria KHÔNG có avatar, nên đây là việc TỰ DỰNG. Nhưng phần cần dựng lại
8
- * không phải cái vỏ `<span>` — mà là logic thật mà `@radix-ui/react-avatar` cầm:
9
- * ảnh chỉ được vẽ khi đã tải XONG, fallback chỉ được vẽ khi ảnh CHƯA xong hoặc
10
- * HỎNG, và `delayMs` giữ fallback im lặng đủ lâu để mạng nhanh không kịp nháy
11
- * một khung initials trước khi ảnh về.
12
- *
13
- * Phép dò là một `new Image()` RỜI, không phải `onLoad`/`onError` trên chính
14
- * thẻ được vẽ: thẻ chỉ tồn tại khi đã `loaded`, nên handler trên nó không bao
15
- * giờ có cơ hội báo "đang tải" hay "hỏng". Đây cũng là lý do một `<img>` hỏng
16
- * không bao giờ nằm trong DOM để trình duyệt vẽ biểu tượng ảnh vỡ lên trên
17
- * fallback.
18
- */
19
- type ImageLoadingStatus = "idle" | "loading" | "loaded" | "error";
20
4
  export declare const Avatar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
21
5
  asChild?: boolean;
22
6
  } & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
@@ -28,4 +12,3 @@ export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<React.
28
12
  asChild?: boolean;
29
13
  delayMs?: number;
30
14
  } & React.RefAttributes<HTMLSpanElement>>;
31
- export {};
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffectEvent, useLayoutEffect } from "@react-aria/utils";
4
4
  import * as React from "react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { useImageLoadingStatus } from "../../lib/image-loading-status.js";
6
7
  import { Slot } from "../../lib/slot.js";
7
8
  import { cn } from "../../lib/utils.js";
8
9
  const AvatarContext = React.createContext(null);
@@ -13,42 +14,6 @@ function useAvatarContext(part) {
13
14
  }
14
15
  return context;
15
16
  }
16
- function getImageLoadingStatus(image) {
17
- if (!image.complete) {
18
- return "loading";
19
- }
20
- return image.naturalWidth > 0 ? "loaded" : "error";
21
- }
22
- function useImageLoadingStatus(src, {
23
- loadingStatus,
24
- setLoadingStatus,
25
- referrerPolicy,
26
- crossOrigin
27
- }) {
28
- useLayoutEffect(() => {
29
- if (!src) {
30
- setLoadingStatus("error");
31
- return;
32
- }
33
- const image = new window.Image();
34
- const handleLoad = (event) => setLoadingStatus(getImageLoadingStatus(event.currentTarget));
35
- const handleError = () => setLoadingStatus("error");
36
- image.addEventListener("load", handleLoad);
37
- image.addEventListener("error", handleError);
38
- if (referrerPolicy) {
39
- image.referrerPolicy = referrerPolicy;
40
- }
41
- image.crossOrigin = crossOrigin ?? null;
42
- image.src = src;
43
- setLoadingStatus(getImageLoadingStatus(image));
44
- return () => {
45
- image.removeEventListener("load", handleLoad);
46
- image.removeEventListener("error", handleError);
47
- setLoadingStatus("idle");
48
- };
49
- }, [src, crossOrigin, referrerPolicy, setLoadingStatus]);
50
- return loadingStatus;
51
- }
52
17
  const Avatar = React.forwardRef(
53
18
  ({
54
19
  className,
@@ -40,6 +40,16 @@ export type TagInputProps = {
40
40
  maxTagCount?: MaxTagCountProp;
41
41
  /** The node standing in for what `maxTagCount` hid (antd `maxTagPlaceholder`). */
42
42
  maxTagPlaceholder?: MaxTagPlaceholderProp;
43
+ /**
44
+ * antd `maxTagTextLength` — cut each chip's TEXT to this many characters; the value is
45
+ * untouched and stays in `title` and in the remover's accessible name.
46
+ *
47
+ * It was declared on the public prop type and never destructured here, so a 71-character
48
+ * identifier set the chip's width and the chip set the row's, painting outside the control
49
+ * (gh#840). Declared and inert is the same defect as absent, with a worse failure mode:
50
+ * a consumer reads the prop, passes it, and watches nothing happen.
51
+ */
52
+ maxTagTextLength?: number;
43
53
  /** Render one chip yourself (antd `tagRender`) — receives the value and an `onClose` remover. */
44
54
  tagRender?: (props: {
45
55
  value: string;
@@ -30,6 +30,7 @@ const TagInput = React.forwardRef(
30
30
  onClear,
31
31
  maxTagCount,
32
32
  maxTagPlaceholder,
33
+ maxTagTextLength,
33
34
  tagRender,
34
35
  tokenSeparators = [","]
35
36
  }, ref) => {
@@ -95,6 +96,12 @@ const TagInput = React.forwardRef(
95
96
  disabled && "ui-tag-input-disabled",
96
97
  className
97
98
  ),
99
+ onMouseDown: (event) => {
100
+ if (event.target !== event.currentTarget) return;
101
+ event.preventDefault();
102
+ const field = event.currentTarget.querySelector(".ui-tag-input-field");
103
+ field?.focus();
104
+ },
98
105
  children: [
99
106
  tags.length > 0 ? /* @__PURE__ */ jsxs("ul", { role: "list", className: "ui-tag-input-list", "data-slot": "tag-input-list", children: [
100
107
  visibleChips.map((chip) => /* @__PURE__ */ jsx(
@@ -110,7 +117,14 @@ const TagInput = React.forwardRef(
110
117
  disabled: Boolean(disabled) || Boolean(readOnly),
111
118
  onClose: () => removeAt(chip.index)
112
119
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [
113
- chip.value,
120
+ /* @__PURE__ */ jsx(
121
+ "span",
122
+ {
123
+ className: "ui-tag-input-chip-label",
124
+ title: maxTagTextLength && chip.value.length > maxTagTextLength ? chip.value : void 0,
125
+ children: maxTagTextLength && chip.value.length > maxTagTextLength ? `${chip.value.slice(0, maxTagTextLength)}\u2026` : chip.value
126
+ }
127
+ ),
114
128
  !disabled && !readOnly ? /* @__PURE__ */ jsx(
115
129
  "button",
116
130
  {
@@ -125,7 +139,26 @@ const TagInput = React.forwardRef(
125
139
  },
126
140
  chip.value
127
141
  )),
128
- omittedChips.length > 0 ? /* @__PURE__ */ jsx("li", { role: "listitem", className: "ui-tag-input-chip", "data-slot": "tag-input-overflow", children: overflow ?? t("dataEntry.selection.overflow", { count: omittedChips.length }) }) : null
142
+ omittedChips.length > 0 ? (
143
+ /* `+N` NAMES WHAT IT HIDES (gh#840). It used to be a bare `<li role="listitem">` with
144
+ no title, no tabindex and no role — so a reader who saw `+1` had no way, by mouse
145
+ or by keyboard, to learn which tag it stood for. antd's `maxTagPlaceholder` exists
146
+ for exactly this; the default now carries the omitted values itself. */
147
+ /* @__PURE__ */ jsx(
148
+ "li",
149
+ {
150
+ role: "listitem",
151
+ className: "ui-tag-input-chip",
152
+ "data-slot": "tag-input-overflow",
153
+ tabIndex: 0,
154
+ title: omittedChips.map((c) => c.value).join(", "),
155
+ "aria-label": t("ui.tagInput.overflowLabel", {
156
+ tags: omittedChips.map((c) => c.value).join(", ")
157
+ }),
158
+ children: overflow ?? t("dataEntry.selection.overflow", { count: omittedChips.length })
159
+ }
160
+ )
161
+ ) : null
129
162
  ] }) : null,
130
163
  /* @__PURE__ */ jsx(
131
164
  "input",