@spfx-fast/sharepoint-ui 0.4.3 → 0.5.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 (282) hide show
  1. package/QUICKSTART.md +13 -128
  2. package/README.md +19 -26
  3. package/components.json +240 -5834
  4. package/dist/accordion.d.ts +11 -0
  5. package/dist/accordion.js +4 -0
  6. package/dist/alert.d.ts +3 -0
  7. package/dist/alert.js +4 -0
  8. package/dist/attachment-list.d.ts +15 -0
  9. package/dist/attachment-list.js +14 -0
  10. package/dist/avatar-group.d.ts +12 -0
  11. package/dist/avatar-group.js +7 -0
  12. package/dist/avatar.d.ts +6 -0
  13. package/dist/avatar.js +5 -0
  14. package/dist/badge.d.ts +3 -0
  15. package/dist/badge.js +4 -0
  16. package/dist/breadcrumb.d.ts +10 -0
  17. package/dist/breadcrumb.js +9 -0
  18. package/dist/button-group.d.ts +6 -0
  19. package/dist/button-group.js +4 -0
  20. package/dist/button.d.ts +3 -0
  21. package/dist/button.js +4 -0
  22. package/dist/card.d.ts +3 -0
  23. package/dist/card.js +4 -0
  24. package/dist/carousel.d.ts +11 -0
  25. package/dist/carousel.js +6 -0
  26. package/dist/checkbox.d.ts +3 -0
  27. package/dist/checkbox.js +4 -0
  28. package/dist/combobox.d.ts +9 -0
  29. package/dist/combobox.js +6 -0
  30. package/dist/command-bar.d.ts +6 -0
  31. package/dist/command-bar.js +4 -0
  32. package/dist/copy-button.d.ts +7 -0
  33. package/dist/copy-button.js +19 -0
  34. package/dist/data-grid.d.ts +25 -0
  35. package/dist/data-grid.js +39 -0
  36. package/dist/date-range-picker.d.ts +12 -0
  37. package/dist/date-range-picker.js +4 -0
  38. package/dist/dialog.d.ts +9 -0
  39. package/dist/dialog.js +21 -0
  40. package/dist/divider.d.ts +3 -0
  41. package/dist/divider.js +4 -0
  42. package/dist/drawer.d.ts +6 -0
  43. package/dist/drawer.js +5 -0
  44. package/dist/empty-state.d.ts +7 -0
  45. package/dist/empty-state.js +5 -0
  46. package/dist/field.d.ts +15 -0
  47. package/dist/field.js +11 -0
  48. package/dist/file-dropzone.d.ts +15 -0
  49. package/dist/file-dropzone.js +21 -0
  50. package/dist/filter-bar.d.ts +9 -0
  51. package/dist/filter-bar.js +9 -0
  52. package/dist/image.d.ts +5 -0
  53. package/dist/image.js +4 -0
  54. package/dist/index.d.ts +68 -78
  55. package/dist/index.js +60 -68
  56. package/dist/input.d.ts +3 -0
  57. package/dist/input.js +4 -0
  58. package/dist/label.d.ts +3 -0
  59. package/dist/label.js +4 -0
  60. package/dist/link.d.ts +3 -0
  61. package/dist/link.js +9 -0
  62. package/dist/list-view.d.ts +29 -0
  63. package/dist/list-view.js +14 -0
  64. package/dist/list.d.ts +6 -0
  65. package/dist/list.js +5 -0
  66. package/dist/lookup-picker.d.ts +12 -0
  67. package/dist/lookup-picker.js +4 -0
  68. package/dist/menu.d.ts +6 -0
  69. package/dist/menu.js +4 -0
  70. package/dist/nav.d.ts +12 -0
  71. package/dist/nav.js +4 -0
  72. package/dist/pagination.d.ts +11 -0
  73. package/dist/pagination.js +5 -0
  74. package/dist/person-picker.d.ts +13 -0
  75. package/dist/person-picker.js +4 -0
  76. package/dist/persona.d.ts +7 -0
  77. package/dist/persona.js +5 -0
  78. package/dist/popover.d.ts +6 -0
  79. package/dist/popover.js +4 -0
  80. package/dist/progress-bar.d.ts +5 -0
  81. package/dist/progress-bar.js +5 -0
  82. package/dist/radio-group.d.ts +6 -0
  83. package/dist/radio-group.js +4 -0
  84. package/dist/radio.d.ts +3 -0
  85. package/dist/radio.js +4 -0
  86. package/dist/rating.d.ts +8 -0
  87. package/dist/rating.js +5 -0
  88. package/dist/react.d.ts +1 -4715
  89. package/dist/react.js +1 -7097
  90. package/dist/select.d.ts +3 -0
  91. package/dist/select.js +4 -0
  92. package/dist/sharepoint-field.d.ts +18 -0
  93. package/dist/sharepoint-field.js +54 -0
  94. package/dist/skeleton.d.ts +6 -0
  95. package/dist/skeleton.js +9 -0
  96. package/dist/spinner.d.ts +5 -0
  97. package/dist/spinner.js +4 -0
  98. package/dist/swatch-picker.d.ts +17 -0
  99. package/dist/swatch-picker.js +11 -0
  100. package/dist/switch.d.ts +3 -0
  101. package/dist/switch.js +4 -0
  102. package/dist/table.d.ts +6 -0
  103. package/dist/table.js +4 -0
  104. package/dist/tabs.d.ts +14 -0
  105. package/dist/tabs.js +30 -0
  106. package/dist/tag-group.d.ts +6 -0
  107. package/dist/tag-group.js +4 -0
  108. package/dist/tag-picker.d.ts +12 -0
  109. package/dist/tag-picker.js +4 -0
  110. package/dist/tag.d.ts +7 -0
  111. package/dist/tag.js +5 -0
  112. package/dist/text.d.ts +5 -0
  113. package/dist/text.js +4 -0
  114. package/dist/textarea.d.ts +3 -0
  115. package/dist/textarea.js +4 -0
  116. package/dist/theme.d.ts +15 -175
  117. package/dist/theme.js +36 -239
  118. package/dist/toast.d.ts +9 -0
  119. package/dist/toast.js +12 -0
  120. package/dist/tree.d.ts +13 -0
  121. package/dist/tree.js +9 -0
  122. package/dist/view-card.d.ts +8 -0
  123. package/dist/view-card.js +4 -0
  124. package/dist/view-switcher.d.ts +13 -0
  125. package/dist/view-switcher.js +7 -0
  126. package/dist/virtual-list.d.ts +11 -0
  127. package/dist/virtual-list.js +25 -0
  128. package/measurements.json +365 -0
  129. package/package.json +14 -46
  130. package/src/theme.css +125 -0
  131. package/AGENTS.md +0 -231
  132. package/dist/appearance.d.ts +0 -6
  133. package/dist/appearance.js +0 -217
  134. package/dist/elements/accordion.d.ts +0 -9
  135. package/dist/elements/accordion.js +0 -338
  136. package/dist/elements/alert.d.ts +0 -2
  137. package/dist/elements/alert.js +0 -86
  138. package/dist/elements/avatar-group-primitives.d.ts +0 -3
  139. package/dist/elements/avatar-group-primitives.js +0 -189
  140. package/dist/elements/avatar-group.d.ts +0 -2
  141. package/dist/elements/avatar-group.js +0 -110
  142. package/dist/elements/avatar.d.ts +0 -4
  143. package/dist/elements/avatar.js +0 -251
  144. package/dist/elements/badge.d.ts +0 -8
  145. package/dist/elements/badge.js +0 -182
  146. package/dist/elements/breadcrumb.d.ts +0 -4
  147. package/dist/elements/breadcrumb.js +0 -233
  148. package/dist/elements/button-group.d.ts +0 -1
  149. package/dist/elements/button-group.js +0 -20
  150. package/dist/elements/button-variants.d.ts +0 -6
  151. package/dist/elements/button-variants.js +0 -339
  152. package/dist/elements/button.d.ts +0 -5
  153. package/dist/elements/button.js +0 -233
  154. package/dist/elements/card-primitives.d.ts +0 -3
  155. package/dist/elements/card-primitives.js +0 -83
  156. package/dist/elements/card.d.ts +0 -5
  157. package/dist/elements/card.js +0 -213
  158. package/dist/elements/carousel.d.ts +0 -92
  159. package/dist/elements/carousel.js +0 -1115
  160. package/dist/elements/color-picker.d.ts +0 -24
  161. package/dist/elements/color-picker.js +0 -1004
  162. package/dist/elements/combobox.d.ts +0 -1
  163. package/dist/elements/combobox.js +0 -1012
  164. package/dist/elements/data-grid-model.d.ts +0 -120
  165. package/dist/elements/data-grid-model.js +0 -294
  166. package/dist/elements/data-grid.d.ts +0 -30
  167. package/dist/elements/data-grid.js +0 -1576
  168. package/dist/elements/date-picker.d.ts +0 -1
  169. package/dist/elements/date-picker.js +0 -486
  170. package/dist/elements/dialog.d.ts +0 -23
  171. package/dist/elements/dialog.js +0 -409
  172. package/dist/elements/divider.d.ts +0 -2
  173. package/dist/elements/divider.js +0 -66
  174. package/dist/elements/drawer.d.ts +0 -3
  175. package/dist/elements/drawer.js +0 -308
  176. package/dist/elements/field-composition.d.ts +0 -26
  177. package/dist/elements/field-composition.js +0 -133
  178. package/dist/elements/field.d.ts +0 -1
  179. package/dist/elements/field.js +0 -67
  180. package/dist/elements/file-upload.d.ts +0 -6
  181. package/dist/elements/file-upload.js +0 -211
  182. package/dist/elements/floating-position.d.ts +0 -36
  183. package/dist/elements/floating-position.js +0 -92
  184. package/dist/elements/image.d.ts +0 -28
  185. package/dist/elements/image.js +0 -38
  186. package/dist/elements/info.d.ts +0 -9
  187. package/dist/elements/info.js +0 -102
  188. package/dist/elements/input.d.ts +0 -5
  189. package/dist/elements/input.js +0 -255
  190. package/dist/elements/label.d.ts +0 -15
  191. package/dist/elements/label.js +0 -56
  192. package/dist/elements/link.d.ts +0 -1
  193. package/dist/elements/link.js +0 -92
  194. package/dist/elements/list.d.ts +0 -19
  195. package/dist/elements/list.js +0 -297
  196. package/dist/elements/menu.d.ts +0 -33
  197. package/dist/elements/menu.js +0 -1103
  198. package/dist/elements/message-bar.d.ts +0 -10
  199. package/dist/elements/message-bar.js +0 -327
  200. package/dist/elements/nav.d.ts +0 -32
  201. package/dist/elements/nav.js +0 -794
  202. package/dist/elements/overflow.d.ts +0 -10
  203. package/dist/elements/overflow.js +0 -218
  204. package/dist/elements/persona.d.ts +0 -3
  205. package/dist/elements/persona.js +0 -125
  206. package/dist/elements/popover.d.ts +0 -77
  207. package/dist/elements/popover.js +0 -840
  208. package/dist/elements/progress-bar.d.ts +0 -4
  209. package/dist/elements/progress-bar.js +0 -82
  210. package/dist/elements/radio-group.d.ts +0 -1
  211. package/dist/elements/radio-group.js +0 -303
  212. package/dist/elements/rating-display.d.ts +0 -2
  213. package/dist/elements/rating-display.js +0 -125
  214. package/dist/elements/rating.d.ts +0 -2
  215. package/dist/elements/rating.js +0 -206
  216. package/dist/elements/rich-text-editor.d.ts +0 -32
  217. package/dist/elements/rich-text-editor.js +0 -1367
  218. package/dist/elements/search.d.ts +0 -3
  219. package/dist/elements/search.js +0 -200
  220. package/dist/elements/select.d.ts +0 -6
  221. package/dist/elements/select.js +0 -291
  222. package/dist/elements/shared.d.ts +0 -22
  223. package/dist/elements/shared.js +0 -409
  224. package/dist/elements/sharepoint-field-basic-controls.d.ts +0 -1
  225. package/dist/elements/sharepoint-field-basic-controls.js +0 -14
  226. package/dist/elements/sharepoint-fields.d.ts +0 -29
  227. package/dist/elements/sharepoint-fields.js +0 -460
  228. package/dist/elements/skeleton.d.ts +0 -6
  229. package/dist/elements/skeleton.js +0 -91
  230. package/dist/elements/slider.d.ts +0 -1
  231. package/dist/elements/slider.js +0 -248
  232. package/dist/elements/spin-button.d.ts +0 -7
  233. package/dist/elements/spin-button.js +0 -499
  234. package/dist/elements/spinner.d.ts +0 -4
  235. package/dist/elements/spinner.js +0 -124
  236. package/dist/elements/swatch-picker.d.ts +0 -5
  237. package/dist/elements/swatch-picker.js +0 -363
  238. package/dist/elements/table.d.ts +0 -21
  239. package/dist/elements/table.js +0 -261
  240. package/dist/elements/tabs.d.ts +0 -1
  241. package/dist/elements/tabs.js +0 -276
  242. package/dist/elements/tag-picker.d.ts +0 -29
  243. package/dist/elements/tag-picker.js +0 -715
  244. package/dist/elements/tags.d.ts +0 -21
  245. package/dist/elements/tags.js +0 -421
  246. package/dist/elements/teaching-popover.d.ts +0 -32
  247. package/dist/elements/teaching-popover.js +0 -412
  248. package/dist/elements/text.d.ts +0 -9
  249. package/dist/elements/text.js +0 -75
  250. package/dist/elements/textarea.d.ts +0 -1
  251. package/dist/elements/textarea.js +0 -182
  252. package/dist/elements/toast.d.ts +0 -25
  253. package/dist/elements/toast.js +0 -337
  254. package/dist/elements/toaster.d.ts +0 -6
  255. package/dist/elements/toaster.js +0 -357
  256. package/dist/elements/toggle.d.ts +0 -2
  257. package/dist/elements/toggle.js +0 -246
  258. package/dist/elements/toolbar.d.ts +0 -12
  259. package/dist/elements/toolbar.js +0 -381
  260. package/dist/elements/tooltip.d.ts +0 -76
  261. package/dist/elements/tooltip.js +0 -527
  262. package/dist/elements/tree-model.d.ts +0 -19
  263. package/dist/elements/tree-model.js +0 -34
  264. package/dist/elements/tree.d.ts +0 -5
  265. package/dist/elements/tree.js +0 -273
  266. package/dist/elements/view-card.d.ts +0 -2
  267. package/dist/elements/view-card.js +0 -78
  268. package/dist/elements/virtual-list.d.ts +0 -41
  269. package/dist/elements/virtual-list.js +0 -369
  270. package/dist/elements.d.ts +0 -73
  271. package/dist/elements.js +0 -210
  272. package/dist/sharepoint-fields.d.ts +0 -135
  273. package/dist/sharepoint-fields.js +0 -402
  274. package/dist/virtualizer-hooks.d.ts +0 -80
  275. package/dist/virtualizer-hooks.js +0 -253
  276. package/scripts/generate-token-index.mjs +0 -43
  277. package/scripts/sync-component-catalog-signatures.mjs +0 -55
  278. package/scripts/validate-component-catalog.mjs +0 -44
  279. package/skills/sharepoint-ui/SKILL.md +0 -12
  280. package/skills/sharepoint-ui/references/choosing-components.md +0 -20
  281. package/skills/sharepoint-ui/references/sharepoint-patterns.md +0 -17
  282. package/tokens.json +0 -3777
package/AGENTS.md DELETED
@@ -1,231 +0,0 @@
1
- # Agent guide for SharePoint UI
2
-
3
- Use this page as the short route from a SharePoint task to a working component. The library is designed for SPFx 1.24 and React 18, but its custom elements can be used without React.
4
-
5
- For the predictable cross-component pattern and copy-ready Tooltip/Persona composition, start with [`QUICKSTART.md`](./QUICKSTART.md). Component pages and their linked JSON slices supply the exact component-specific props, slots, and callback signatures.
6
-
7
- ## Pick the smallest entry point
8
-
9
- - In an SPFx React web part, import the React adapter from `@spfx-fast/sharepoint-ui/react` and import only the components used.
10
- - In plain HTML or another framework, import a leaf definition such as `@spfx-fast/sharepoint-ui/elements/button`, then call `defineButton()` once. For native form labels, use `createLabel()` from `@spfx-fast/sharepoint-ui/elements/label`; it returns a real `<label>` so `htmlFor`/`for` association works.
11
- - `defineSharePointUIElements()` registers the full current catalog; use it only when the whole catalog is needed.
12
- - Set `brandColor` in `SharePointUIProvider` theme data to derive matching accent, focus, selection, surface, hover, and border tokens. Explicit design tokens override the derived values; the theme API also has a framework-free `@spfx-fast/sharepoint-ui/theme` entry point.
13
-
14
- For temporary feedback, mount one `Toaster` near the app root and dispatch from descendants with `useToastController()`. For a controller outside the Toaster subtree or when an app has multiple toast regions, set `toasterId` on the target Toaster and pass the same ID to `useToastController(toasterId)`. The controller supports both `updateToast(id, update)` and Fluent's `updateToast({ toastId, content, ...options })` form. Use a permanent status surface as well when the information must remain available after the toast expires.
15
-
16
- ```tsx
17
- import { Button, Toaster, useToastController } from "@spfx-fast/sharepoint-ui/react";
18
-
19
- function SaveButton() {
20
- const { dispatchToast } = useToastController();
21
- return <Button onClick={() => dispatchToast("Your changes are available.", { title: "Saved", intent: "success" })}>Save</Button>;
22
- }
23
-
24
- <Toaster><SaveButton /></Toaster>
25
- ```
26
-
27
- For framework-neutral use, import `defineToaster` from `@spfx-fast/sharepoint-ui/elements/toaster`, call it once, then use `toaster.show({ title, body, intent })`; retain the returned ID for `update(id, options)`, `dismiss(id)`, `pause(id)`, and `play(id)`. Toasts default to a 3-second timeout and continue timing while hovered or while the browser window is unfocused; `timeout: 0` or `timeout: -1` keeps a toast open until it is dismissed. The React controller exposes `pauseToast(id)` and `playToast(id)`. Focusing any toast pauses the region's timers, ArrowUp/ArrowDown moves through visible toasts, Delete dismisses the focused toast, and Escape dismisses the focused or newest toast. Keep important notifications on a persistent surface after the toast closes.
28
-
29
- The Toaster provides one polite and one assertive live region and serializes announcements; assertive messages take priority over pending polite messages. New announcements are ignored while focus is inside the Toaster. Individual Toast elements are groups, not live regions.
30
-
31
- Compose a nested `Menu` inside its parent menu's children and use a `MenuItem` as its trigger. The parent treats that trigger as one item; the forward arrow opens the child (Right in LTR, Left in RTL), and the backward arrow closes it and restores focus to its trigger. Selecting a child command closes the menu hierarchy.
32
-
33
- For desktop-style command navigation, place top-level `Menu` components inside `MenuBar` and use `MenuBarItem` as each trigger. MenuBar handles one Tab stop, Home/End, LTR/RTL horizontal focus, and closes sibling menus when another opens. `Menu` handles vertical menu navigation, nested submenus, Escape, and command selection.
34
-
35
- Use `ContextMenu` for commands attached to a surface: right-click its trigger, or focus the trigger and press the Context Menu key or Shift+F10. It opens at the pointer or target edge, moves focus into the menu, and restores focus on Escape. Plain HTML and other framework consumers can add the `context-menu` attribute to `spui-menu`.
36
-
37
- Use `SearchBox` for a free-form query, not `ComboBox` when users are choosing from suggestions. It supports controlled `value` or initial `defaultValue`, `onValueChange`, Enter submission through `onSearch`, and a localized clear button. The custom element participates in form value/reset and required validation.
38
-
39
- Use `Nav` for app destinations: `NavItem` and `NavSubItem` preserve native anchor navigation when given `href`; `NavCategoryItem` only expands/collapses its category. Any of these accepts an `Avatar` through `avatar={<Avatar name="Team" image={photoUrl} size={28} />}`; the React adapter marks it decorative so the item text stays the accessible name. Direct custom elements use `slot="avatar"` and should mark the Avatar `decorative`. Keep destination activation and category disclosure separate. `selectedValue`/`openCategories` are controlled; use `defaultSelectedValue`/`defaultOpenCategories` for initial state. `multiple` allows several categories open. Items follow normal Tab and native Enter/Space behavior. At 640px and below Nav becomes a 44px menu button with a theme-derived drawer at the logical start; Escape, outside click, and destination selection dismiss it. Use controlled `open`/`onOpenChange` or `defaultOpen`, and localize `openLabel`/`closeLabel` or provider strings `navigationMenu`/`navigationClose`. Set the provider `lang`; it derives LTR/RTL, and an explicit `dir` overrides that choice. The desktop rail defaults to 260px (`--spui-nav-width`).
40
-
41
- For a Fluent-shaped application shell, compose `NavDrawer` with `NavDrawerHeader`, `NavDrawerBody`, and `NavDrawerFooter`; choose `type="overlay"` or `type="inline"`. Its default `tabbable={false}` follows roving arrow navigation; opt into `tabbable` when each destination should be a Tab stop. Use `selectedCategoryValue`/`defaultSelectedCategoryValue` for the selected category and the Fluent callback names `onNavItemSelect`/`onNavCategoryItemToggle` when sharing code with Fluent. `AppItem` is a destination, `AppItemStatic` is decorative identity text, and `SplitNavItem` keeps its primary destination independent from optional action, menu, and toggle buttons; add matching tooltip slots for accessible labels. Connect `Hamburger.onClick` to the owning controlled `open` state.
42
-
43
- Use `Drawer` for focused side-sheet or full-screen work. Opt into the header expansion control with `expandable`; use controlled `expanded`/`onExpandedChange` when the app owns state. Localize it with `expandLabel`/`restoreLabel` or provider strings `drawerExpand`/`drawerRestore`. For direct custom elements, set `expandable` and listen for `spui-expanded-change`; the control is hidden by default. The surface inherits `dir` and uses logical placement for `start`/`end`.
44
-
45
- Compose `DrawerHeader`, `DrawerHeaderNavigation`, `DrawerHeaderTitle`, `DrawerBody`, and `DrawerFooter` inside `OverlayDrawer` or `Drawer`. `DrawerHeaderTitle` can provide a separate `action`; the drawer engine keeps the heading and popup semantics. `OverlayDrawer` defaults to a modal start-side small surface and supports `modalType` and `defaultOpen`. React `Drawer` accepts `surfaceMotion` and `backdropMotion` components; exiting top-layer content stays inert until its animations finish. Ensure custom motion respects reduced-motion preferences. Use `InlineDrawer` for non-modal in-flow content, or `Drawer type="inline"`; it does not trap focus. For direct elements, `spui-inline-drawer` accepts `title`, default body content, `slot="header-navigation"`, `slot="header-action"`, and `slot="actions"`.
46
-
47
- Use `Input` for a bare form-associated input when a label/layout is composed separately. Supply `aria-label` or `aria-labelledby` for its accessible name; because the native control lives in a shadow root, an outside `<label htmlFor>` cannot label the inner input. Use `TextField` when the library should render the visible, associated label. `Input` supports native input types, controlled `value` or initial `defaultValue`, `onValueChange`, `name`, `required`, reset, disabled fieldsets, size/appearance, and `before`/`after` slots.
48
-
49
- Use `Text` or its named typography presets (`Body1`, `Title1`, `Caption1Strong`, and others) for token-based copy. `Text` defaults to Fluent size `300`; outside React, set `size` and `weight` on `<spui-text>` to use the same typography engine.
50
-
51
- Use React `Field` to compose one form control with a visible label, hint, required/disabled state, and `error`/`warning`/`success` validation feedback. For framework-neutral code, pass the native or custom form control to `createFormField(control, options)`; it returns a light-DOM wrapper and connects the label, helper text, and validation message through stable IDs and ARIA references. The control remains responsible for native form submission and validity.
52
-
53
- Use `FileUpload` for file selection UI in forms, then send the emitted `File[]` to the app's upload action. It supports `accept`, `multiple`, `maxFiles`, `maxFileSize` (bytes), `disabled`, `required`, and localized labels; use `onFilesChange` and `onFilesError` rather than inspecting shadow DOM. Native forms receive selected files under the host's `name`; the component does not upload or persist them to SharePoint.
54
-
55
- Use `Button` for one action; choose `appearance="primary" | "secondary" | "outline" | "subtle" | "transparent"`, `size="small" | "medium" | "large"`, and `shape="rounded" | "circular" | "square"` without replacing the element. Use `MenuButton` as a `Menu` trigger when the trigger should include a chevron; `Menu` owns popup state and focus behavior, and `menuIcon` can replace the default chevron. Use `CompoundButton` when an action needs a title, short description, and optional decorative `icon`. Use `ToggleButton` for a binary pressed state (`pressed` + `onPressedChange`, or `defaultPressed`). Use `SplitButton` when a primary action has related alternatives: its `menuItems` should be `MenuItem` elements and its `menuLabel` should name the menu trigger.
56
-
57
- ```tsx
58
- import { CompoundButton, Menu, MenuButton, MenuItem, SplitButton, ToggleButton } from "@spfx-fast/sharepoint-ui/react";
59
-
60
- <CompoundButton secondaryContent="Includes the latest approved changes" onClick={openSummary}>Review summary</CompoundButton>
61
- <ToggleButton pressed={pinned} onPressedChange={setPinned}>Pin item</ToggleButton>
62
- <Menu trigger={<MenuButton>More actions</MenuButton>} label="More actions"><MenuItem value="copy">Copy link</MenuItem></Menu>
63
- <SplitButton menuLabel="More publish options" onClick={publishNow}
64
- menuItems={<><MenuItem onSelect={saveDraft}>Save draft</MenuItem><MenuItem onSelect={schedule}>Schedule</MenuItem></>}>Publish</SplitButton>
65
- ```
66
-
67
- For framework-neutral use, import the button definitions from `@spfx-fast/sharepoint-ui/elements/button` and `@spfx-fast/sharepoint-ui/elements/button-variants`; `MenuButton` composes directly in `spui-menu`'s `trigger` slot, while `SplitButton` also needs `defineMenu`, `defineMenuItem`, and related item definitions from `@spfx-fast/sharepoint-ui/elements/menu`. `spui-toggle-button` emits `spui-pressed-change` with `{ pressed }`; `spui-split-button` is the labelled group host for a `spui-button` in its `primary` slot and a `spui-menu` in its `menu` slot. Supply the menu trigger in `spui-menu`'s `trigger` slot and menu items in its `menu` slot. In custom-element composition, set `disabled` on both the primary button and menu trigger.
68
-
69
- ## Build with existing pieces
70
-
71
- ```tsx
72
- import { SharePointUIProvider, SharePointCard, ViewCard, Button, TextField } from "@spfx-fast/sharepoint-ui/react";
73
-
74
- <SharePointUIProvider lang="en-GB" dir="ltr" appearance="fluent">
75
- <SharePointCard>
76
- <ViewCard kind="task" title={task.title} subtitle={task.dueLabel}>
77
- <Button onClick={() => openTask(task.id)}>Open task</Button>
78
- </ViewCard>
79
- <TextField label="Search tasks" value={query} onValueChange={setQuery} />
80
- </SharePointCard>
81
- </SharePointUIProvider>
82
- ```
83
-
84
- Use `Card` for the themed surface and compose `CardHeader`, `CardPreview`, and `CardFooter` for reusable document, media, person, or task views. `Card` supports `appearance="filled | filled-alternative | outline | subtle"`, `orientation="vertical | horizontal"`, and `size="small | medium | large"`; `SharePointCard` remains a compatibility alias. Header slots are `image`, `header`, `description`, and logical-end `action`; Preview accepts media or custom content plus an optional `logo`; Footer accepts children plus an optional logical-end `action`.
85
-
86
- Use `Carousel` to present a small, curated sequence of existing cards or views. Give it a meaningful `label`; each direct child is a slide, so reuse `ViewCard` instead of rebuilding record markup. It uses native touch scrolling and scroll snapping, 44px previous/next and slide controls, keyboard arrows, reduced-motion behavior, RTL-aware direction, localized provider strings, and optional `wrap`. Prefer manual navigation for SharePoint task/document content; autoplay is intentionally not enabled by default. Listen to `onIndexChange` or control it with `selectedIndex`.
87
-
88
- ```tsx
89
- import { Carousel, ViewCard, Badge } from "@spfx-fast/sharepoint-ui/react";
90
-
91
- <Carousel label="Recently updated documents" onIndexChange={setActiveDocument}>
92
- {documents.map(document => <ViewCard key={document.id} kind="document" title={document.name} subtitle={document.modifiedLabel}>
93
- <Badge slot="status">{document.status}</Badge>
94
- </ViewCard>)}
95
- </Carousel>
96
- ```
97
-
98
- For framework-neutral use, call `defineCarousel()` from `@spfx-fast/sharepoint-ui/elements/carousel`; compose direct child slide elements, set `aria-label`, and listen for `spui-carousel-change` with `{ value: index }`.
99
-
100
- Use `Overflow` for responsive command strips. Keep essential actions `pinned`, give other actions a stable `id` and relative `priority` (higher stays visible longer), and supply an equivalent `MenuItem` through `overflowContent`. Set that MenuItem's `value` to the same item ID to receive it through `onOverflowItemSelect`. `OverflowDivider` hides when its `groupId` has no visible items. `minimumVisible`, `overflowDirection`, and `padding` control the width policy. The native engine observes size and item-list changes; it does not clone or reparent interactive controls. For plain elements, provide your own trigger/menu in the `overflow-trigger` slot and use `spui-overflow-change` (`visibleIds`, `overflowIds`) to render its contents.
101
-
102
- ```tsx
103
- import { Button, MenuItem, Overflow, OverflowDivider, OverflowItem } from "@spfx-fast/sharepoint-ui/react";
104
-
105
- <Overflow label="Page actions" onOverflowItemSelect={runAction} minimumVisible={1}>
106
- <OverflowItem id="save" pinned overflowContent={<MenuItem value="save">Save</MenuItem>}>
107
- <Button appearance="primary" onClick={() => runAction("save")}>Save</Button>
108
- </OverflowItem>
109
- <OverflowDivider groupId="secondary" />
110
- <OverflowItem id="export" priority={10} groupId="secondary" overflowContent={<MenuItem value="export">Export</MenuItem>}>
111
- <Button onClick={() => runAction("export")}>Export</Button>
112
- </OverflowItem>
113
- </Overflow>
114
- ```
115
-
116
- For a selectable brand palette, use `SwatchPicker` with descriptive names and stable values. Keep arrow-key focus as the default for large palettes; set `focusMode="tab"` only when each choice should get a separate Tab stop. Use `ImageSwatch` for texture or material options and `EmptySwatch` to open a color editor.
117
-
118
- ```tsx
119
- import { ColorSwatch, EmptySwatch, SwatchPicker } from "@spfx-fast/sharepoint-ui/react";
120
-
121
- <SwatchPicker aria-label="Workspace colors" selectedValue={selectedColor} columns={4} onSelectionChange={setSelection}>
122
- <ColorSwatch value="brand" color="#007780" aria-label="Brand teal" />
123
- <ColorSwatch value="blue" color="#005A9E" aria-label="Blue" />
124
- <ColorSwatch value="locked" color="#777777" aria-label="Managed color" disabled />
125
- <EmptySwatch aria-label="Add a color" onAdd={openColorPicker} />
126
- </SwatchPicker>
127
- ```
128
-
129
- ```tsx
130
- import { Badge, Button, Card, CardFooter, CardHeader, CardPreview, Link } from "@spfx-fast/sharepoint-ui/react";
131
-
132
- <Card aria-label="Quarterly planning document">
133
- <CardHeader image={<span aria-label="Word document">DOCX</span>} header="Quarterly planning.docx" description="Modified yesterday · Shared Documents" action={<Badge>Shared</Badge>} />
134
- <CardPreview logo={<span>DOCX</span>}><img src={previewUrl} alt="First page of the quarterly planning document" /></CardPreview>
135
- <CardFooter action={<Button appearance="secondary">Share</Button>}><Link href={libraryUrl}>Open library</Link></CardFooter>
136
- </Card>
137
- ```
138
-
139
- For plain HTML or another framework, call `defineCard()` from `@spfx-fast/sharepoint-ui/elements/card` and `defineCardHeader()`, `defineCardPreview()`, and `defineCardFooter()` from `@spfx-fast/sharepoint-ui/elements/card-primitives`; compose the matching `spui-card-*` tags and named slots. Import only the elements the view uses.
140
-
141
- Use `Avatar` for a standalone image or initials marker. Use `Persona` when the identity also needs primary/secondary/tertiary text and presence; set `presenceLabel` to localize the status name and use the `avatar` slot to replace its default marker. Place it in `ViewCard`'s `leading` slot only when the card layout has room for its identity text. Use `Link` for navigation, `Divider` to separate content groups, and `Accordion`/`AccordionItem` for progressive disclosure. For document, person, task, approval, and list-item cards, extend `ViewCard` with its named slots rather than cloning its markup. Keep SharePoint fetching, permissions, and writes in an SPFx service; controls emit values and do not silently persist data.
142
-
143
- For a people stack with named overflow details, compose `AvatarGroupItem` children and an `AvatarGroupPopover` inside `AvatarGroup`. The popover count defaults to its child count; pass `count` when children are hidden in fragments. `indicator="icon"` replaces the numeric trigger, and `onOpenChange` supports controlled app state. `AvatarGroupItem` takes the same identity inputs as `Avatar` and inherits `AvatarGroup.size` unless it has its own size. The popup uses the library Popover, localized `label`/`triggerLabel`, Escape dismissal, outside dismissal, and logical placement. Framework-neutral consumers can import `defineAvatarGroup` from `@spfx-fast/sharepoint-ui/elements/avatar-group` and `defineAvatarGroupItem`/`defineAvatarGroupPopover` from `@spfx-fast/sharepoint-ui/elements/avatar-group-primitives`; use the `spui-avatar-group-item` and `spui-avatar-group-popover` tags as direct children of `spui-avatar-group`.
144
-
145
- ## Customize without forking
146
-
147
- - Use `SharePointUIProvider` for locale, direction, appearance, strings, and theme-token overrides.
148
- - Keep the React provider at the app or page root: it renders one `display: contents` theme wrapper so all tokens inherit through DOM and shadow boundaries. Avoid placing it inside slot-driven custom-element composition; per-component inline styles still override inherited tokens.
149
- - React refs are supported on 157 catalogued adapters and target the rendered root element (normally the custom-element host; `InfoLabel` targets its `span`; standalone `Option`/`OptionGroup` render a `div` outside a ComboBox). `Input` and `TextArea` refs also expose `focus()` and `select()`. `npm run verify:react-refs` checks every ref-enabled catalog entry in Chromium.
150
- - Override CSS variables for broad design changes and `::part(...)` for one component detail. Fonts inherit through `--spui-font-family`.
151
- - In tests, prefer role/name queries and stable user-visible semantics. Add `data-testid` to the component host only when semantics are insufficient; custom elements accept it directly, and React adapters forward it to the host. Avoid selectors based on shadow DOM internals or generated IDs.
152
- - Set `lang` for content language; `SharePointUIProvider` derives `dir` from it. Set `dir` only when the region's direction should override its language. Supply localized provider strings for every built-in label used by the page.
153
- - Use the `fluent`, `ios`, `windows`, or `linux` base appearance. The no-host-theme accent fallback is stone (`DEFAULT_ACCENT`, `#78716c`); SharePoint semantic theme colors override it when provided. Accent hover, focus fallback, and glass tint derive from the active accent. `glass` derives surface and spacing from its selected base appearance and theme.
154
-
155
- ## Data and rich text
156
-
157
- - For metadata-driven React forms, render `<SharePointField field={field} value={values[name]} onValueChange={...} options={lookupOptions[name]} />` from `@spfx-fast/sharepoint-ui/react`. It selects the matching control, applies accessible field labels and constraints, and supports rich-text Notes. The host supplies Lookup/User options from an authorized data source; built-in Choice values come from SharePoint metadata. For custom layouts, use `getSharePointFieldAdapter(field)` and render its recipe explicitly. Use `serializeSharePointFieldValues(fields, valuesByInternalName)` for one PATCH body: omitted values stay untouched, `null` clears optional fields, and invalid or unknown values throw. It validates supplied values only; new-item forms must include every required value. `getSharePointListItemUrl(webUrl, listId, itemId)` constructs the item endpoint; the host still owns authorization, ETag, and REST entity-type metadata. The adapter's `required`, `readOnly`, `step`, `min`, `max`, and `maxLength` metadata should drive the UI. Generic and focused serializers reject read-only or empty required values and enforce configured numeric precision and bounds. URL recipes expose `urlFormat` (`hyperlink` or `picture`) for record views. Use `getSharePointNextPageUrl(webUrl, nextLink)` to safely follow same-origin OData pages. `getSharePointLookupOptionsUrl(field, sourceWebUrl)` builds lookup requests. These helpers validate and shape data but do not send requests or replace SharePoint permissions and server validation; people search remains tenant-authorized app work.
158
-
159
- - Forward SharePoint adapter `min`, `max`, and `maxLength` to matching controls when present. The serializers enforce those numeric bounds and text limits too; SharePoint remains the final validator.
160
- - Use `ColorPicker` when users need a theme-aware color choice. Its `value` is `#RRGGBB`, `alpha` is `0`–`1`, and `onValueChange(value, alpha)` receives both values. Pass `colors` for app-specific swatches; tenant accent, success, warning, and danger swatches are included by default. The element submits `#RRGGBB` or `#RRGGBBAA` as a native form value.
161
-
162
- - Use `Table` for compact composable rows and `DataGrid` for virtualized/editable data. Compose `TableHeader`, `TableBody`, `TableRow`, `TableHeaderCell`, and `TableCell`; use `TableSelectionCell` for selection, `TableCellLayout`/`TableCellActions` for concise record content, `TableResizeHandle` for consumer-controlled width, and `createTableColumn` for reusable column definitions. Sorting and resizing emit requests: keep that state in the host and pass it back through `sortDirection` or `value`.
163
- - For Fluent-shaped custom React tables, compose `useTableFeatures(options, plugins)` with `useTableSelection`, `useTableSort`, and `useTableColumnSizing_unstable`; keep the plugin order stable between renders because each plugin runs React hooks. `TableFeaturePlugin` remains generic so item types flow from `useTableFeatures` into the plugin pipeline.
164
- - Composed DataGrid descendants can select shared state with `useDataGridContext_unstable(selector)`. Its context extends Fluent's `TableFeaturesState` contract with `getRows`, `selection`, `sort`, `tableRef`, and `columnSizing_unstable`, plus SharePoint data-source and editing state. Indexed sources expose currently loaded rows; selecting one field avoids rerenders when unrelated context fields change.
165
- - Use `Select` for fixed options, `ComboBox` for searchable options, `ViewCard` for repeated SharePoint records, `VirtualList` for large collections, and `DataGrid` when records need columns, sorting, or selection. React and framework-neutral VirtualList support horizontal/vertical axes and measured item sizes; set `axis="horizontal"`, `measureItems`, and a CSS `--spui-list-width` when rendering a horizontal rail. Direction follows `dir`, including normalized RTL scroll offsets. Give DataGrid typed columns and a stable `getRowId`; it automatically virtualizes above 100 display rows and windows body columns when there are more than 16 (override with `virtualize` and `virtualizeColumns`). It retains the full header and track widths for horizontal scrolling. Set `filterable` to show a labeled quick-search field over columns (mark a column `searchable: false` to exclude it), localize it with `filterLabel`, and use controlled `filterValue`/`onFilterValueChange` or initial `defaultFilterValue`. Use `selectionMode="single" | "multiple"` and `onSelectionChange` as needed. Use controlled `multiSort`/`onMultiSortChange` or initial `defaultMultiSort`; Shift+click sortable headers to add, reverse, or remove secondary keys. Localize the accessible priority phrase through `sortPriorityLabel` (use `{priority}` as its replacement token). Set `groupBy` to a column id for accessible group headings and counts; use `collapsedGroupKeys`/`onCollapsedGroupKeysChange` for controlled expansion or `defaultCollapsedGroupKeys` for initial collapsed groups. Group keys are stable JSON tuples; `getDataGridGroupKey(columnId, value)` returns one when you need to initialize collapsed state. Set `resizableColumns` for pointer resizing with keyboard arrows, Home/End, double-click auto-fit to the currently rendered rows, and RTL-aware direction. Set `reorderableColumns` for drag handles or Alt+Shift+Arrow keyboard movement; control `columnOrder` and persist `onColumnOrderChange` when layouts should survive a reload. Use `columnWidths` for controlled widths, `defaultColumnWidths` for initial widths, `minWidth`/`maxWidth` on columns for bounds, and `onColumnResize` to persist user choices. Use `getRowHeight(item, index)` for record-dependent heights; it is memoized per displayed data set and participates in virtual range lookup and arrow-key reveal. Invalid heights fall back to `rowHeight`, and values are capped at 10,000 px. Ctrl+C/Cmd+C copies the focused cell or selected rows as TSV. Paste TSV into editable cells; multi-cell paste is limited to 1,000 cells and 1,000,000 characters, validates the full range first, and requires `onCellsEdit` so the host can persist it transactionally. Local values update only after callback success. A single-cell paste can use `onCellEdit`. Cell content can be composed with a regular React `renderCell` callback.
166
- - For Fluent-style React composition, pass either `items` or an indexed `dataSource`, `createTableColumn()` definitions, and `getRowId` to `DataGrid`, then compose `DataGridHeader`/`DataGridBody`, `DataGridRow`, `DataGridHeaderCell`, and `DataGridCell`. Use the render helper's `columns` when mapping cells so custom rows follow `columnOrder`; set `reorderableColumns`, `columnOrder`, and `onColumnOrderChange` for user-managed layouts. The composed path also supports `resizableColumns`, `columnWidths`/`defaultColumnWidths`, `columnSizingOptions`, `onColumnResize`, sort state, selection, editing, and all DataGrid focus modes. `dataSource.length` counts display rows; implement `loadRange(start, end, signal)`, `getItem` or `getRow`, and optional `subscribe`; give the grid a bounded height so it can request only visible rows plus overscan. Range requests abort when the viewport moves, `onRenderedRange` reports mounted bounds, and loading failures reach `onDataLoadError` and an alert. A provider `group` row gets a built-in accessible toggle; listen for `onDataSourceGroupToggle` and update the source rows. Use `selectionModel`/`onDataSourceSelectionChange` for compact query-scoped select-all on remote data. Mark a body `DataGridCell` with `editable` and provide its current `value` plus optional `editType`; use grid-level `onCellEdit` to persist changes, `validateCellEdit` to reject invalid values, and localized `rowLabel`/`editHint` for editor announcements. Successful edits update the local cell value only after persistence succeeds; rejected edits retain the draft and announce the error.
167
- - For a framework-neutral grid, import `defineDataGrid` from `@spfx-fast/sharepoint-ui/elements/data-grid`, then assign its `items`, `columns`, `itemKey`, optional `renderCell`, `selectedKeys`, `sort` or `sorts`, `filterValue`, `resizableColumns`, `columnWidths`, `reorderableColumns`, `columnOrder`, `groupBy`, and `collapsedGroupKeys` properties. Add the `filterable` attribute and set a localized `filter-label` to show its native searchbox. Set `virtualize-columns="false"` to render every body cell in a wide grid. Listen for `spui-selection-change`, `spui-sort-change` (`detail.sort` is the primary key; `detail.sorts` contains all keys), `spui-filter-change`, `spui-column-resize`, `spui-column-order-change`, `spui-group-state-change`, `spui-cells-edit`, and `spui-cells-edit-error`; set `onCellsEdit` for atomic multi-cell paste persistence. Auto-fit resize events include `detail.autoFit === true`. Set the optional `getRowHeight` property for record-dependent heights; assign it after `items` for immediate remeasurement. Invalid heights fall back to `row-height`, and values are capped at 10,000 px. Give it a `label` or `aria-label`.
168
- - Use `Tree` for nested SharePoint navigation or folder hierarchies. Supply unique stable `id` values and `label`s; `children` may be nested. `defaultExpandedKeys` and `defaultSelectedKeys` initialize state, while `expandedKeys` and `selectedKeys` control it. Selection and expansion callbacks return key arrays; the selection callback also returns selected node records. It supports Up/Down/Home/End, expand/collapse arrows mirrored in RTL, Enter/Space, and locale-aware typeahead.
169
- - For framework-neutral Tree, import `defineTree` from `@spfx-fast/sharepoint-ui/elements/tree`, assign `items`, `expandedKeys`, and `selectedKeys` properties, and listen for `spui-tree-expanded-change` and `spui-tree-selection-change`.
170
- - Localize React defaults with `SharePointUIProvider` strings: `Tree`/`FlatTree` use `treeLabel`, `treeToggleLabel`, `treeEmptyMessage`; `Toolbar`, `Breadcrumb`, and `MenuBar` use their matching `*Label` strings; `SplitButton`, `Hamburger`, and `SplitNavItem` use `splitButtonMenuLabel`, `navigationToggle`, and `splitNavItemActionLabel`; virtualized lists use `virtualizerLabel`; row selection and resize handles use `dataGridSelectRow` and `tableResizeHandle`. Explicit labels take precedence. Direct custom elements use their `label`/`aria-label` attributes.
171
- - Use `Tag` inside a labelled `TagGroup` for filters and selected values. A dismissible React Tag calls `onDismiss(value, label)`; update the parent array to remove it. Semantic colors and `brand` appearance derive from the active SharePoint theme. For framework-neutral use, import `defineTag` and `defineTagGroup` from `@spfx-fast/sharepoint-ui/elements/tags` and listen for `spui-dismiss`.
172
- - Use `TagPicker` when users need to search and select multiple values. Supply unique stable option values, a visible label, and controlled `selectedValues`/`onChange`; disabled options cannot be selected. Its popup flips and sizes within the viewport and clipping ancestors, including SharePoint sections; set `positioning` for placement, alignment, fallback, offset, or explicit boundary control. Its `name` submits repeated values in native forms. For framework-neutral use, import `defineTagPicker` from `@spfx-fast/sharepoint-ui/elements/tag-picker`, assign the `value` array, and listen for `spui-change`.
173
- - Use `Rating` for a labelled star score. `value` is controlled; `defaultValue` initializes an uncontrolled rating. Use `step={0.5}` for half stars, set `max` for the scale, and provide `itemLabel` when the radio option labels need custom localization.
174
- - For framework-neutral `spui-virtual-list`, assign `items`, `renderItem`, and `itemKey` after importing `defineVirtualList`. Set `axis="horizontal"` or its `axis` property, `item-size`/`itemSize` for the estimated width or height, and `measure-items`/`measureItems` to track rendered sizes. `row-height` remains the vertical compatibility alias. Set `--spui-list-width` for a horizontal viewport; direction follows inherited `dir`, including logical RTL scrolling. Use `scrollToIndex(index, "start" | "center" | "end", behavior)` for programmatic navigation.
175
- - `RichTextEditor` emits allowlist-normalized HTML. Store it in a SharePoint rich-text field and render it back through the editor or another sanitizer-aware view; do not inject saved HTML with raw `innerHTML`. After inserting a table, click a cell to enable row/column insertion and deletion; table removal is available from any selected cell. Editing controls disable for merged cells so row/column changes cannot corrupt their spans. Localize the toolbar through `SharePointUIProvider.strings` (for example, `richTextInsertRowBelow` and `richTextDeleteTable`).
176
- - Pass a SharePoint multiline field's metadata as React `field={column}` so the editor derives the maximum content mode the field can store. `RichText: false` selects plain text; `RestrictedMode: true` removes enhanced rich actions; `RichTextMode: "Compatible"` selects compatible rich formatting; `"FullHtml"` selects the broad sanitized HTML mode. Explicit `contentMode` may narrow but never exceed that capability ceiling; `allowHyperlink={false}` can further disable links, but cannot override `AllowHyperlink: false`. For framework-neutral custom elements, spread `getRichTextEditorFieldOptions(column)` instead. If no restrictions are available, the editor keeps the broad sanitized HTML mode.
177
- - Connect the editor's `linkPicker` to the SPFx app's existing page/file picker; this keeps tenant-specific picker dependencies out of the UI package. Return `null` on cancel, otherwise map the selected item's absolute URL and optional label into `RichTextEditorLink`. The editor validates the URL and leaves it for review before Apply.
178
-
179
- ```tsx
180
- import { RichTextEditor, type RichTextEditorLink } from "@spfx-fast/sharepoint-ui/react";
181
-
182
- const linkPicker = async (current: RichTextEditorLink | null) => {
183
- const selected = await pageFilePicker.open({ initialUrl: current?.href });
184
- return selected ? { href: selected.url, text: selected.name, title: selected.name } : null;
185
- };
186
-
187
- <RichTextEditor value={html} onValueChange={setHtml} linkPicker={linkPicker}
188
- onLinkPickerError={error => console.error("SharePoint link picker failed", error)} />
189
- ```
190
- - Persist through `SPHttpClient` in the web part. Include loading, saved, and error states, and verify important writes by reading the item back.
191
-
192
- ## Before handing off a web part
193
-
194
- - Confirm selected imports survive the production bundler and the generated SPFx template can resolve the package.
195
- - Check keyboard use, visible focus, form submission/reset when applicable, and small-screen layout.
196
- - Check the chosen SharePoint theme, a strong background, and RTL if the page may be localized.
197
- - Report what was measured. Host page heap and timings include SharePoint work and are not component-only numbers.
198
-
199
- For a fast local bundle estimate of a catalog component, run `npm run measure:component -- AvatarGroup` from the repository root. It reports raw, gzip, and Brotli sizes plus resolved/included module counts for its custom-element, native-factory, and React slices when those APIs exist; the React runtime and SharePoint host are excluded. Add `--verbose` to see the retained modules.
200
-
201
- Coverage is still growing. Check [the current parity plan](../../investigation/FLUENT-V9-PARITY-PLAN.md) before assuming a Fluent v9 family exists.
202
-
203
- ### Compose accessible fields
204
-
205
- Use `Field` around one native or custom form control to add an associated label, hint, required indicator, and validation message. A message without an explicit `validationState` is treated as an error; success, warning, and error colors derive from the active theme. Disabled controls keep their label and helper text readable.
206
-
207
- ```tsx
208
- import { Field, Input } from "@spfx-fast/sharepoint-ui/react";
209
-
210
- <Field label="Work item" hint="Use a short, recognizable title" required>
211
- <Input name="title" value={title} onValueChange={setTitle} />
212
- </Field>
213
- ```
214
-
215
- Plain DOM and non-React consumers can compose the same structure with `createFormField(control, options)` from `@spfx-fast/sharepoint-ui/elements/field-composition`. It returns a light-DOM wrapper and associates the control with the label, hint, and validation message using native label and ARIA relationships.
216
-
217
- ### Display images
218
-
219
- Use the native-image `Image` adapter for required alternative text, responsive `srcSet`/`sizes`, lazy loading, fit, shape, border, and theme-aware shadow. Set `alt=""` only for decorative images; provide intrinsic `width` and `height` to reserve layout space.
220
-
221
- ```tsx
222
- import { Image } from "@spfx-fast/sharepoint-ui/react";
223
-
224
- <Image src={preview} srcSet={previewSrcSet} sizes="(max-width: 640px) 100vw, 640px"
225
- alt="Quarterly planning document preview" width={620} height={350}
226
- fit="cover" shape="rounded" bordered shadow />
227
- ```
228
-
229
- Framework-neutral consumers can create the equivalent `<img>` with `createImage({ src, alt, width, height, fit, shape, bordered, shadow })` from `@spfx-fast/sharepoint-ui/elements/image`. It remains a browser-native image and does not register a custom element.
230
-
231
- For framework-neutral guided tours, call `defineTeachingPopover()` from `@spfx-fast/sharepoint-ui/elements/teaching-popover` once. Compose `<spui-teaching-popover>` with `<spui-teaching-popover-trigger slot="trigger">`, `<spui-teaching-popover-surface slot="content">`, and the header/title/body/footer parts. Use `<spui-teaching-popover-carousel>` with direct `<spui-teaching-popover-carousel-card value="...">` children; its footer buttons use `nav-type="previous|next|dismiss"`, and `alt-text` supplies the label shown at the first/last edge. Listen for `spui-carousel-change` (uncontrolled) or `spui-value-change` (controlled) with `{ value, currentPage, totalPages }`. Set `lang` and `dir` on the guided popover so its default labels and keyboard direction match the active locale.
@@ -1,6 +0,0 @@
1
- export type SharePointUIAppearance = "fluent" | "ios" | "windows" | "linux" | "glass";
2
- export type SharePointUIDensity = "compact" | "comfortable" | "touch";
3
- export type SharePointUIStyleTokens = Record<`--spui-${string}`, string | number>;
4
- export declare const DEFAULT_ACCENT = "#78716c";
5
- export type SharePointUIBaseAppearance = Exclude<SharePointUIAppearance, "glass">;
6
- export declare function getSharePointUIStyleTokens(appearance?: SharePointUIAppearance, overrides?: Partial<SharePointUIStyleTokens>, baseAppearance?: SharePointUIBaseAppearance, density?: SharePointUIDensity): SharePointUIStyleTokens;
@@ -1,217 +0,0 @@
1
- export const DEFAULT_ACCENT = "#78716c";
2
- const presets = {
3
- fluent: { "--spui-font-family": '"Segoe UI", sans-serif', "--spui-control-radius": "4px", "--spui-control-height": "32px", "--spui-card-radius": "8px", "--spui-base-card-padding": "16px", "--spui-card-padding": "16px", "--spui-base-control-gap": "8px", "--spui-control-gap": "8px", "--spui-control-padding-inline": "14px", "--spui-ui-text": "#242424", "--spui-ui-muted": "#616161", "--spui-ui-canvas": "#f5f5f5", "--spui-ui-surface": "#ffffff", "--spui-ui-surface-hover": "#f5f5f5", "--spui-ui-stroke": "#d1d1d1", "--spui-ui-accent": DEFAULT_ACCENT, "--spui-ui-accent-hover": "color-mix(in srgb, var(--spui-ui-accent) 84%, black)", "--spui-ui-accent-text": "#ffffff", "--spui-ui-success": "#107c10", "--spui-ui-success-surface": "#dff6dd", "--spui-ui-warning": "#835b00", "--spui-ui-warning-surface": "#fff4ce", "--spui-ui-danger": "#b10e1c", "--spui-ui-danger-surface": "#fde7e9", "--spui-focus-ring": "var(--spui-ui-accent)", "--spui-card-shadow": "0 1px 3px #0001", "--spui-color-scheme": "light" },
4
- ios: { "--spui-font-family": '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif', "--spui-control-radius": "11px", "--spui-control-height": "44px", "--spui-card-radius": "18px", "--spui-base-card-padding": "20px", "--spui-card-padding": "20px", "--spui-base-control-gap": "10px", "--spui-control-gap": "10px", "--spui-control-padding-inline": "16px", "--spui-ui-text": "light-dark(#1c1c1e, #f2f2f7)", "--spui-ui-muted": "light-dark(#636366, #aeaeb2)", "--spui-ui-canvas": "light-dark(#f2f2f7, #000000)", "--spui-ui-surface": "light-dark(#ffffff, #1c1c1e)", "--spui-ui-surface-hover": "light-dark(#e5e5ea, #2c2c2e)", "--spui-ui-stroke": "light-dark(#c6c6c8, #38383a)", "--spui-ui-accent": DEFAULT_ACCENT, "--spui-ui-accent-hover": "color-mix(in srgb, var(--spui-ui-accent) 84%, black)", "--spui-ui-accent-text": "#ffffff", "--spui-ui-success": "light-dark(#248a3d, #30d158)", "--spui-ui-success-surface": "light-dark(#e4f5e9, #14321c)", "--spui-ui-warning": "light-dark(#9a6700, #ffd60a)", "--spui-ui-warning-surface": "light-dark(#fff3d6, #342900)", "--spui-ui-danger": "light-dark(#d70015, #ff453a)", "--spui-ui-danger-surface": "light-dark(#ffe5e8, #3a1114)", "--spui-focus-ring": "var(--spui-ui-accent)", "--spui-card-shadow": "light-dark(0 2px 10px #1c1c1e12, 0 2px 10px #0008)", "--spui-control-shadow": "light-dark(0 1px 2px #1c1c1e0d, none)", "--spui-color-scheme": "light dark" },
5
- windows: { "--spui-font-family": '"Segoe UI Variable", "Segoe UI", sans-serif', "--spui-control-radius": "6px", "--spui-control-height": "32px", "--spui-card-radius": "8px", "--spui-base-card-padding": "16px", "--spui-card-padding": "16px", "--spui-base-control-gap": "8px", "--spui-control-gap": "8px", "--spui-ui-text": "#1a1a1a", "--spui-ui-muted": "#5d5d5d", "--spui-ui-canvas": "#f3f3f3", "--spui-ui-surface": "#ffffff", "--spui-ui-surface-hover": "#f5f5f5", "--spui-ui-stroke": "#d1d1d1", "--spui-ui-accent": DEFAULT_ACCENT, "--spui-ui-accent-hover": "color-mix(in srgb, var(--spui-ui-accent) 84%, black)", "--spui-ui-accent-text": "#ffffff", "--spui-ui-success": "#0f7b0f", "--spui-ui-success-surface": "#dff6dd", "--spui-ui-warning": "#835b00", "--spui-ui-warning-surface": "#fff4ce", "--spui-ui-danger": "#c42b1c", "--spui-ui-danger-surface": "#fde7e9", "--spui-focus-ring": "var(--spui-ui-accent)", "--spui-card-shadow": "0 2px 6px #00000012", "--spui-color-scheme": "light" },
6
- linux: { "--spui-font-family": "system-ui, Ubuntu, Cantarell, sans-serif", "--spui-control-radius": "6px", "--spui-control-height": "36px", "--spui-card-radius": "10px", "--spui-base-card-padding": "18px", "--spui-card-padding": "18px", "--spui-base-control-gap": "8px", "--spui-control-gap": "8px", "--spui-ui-text": "#241f31", "--spui-ui-muted": "#5e5c64", "--spui-ui-canvas": "#f6f5f4", "--spui-ui-surface": "#ffffff", "--spui-ui-surface-hover": "#f1f0ef", "--spui-ui-stroke": "#c7c5c3", "--spui-ui-accent": DEFAULT_ACCENT, "--spui-ui-accent-hover": "color-mix(in srgb, var(--spui-ui-accent) 84%, black)", "--spui-ui-accent-text": "#ffffff", "--spui-ui-success": "#2ec27e", "--spui-ui-success-surface": "#e4f5ec", "--spui-ui-warning": "#9c6e03", "--spui-ui-warning-surface": "#fff3d6", "--spui-ui-danger": "#e01b24", "--spui-ui-danger-surface": "#ffe5e8", "--spui-focus-ring": "var(--spui-ui-accent)", "--spui-card-shadow": "0 2px 8px #00000012", "--spui-color-scheme": "light" },
7
- glass: {}
8
- };
9
- function scalePresetLength(value, scale) {
10
- if (typeof value !== "string")
11
- return String(value ?? "0px");
12
- const pixels = /^(-?\d*\.?\d+)px$/.exec(value.trim());
13
- // ponytail: presets use px lengths; keep custom units intact instead of emitting unsupported CSS math.
14
- return pixels ? `${Number(pixels[1]) * scale}px` : value;
15
- }
16
- export function getSharePointUIStyleTokens(appearance = "fluent", overrides, baseAppearance = "fluent", density = "comfortable") {
17
- const selectedBase = appearance === "glass" ? baseAppearance : appearance;
18
- const tokens = { ...presets[selectedBase] };
19
- Object.assign(tokens, { "--spui-motion-duration-fast": "120ms", "--spui-motion-duration-normal": "150ms", "--spui-motion-easing-standard": "cubic-bezier(.2,0,0,1)" });
20
- if (selectedBase === "fluent")
21
- Object.assign(tokens, {
22
- "--spui-ui-text": "var(--spui-semantic-body-text, var(--colorNeutralForeground1, #242424))",
23
- "--spui-ui-muted": "var(--spui-semantic-body-subtext, var(--colorNeutralForeground2, #616161))",
24
- "--spui-ui-canvas": "var(--spui-semantic-body-background, var(--colorNeutralBackground2, #f5f5f5))",
25
- "--spui-ui-surface": "var(--spui-semantic-card-background, var(--spui-semantic-body-background, var(--colorNeutralBackground1, #ffffff)))",
26
- "--spui-ui-surface-hover": "var(--spui-semantic-button-background-hovered, var(--colorNeutralBackground1Hover, #f5f5f5))",
27
- "--spui-ui-stroke": "var(--spui-semantic-body-divider, var(--colorNeutralStroke2, #d1d1d1))",
28
- "--spui-ui-accent": `var(--spui-semantic-primary-button-background, var(--colorBrandBackground, ${DEFAULT_ACCENT}))`,
29
- "--spui-ui-accent-hover": "var(--spui-semantic-primary-button-background-hovered, color-mix(in srgb, var(--spui-ui-accent) 84%, black))",
30
- "--spui-ui-accent-text": "var(--spui-semantic-primary-button-text, var(--colorNeutralForegroundOnBrand, #ffffff))",
31
- "--spui-ui-success": "var(--spui-semantic-success-text, #107c10)",
32
- "--spui-ui-success-surface": "var(--spui-semantic-success-background, #dff6dd)",
33
- "--spui-ui-warning": "var(--spui-semantic-warning-text, #835b00)",
34
- "--spui-ui-warning-surface": "var(--spui-semantic-warning-background, #fff4ce)",
35
- "--spui-ui-danger": "var(--spui-semantic-error-text, #b10e1c)",
36
- "--spui-ui-danger-surface": "var(--spui-semantic-error-background, #fde7e9)",
37
- "--spui-focus-ring": "var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent)))"
38
- });
39
- else {
40
- const presetText = String(tokens["--spui-ui-text"]);
41
- const presetMuted = String(tokens["--spui-ui-muted"]);
42
- const presetCanvas = String(tokens["--spui-ui-canvas"]);
43
- const presetSurface = String(tokens["--spui-ui-surface"]);
44
- const presetStroke = String(tokens["--spui-ui-stroke"]);
45
- const presetAccent = String(tokens["--spui-ui-accent"]);
46
- const presetAccentHover = String(tokens["--spui-ui-accent-hover"]);
47
- const presetAccentText = String(tokens["--spui-ui-accent-text"]);
48
- Object.assign(tokens, {
49
- "--spui-ui-text": `var(--spui-semantic-body-text, var(--colorNeutralForeground1, ${presetText}))`,
50
- "--spui-ui-muted": `var(--spui-semantic-body-subtext, var(--colorNeutralForeground2, ${presetMuted}))`,
51
- "--spui-ui-canvas": `color-mix(in srgb, var(--spui-semantic-body-background, ${presetCanvas}) 90%, var(--spui-semantic-primary-button-background, ${presetAccent}) 10%)`,
52
- "--spui-ui-surface": `var(--spui-semantic-card-background, var(--spui-semantic-body-background, var(--colorNeutralBackground1, ${presetSurface})))`,
53
- "--spui-ui-surface-hover": `color-mix(in srgb, var(--spui-ui-surface, ${presetSurface}) 90%, var(--spui-ui-accent, ${presetAccent}) 10%)`,
54
- "--spui-ui-stroke": `var(--spui-semantic-body-divider, var(--colorNeutralStroke2, ${presetStroke}))`,
55
- "--spui-ui-accent": `var(--spui-semantic-primary-button-background, var(--colorBrandBackground, ${presetAccent}))`,
56
- "--spui-ui-accent-hover": `var(--spui-semantic-primary-button-background-hovered, color-mix(in srgb, var(--spui-ui-accent, ${presetAccent}) 84%, var(--spui-ui-text, ${presetText}) 16%))`,
57
- "--spui-ui-accent-text": `var(--spui-semantic-primary-button-text, var(--colorNeutralForegroundOnBrand, ${presetAccentText}))`,
58
- "--spui-ui-success": `var(--spui-semantic-success-text, ${tokens["--spui-ui-success"]})`,
59
- "--spui-ui-success-surface": `var(--spui-semantic-success-background, ${tokens["--spui-ui-success-surface"]})`,
60
- "--spui-ui-warning": `var(--spui-semantic-warning-text, ${tokens["--spui-ui-warning"]})`,
61
- "--spui-ui-warning-surface": `var(--spui-semantic-warning-background, ${tokens["--spui-ui-warning-surface"]})`,
62
- "--spui-ui-danger": `var(--spui-semantic-error-text, ${tokens["--spui-ui-danger"]})`,
63
- "--spui-ui-danger-surface": `var(--spui-semantic-error-background, ${tokens["--spui-ui-danger-surface"]})`,
64
- "--spui-focus-ring": "var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent)))"
65
- });
66
- }
67
- if (selectedBase === "ios") {
68
- const tenantText = "var(--spui-semantic-body-text, var(--colorNeutralForeground1, #1c1c1e))";
69
- const tenantMuted = "var(--spui-semantic-body-subtext, var(--colorNeutralForeground2, #636366))";
70
- const tenantCanvas = "var(--spui-semantic-body-background, #f2f2f7)";
71
- const tenantSurface = "var(--spui-semantic-card-background, var(--colorNeutralBackground1, #ffffff))";
72
- const tenantAccent = `var(--spui-semantic-primary-button-background, var(--colorBrandBackground, ${DEFAULT_ACCENT}))`;
73
- Object.assign(tokens, {
74
- "--spui-ui-text": `light-dark(${tenantText}, color-mix(in srgb, ${tenantText} 14%, white 86%))`,
75
- "--spui-ui-muted": `light-dark(${tenantMuted}, color-mix(in srgb, ${tenantMuted} 18%, white 82%))`,
76
- "--spui-ui-canvas": `light-dark(color-mix(in srgb, ${tenantCanvas} 90%, ${tenantAccent} 10%), color-mix(in srgb, ${tenantCanvas} 18%, black 82%))`,
77
- "--spui-ui-surface": `light-dark(${tenantSurface}, color-mix(in srgb, ${tenantSurface} 20%, black 80%))`,
78
- "--spui-ui-surface-hover": `light-dark(color-mix(in srgb, var(--spui-ui-surface, #ffffff) 90%, ${tenantAccent} 10%), color-mix(in srgb, var(--spui-ui-surface, #1c1c1e) 90%, white 10%))`,
79
- "--spui-ui-stroke": "light-dark(var(--spui-semantic-body-divider, var(--colorNeutralStroke2, #c6c6c8)), color-mix(in srgb, var(--spui-semantic-body-divider, #38383a) 25%, white 75%))",
80
- "--spui-ui-accent": `light-dark(${tenantAccent}, ${tenantAccent})`,
81
- "--spui-ui-accent-hover": `var(--spui-semantic-primary-button-background-hovered, color-mix(in srgb, ${tenantAccent} 84%, black))`,
82
- "--spui-ui-success": "light-dark(var(--spui-semantic-success-text, #248a3d), color-mix(in srgb, var(--spui-semantic-success-text, #248a3d) 32%, white 68%))",
83
- "--spui-ui-success-surface": "light-dark(var(--spui-semantic-success-background, #e4f5e9), color-mix(in srgb, var(--spui-semantic-success-background, #14321c) 28%, black 72%))",
84
- "--spui-ui-warning": "light-dark(var(--spui-semantic-warning-text, #9a6700), color-mix(in srgb, var(--spui-semantic-warning-text, #9a6700) 30%, white 70%))",
85
- "--spui-ui-warning-surface": "light-dark(var(--spui-semantic-warning-background, #fff3d6), color-mix(in srgb, var(--spui-semantic-warning-background, #342900) 28%, black 72%))",
86
- "--spui-ui-danger": "light-dark(var(--spui-semantic-error-text, #d70015), color-mix(in srgb, var(--spui-semantic-error-text, #d70015) 30%, white 70%))",
87
- "--spui-ui-danger-surface": "light-dark(var(--spui-semantic-error-background, #ffe5e8), color-mix(in srgb, var(--spui-semantic-error-background, #3a1114) 28%, black 72%))",
88
- "--spui-focus-ring": "var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent)))"
89
- });
90
- }
91
- // Stable shared roles for applications that style slots or compose their own
92
- // controls. These remain live aliases so SharePoint and appearance changes
93
- // continue to flow through without copying resolved colors at render time.
94
- Object.assign(tokens, {
95
- "--spui-ui-foreground-subtle": "color-mix(in srgb, var(--spui-ui-text) 72%, var(--spui-ui-surface))",
96
- "--spui-ui-foreground-disabled": "var(--spui-semantic-disabled-text, var(--colorNeutralForegroundDisabled, color-mix(in srgb, var(--spui-ui-muted) 68%, var(--spui-ui-surface))))",
97
- "--spui-ui-text-muted": "var(--colorNeutralForeground3, var(--spui-ui-muted))",
98
- "--spui-ui-surface-raised": "color-mix(in srgb, var(--spui-ui-surface) 96%, var(--spui-ui-text))",
99
- "--spui-ui-surface-secondary": "var(--colorNeutralBackground3, var(--spui-ui-surface-raised))",
100
- "--spui-ui-surface-subtle": "var(--colorNeutralBackground2, var(--spui-ui-canvas))",
101
- "--spui-ui-surface-alt": "var(--colorNeutralBackground2, var(--spui-ui-surface-subtle))",
102
- "--spui-ui-surface-sunken": "color-mix(in srgb, var(--spui-ui-canvas) 92%, var(--spui-ui-text))",
103
- "--spui-ui-border-subtle": "color-mix(in srgb, var(--spui-ui-stroke) 55%, var(--spui-ui-surface))",
104
- "--spui-ui-border-strong": "color-mix(in srgb, var(--spui-ui-stroke) 72%, var(--spui-ui-text))",
105
- "--spui-ui-border-focus": "color-mix(in srgb, var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent))) 30%, var(--spui-ui-text, #242424))",
106
- "--spui-ui-focus": "var(--spui-ui-border-focus)",
107
- "--spui-ui-border-disabled": "var(--colorNeutralStrokeDisabled, color-mix(in srgb, var(--spui-ui-stroke) 40%, var(--spui-ui-surface)))",
108
- "--spui-ui-accent-pressed": "var(--colorBrandBackgroundPressed, color-mix(in srgb, var(--spui-ui-accent) 76%, black))",
109
- "--spui-ui-accent-subtle": "var(--colorBrandBackground2, color-mix(in srgb, var(--spui-ui-accent) 14%, var(--spui-ui-surface)))",
110
- "--spui-ui-accent-subtle-hover": "var(--colorBrandBackground2Hover, color-mix(in srgb, var(--spui-ui-accent) 20%, var(--spui-ui-surface)))",
111
- "--spui-ui-accent-subtle-pressed": "var(--colorBrandBackground2Pressed, color-mix(in srgb, var(--spui-ui-accent) 26%, var(--spui-ui-surface)))",
112
- "--spui-ui-hover": "var(--spui-ui-surface-hover)",
113
- "--spui-selection-background": "color-mix(in srgb, var(--spui-ui-accent) 14%, var(--spui-ui-surface))",
114
- "--spui-selection-background-hover": "var(--colorNeutralBackground1Selected, color-mix(in srgb, var(--spui-ui-accent) 20%, var(--spui-ui-surface)))",
115
- "--spui-selection-foreground": "var(--spui-ui-text)",
116
- "--spui-selection-border": "var(--spui-ui-accent)",
117
- "--spui-link-color": "var(--spui-semantic-link, color-mix(in srgb, var(--spui-ui-accent) 55%, var(--spui-ui-text) 45%))",
118
- "--spui-link-visited": "var(--spui-semantic-link-visited, var(--spui-link-color))",
119
- "--spui-overlay-scrim": "rgb(0 0 0 / 40%)",
120
- "--spui-ui-info": "var(--spui-ui-accent)",
121
- "--spui-ui-info-surface": "color-mix(in srgb, var(--spui-ui-accent) 12%, var(--spui-ui-surface))",
122
- "--spui-font-family-mono": "ui-monospace, SFMono-Regular, Consolas, monospace",
123
- "--spui-text-font-size-small": "12px",
124
- "--spui-text-font-size-large": "18px",
125
- "--spui-text-font-size-heading": "24px",
126
- "--spui-text-font-weight-strong": "600",
127
- "--spui-text-line-height-heading": "1.25",
128
- "--spui-text-letter-spacing": "normal",
129
- "--spui-control-padding-block": "6px",
130
- "--spui-section-gap": "24px",
131
- "--spui-list-gap": "var(--spui-control-gap)",
132
- "--spui-touch-target-min": "44px",
133
- "--spui-surface-radius": "var(--spui-card-radius)",
134
- "--spui-pill-radius": "999px",
135
- "--spui-focus-ring-width": "2px",
136
- "--spui-popover-shadow": "0 4px 16px #0002",
137
- "--spui-overlay-shadow": "0 12px 40px #0003",
138
- "--spui-focus-shadow": "0 0 0 2px var(--spui-focus-ring)",
139
- "--spui-motion-duration-slow": "220ms",
140
- "--spui-motion-easing-emphasized": "cubic-bezier(.2,0,0,1)"
141
- });
142
- const densityScale = density === "compact" ? 0.88 : density === "touch" ? 1.12 : 1;
143
- if (densityScale !== 1) {
144
- for (const name of ["--spui-control-height", "--spui-control-gap", "--spui-control-padding-inline", "--spui-control-padding-block", "--spui-card-padding", "--spui-field-gap", "--spui-section-gap", "--spui-list-gap", "--spui-base-card-padding", "--spui-base-control-gap", "--spui-base-control-padding", "--spui-base-control-height"]) {
145
- if (tokens[name] !== undefined)
146
- tokens[name] = scalePresetLength(tokens[name], densityScale);
147
- }
148
- }
149
- // Treat incoming style tokens as the selected base theme. Glass derives its
150
- // finish from those resolved colors and dimensions, then explicit glass
151
- // token overrides can still opt out of an individual derived value.
152
- for (const [name, value] of Object.entries(overrides ?? {}))
153
- if (typeof value === "string" || typeof value === "number")
154
- tokens[name] = value;
155
- tokens["--spui-base-card-padding"] = overrides?.["--spui-base-card-padding"] ?? tokens["--spui-card-padding"] ?? "16px";
156
- tokens["--spui-base-control-gap"] = overrides?.["--spui-base-control-gap"] ?? tokens["--spui-control-gap"] ?? "8px";
157
- tokens["--spui-base-control-padding"] = overrides?.["--spui-base-control-padding"] ?? tokens["--spui-control-padding-inline"] ?? "14px";
158
- tokens["--spui-base-control-height"] = overrides?.["--spui-base-control-height"] ?? tokens["--spui-control-height"] ?? "32px";
159
- if (appearance === "glass")
160
- Object.assign(tokens, {
161
- "--spui-base-text": overrides?.["--spui-ui-text"] ?? tokens["--spui-ui-text"],
162
- "--spui-base-muted": overrides?.["--spui-ui-muted"] ?? tokens["--spui-ui-muted"],
163
- "--spui-base-canvas": overrides?.["--spui-ui-canvas"] ?? tokens["--spui-ui-canvas"],
164
- "--spui-base-surface": overrides?.["--spui-ui-surface"] ?? tokens["--spui-ui-surface"],
165
- "--spui-base-stroke": overrides?.["--spui-ui-stroke"] ?? tokens["--spui-ui-stroke"],
166
- "--spui-base-accent": overrides?.["--spui-ui-accent"] ?? tokens["--spui-ui-accent"],
167
- "--spui-base-accent-text": overrides?.["--spui-ui-accent-text"] ?? tokens["--spui-ui-accent-text"],
168
- "--spui-base-success": overrides?.["--spui-ui-success-surface"] ?? tokens["--spui-ui-success-surface"],
169
- "--spui-base-warning": overrides?.["--spui-ui-warning-surface"] ?? tokens["--spui-ui-warning-surface"],
170
- "--spui-base-danger": overrides?.["--spui-ui-danger-surface"] ?? tokens["--spui-ui-danger-surface"],
171
- "--spui-base-success-text": overrides?.["--spui-ui-success"] ?? tokens["--spui-ui-success"],
172
- "--spui-base-warning-text": overrides?.["--spui-ui-warning"] ?? tokens["--spui-ui-warning"],
173
- "--spui-base-danger-text": overrides?.["--spui-ui-danger"] ?? tokens["--spui-ui-danger"],
174
- "--spui-ui-text": "var(--spui-base-text)",
175
- "--spui-ui-muted": "var(--spui-base-muted)",
176
- "--spui-ui-canvas": "color-mix(in srgb, var(--spui-base-canvas) var(--spui-glass-canvas-opacity,84%), transparent)",
177
- "--spui-ui-surface": "color-mix(in srgb, var(--spui-base-surface) var(--spui-glass-surface-opacity,88%), transparent)",
178
- "--spui-ui-surface-hover": "color-mix(in srgb, var(--spui-base-surface) 90%, var(--spui-base-accent) 10%)",
179
- "--spui-ui-stroke": "color-mix(in srgb, var(--spui-base-stroke) var(--spui-glass-border-opacity,64%), transparent)",
180
- "--spui-ui-accent": "var(--spui-base-accent)",
181
- "--spui-ui-accent-hover": "color-mix(in srgb, var(--spui-base-accent) 84%, black)",
182
- "--spui-ui-accent-text": "var(--spui-base-accent-text)",
183
- "--spui-ui-success-surface": "color-mix(in srgb, var(--spui-base-success) 72%, transparent)",
184
- "--spui-ui-warning-surface": "color-mix(in srgb, var(--spui-base-warning) 72%, transparent)",
185
- "--spui-ui-danger-surface": "color-mix(in srgb, var(--spui-base-danger) 72%, transparent)",
186
- "--spui-success-surface": "var(--spui-ui-success-surface)",
187
- "--spui-warning-surface": "var(--spui-ui-warning-surface)",
188
- "--spui-danger-surface": "var(--spui-ui-danger-surface)",
189
- "--spui-success-text": "var(--spui-base-success-text)",
190
- "--spui-warning-text": "var(--spui-base-warning-text)",
191
- "--spui-danger-text": "var(--spui-base-danger-text)",
192
- "--spui-control-background": "color-mix(in srgb, var(--spui-base-surface) 86%, transparent)",
193
- "--spui-control-border": "color-mix(in srgb, var(--spui-base-stroke) 72%, transparent)",
194
- "--spui-primary-background": "color-mix(in srgb, var(--spui-base-accent) 94%, var(--spui-base-surface) 6%)",
195
- "--spui-primary-hover": "color-mix(in srgb, var(--spui-base-accent) 86%, black 14%)",
196
- "--spui-card-background": "linear-gradient(145deg, color-mix(in srgb, var(--spui-base-surface) 76%, var(--spui-base-accent) 12%), color-mix(in srgb, var(--spui-base-surface) 84%, var(--spui-base-accent) 8%))",
197
- "--spui-card-border": "var(--spui-ui-stroke)",
198
- "--spui-card-backdrop": "blur(var(--spui-glass-blur,16px)) saturate(var(--spui-glass-saturation,150%))",
199
- "--spui-card-shadow": "0 8px 28px color-mix(in srgb, var(--spui-base-text) 14%, transparent)",
200
- "--spui-spacing-scale": "1.12",
201
- "--spui-control-padding-inline": scalePresetLength(tokens["--spui-base-control-padding"], 1.12),
202
- "--spui-control-height": scalePresetLength(tokens["--spui-base-control-height"], 1.04),
203
- "--spui-card-padding": scalePresetLength(tokens["--spui-base-card-padding"], 1.12),
204
- "--spui-control-gap": scalePresetLength(tokens["--spui-base-control-gap"], 1.12),
205
- "--spui-color-scheme": tokens["--spui-color-scheme"] ?? "light"
206
- });
207
- if (appearance === "glass") {
208
- // Directly specified finished values win; base values still flow through
209
- // the glass transforms above.
210
- for (const name of ["--spui-control-padding-inline", "--spui-control-height", "--spui-card-padding", "--spui-control-gap", "--spui-color-scheme"]) {
211
- const value = overrides?.[name];
212
- if (typeof value === "string" || typeof value === "number")
213
- tokens[name] = value;
214
- }
215
- }
216
- return tokens;
217
- }
@@ -1,9 +0,0 @@
1
- export type AccordionItemValue = string | number;
2
- export type AccordionNavigation = "linear" | "circular";
3
- export type AccordionToggleData<TValue extends AccordionItemValue = AccordionItemValue> = {
4
- value: TValue;
5
- openItems: TValue[];
6
- };
7
- export declare function defineAccordionHeader(): void;
8
- export declare function defineAccordionPanel(): void;
9
- export declare function defineAccordion(): void;