@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/dist/theme.js CHANGED
@@ -1,244 +1,41 @@
1
- import { getSharePointUIStyleTokens } from "./appearance.js";
2
- /** Build named swatches from the current SharePoint palette; custom tenant keys are preserved. */
3
- export function getSharePointThemeColorPresets(palette, labelForKey) {
4
- return Object.entries(palette ?? {}).flatMap(([name, color]) => {
5
- if (typeof color !== "string" || !color.trim())
6
- return [];
7
- const readableName = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Za-z])(\d+)/g, "$1 $2").replace(/[_-]+/g, " ").replace(/^theme\s+/i, "").replace(/\s+/g, " ").trim();
8
- const label = labelForKey?.(name) ?? readableName.replace(/\b[a-z]/g, character => character.toUpperCase());
9
- return [{ name, value: name, color, label }];
10
- });
11
- }
12
- /** Compose reusable theme packs left-to-right; later packs override earlier values within each group. */
13
- export function composeSharePointUIDesignThemes(...themes) {
14
- return themes.reduce((merged, theme) => ({
15
- ...merged,
16
- ...theme,
17
- palette: { ...merged.palette, ...theme?.palette },
18
- fluentTokens: { ...merged.fluentTokens, ...theme?.fluentTokens },
19
- colors: { ...merged.colors, ...theme?.colors },
20
- typography: { ...merged.typography, ...theme?.typography },
21
- spacing: { ...merged.spacing, ...theme?.spacing },
22
- shape: { ...merged.shape, ...theme?.shape },
23
- elevation: { ...merged.elevation, ...theme?.elevation },
24
- materials: { ...merged.materials, ...theme?.materials },
25
- motion: { ...merged.motion, ...theme?.motion },
26
- layers: { ...merged.layers, ...theme?.layers },
27
- customProperties: { ...merged.customProperties, ...theme?.customProperties }
28
- }), {});
29
- }
30
- const designTokenMap = {
31
- "colors.accent": "--spui-ui-accent", "colors.accentHover": "--spui-ui-accent-hover", "colors.accentPressed": "--spui-ui-accent-pressed",
32
- "colors.accentSubtle": "--spui-ui-accent-subtle", "colors.accentSubtleHover": "--spui-ui-accent-subtle-hover", "colors.accentSubtlePressed": "--spui-ui-accent-subtle-pressed", "colors.accentForeground": "--spui-ui-accent-text", "colors.secondaryHover": "--spui-button-secondary-hover",
33
- "colors.foreground": "--spui-ui-text", "colors.foregroundMuted": "--spui-ui-muted", "colors.foregroundTertiary": "--spui-ui-text-muted", "colors.foregroundSubtle": "--spui-ui-foreground-subtle",
34
- "colors.foregroundDisabled": "--spui-ui-foreground-disabled", "colors.canvas": "--spui-ui-canvas",
35
- "colors.surface": "--spui-ui-surface", "colors.surfaceHover": "--spui-ui-surface-hover", "colors.surfaceAlt": "--spui-ui-surface-alt", "colors.surfaceSecondary": "--spui-ui-surface-secondary", "colors.surfaceSubtle": "--spui-ui-surface-subtle", "colors.surfaceRaised": "--spui-ui-surface-raised",
36
- "colors.surfaceSunken": "--spui-ui-surface-sunken", "colors.border": "--spui-ui-stroke", "colors.borderSubtle": "--spui-ui-border-subtle",
37
- "colors.borderStrong": "--spui-ui-border-strong", "colors.borderFocus": "--spui-ui-border-focus", "colors.borderDisabled": "--spui-ui-border-disabled",
38
- "colors.selectedBackground": "--spui-selection-background", "colors.selectedBackgroundHover": "--spui-selection-background-hover", "colors.selectedForeground": "--spui-selection-foreground",
39
- "colors.selectedBorder": "--spui-selection-border", "colors.link": "--spui-link-color", "colors.linkVisited": "--spui-link-visited", "colors.overlay": "--spui-overlay-scrim",
40
- "colors.info": "--spui-ui-info", "colors.infoSurface": "--spui-ui-info-surface",
41
- "colors.success": "--spui-ui-success", "colors.successSurface": "--spui-ui-success-surface", "colors.warning": "--spui-ui-warning",
42
- "colors.warningSurface": "--spui-ui-warning-surface", "colors.danger": "--spui-ui-danger", "colors.dangerSurface": "--spui-ui-danger-surface", "colors.focus": "--spui-ui-focus", "colors.focusRing": "--spui-focus-ring",
43
- "typography.fontFamily": "--spui-font-family", "typography.fontFamilyMono": "--spui-font-family-mono",
44
- "typography.fontSize": "--spui-text-font-size", "typography.fontSizeSmall": "--spui-text-font-size-small",
45
- "typography.fontSizeLarge": "--spui-text-font-size-large", "typography.fontSizeHeading": "--spui-text-font-size-heading",
46
- "typography.fontWeight": "--spui-text-font-weight", "typography.fontWeightStrong": "--spui-text-font-weight-strong",
47
- "typography.lineHeight": "--spui-text-line-height", "typography.lineHeightHeading": "--spui-text-line-height-heading",
48
- "typography.fontSize100": "--fontSizeBase100", "typography.fontSize200": "--fontSizeBase200", "typography.fontSize300": "--fontSizeBase300",
49
- "typography.fontSize400": "--fontSizeBase400", "typography.fontSize500": "--fontSizeBase500", "typography.fontSize600": "--fontSizeBase600",
50
- "typography.fontSize700": "--fontSizeHero700", "typography.fontSize800": "--fontSizeHero800", "typography.fontSize900": "--fontSizeHero900", "typography.fontSize1000": "--fontSizeHero1000",
51
- "typography.fontSize1100": "--spui-text-font-size-1100", "typography.fontSize1200": "--spui-text-font-size-1200",
52
- "typography.lineHeight100": "--lineHeightBase100", "typography.lineHeight200": "--lineHeightBase200", "typography.lineHeight300": "--lineHeightBase300",
53
- "typography.lineHeight400": "--lineHeightBase400", "typography.lineHeight500": "--lineHeightBase500", "typography.lineHeight600": "--lineHeightBase600",
54
- "typography.lineHeight700": "--lineHeightHero700", "typography.lineHeight800": "--lineHeightHero800", "typography.lineHeight900": "--lineHeightHero900", "typography.lineHeight1000": "--lineHeightHero1000",
55
- "typography.lineHeight1100": "--spui-text-line-height-1100", "typography.lineHeight1200": "--spui-text-line-height-1200",
56
- "typography.fontWeightRegular": "--fontWeightRegular", "typography.fontWeightMedium": "--fontWeightMedium",
57
- "typography.fontWeightSemibold": "--fontWeightSemibold", "typography.fontWeightBold": "--fontWeightBold",
58
- "typography.letterSpacing": "--spui-text-letter-spacing",
59
- "spacing.controlHeight": "--spui-control-height", "spacing.controlGap": "--spui-control-gap", "spacing.controlPaddingInline": "--spui-control-padding-inline",
60
- "spacing.controlPaddingBlock": "--spui-control-padding-block", "spacing.cardPadding": "--spui-card-padding", "spacing.fieldGap": "--spui-field-gap",
61
- "spacing.sectionGap": "--spui-section-gap", "spacing.listGap": "--spui-list-gap", "spacing.touchTargetMin": "--spui-touch-target-min",
62
- "shape.controlRadius": "--spui-control-radius", "shape.inputRadius": "--spui-input-radius", "shape.cardRadius": "--spui-card-radius",
63
- "shape.surfaceRadius": "--spui-surface-radius", "shape.pillRadius": "--spui-pill-radius", "shape.focusRingWidth": "--spui-focus-ring-width",
64
- "elevation.cardShadow": "--spui-card-shadow", "elevation.controlShadow": "--spui-control-shadow", "elevation.popoverShadow": "--spui-popover-shadow",
65
- "elevation.overlayShadow": "--spui-overlay-shadow", "elevation.focusShadow": "--spui-focus-shadow",
66
- "materials.glassBlur": "--spui-glass-blur", "materials.glassSaturation": "--spui-glass-saturation",
67
- "materials.glassSurfaceOpacity": "--spui-glass-surface-opacity", "materials.glassCanvasOpacity": "--spui-glass-canvas-opacity", "materials.glassBorderOpacity": "--spui-glass-border-opacity",
68
- "motion.durationFast": "--spui-motion-duration-fast", "motion.durationNormal": "--spui-motion-duration-normal", "motion.durationSlow": "--spui-motion-duration-slow",
69
- "motion.easingStandard": "--spui-motion-easing-standard", "motion.easingEmphasized": "--spui-motion-easing-emphasized",
70
- "layers.dropdown": "--spui-layer-dropdown", "layers.popover": "--spui-layer-popover", "layers.toast": "--spui-layer-toast"
1
+ import { createContext, createElement, useContext } from "react";
2
+ const defaults = {
3
+ accent: "#0f6cbd",
4
+ accentText: "#ffffff",
5
+ background: "#f5f5f5",
6
+ border: "#d1d1d1",
7
+ radius: "4px",
8
+ surface: "#ffffff",
9
+ text: "#242424",
10
+ muted: "#616161",
71
11
  };
72
- export function getSharePointUIDesignTokens(theme) {
73
- const accent = theme?.colors?.accent ?? theme?.brandColor ?? theme?.palette?.themePrimary;
74
- const result = {};
75
- for (const [name, value] of Object.entries(theme?.palette ?? {})) {
76
- if (value !== undefined)
77
- result[`--spui-palette-${toKebab(name)}`] = String(value);
78
- }
79
- for (const [name, value] of Object.entries(theme?.fluentTokens ?? {})) {
80
- if (value !== undefined && (typeof value === "string" || typeof value === "number")) {
81
- result[name.startsWith("--") ? name : `--${name}`] = String(value);
82
- }
83
- }
84
- if (accent)
85
- Object.assign(result, {
86
- "--spui-ui-accent": accent,
87
- "--spui-ui-accent-hover": `color-mix(in srgb, ${accent} 84%, black)`,
88
- "--spui-selection-background": `color-mix(in srgb, ${accent} 14%, var(--spui-ui-surface))`,
89
- "--spui-selection-border": accent,
90
- "--spui-link-color": `var(--spui-semantic-link, color-mix(in srgb, ${accent} 55%, var(--spui-ui-text) 45%))`,
91
- "--spui-ui-border-focus": accent,
92
- "--spui-focus-ring": accent,
93
- "--spui-ui-info": accent,
94
- "--spui-ui-info-surface": `color-mix(in srgb, ${accent} 12%, var(--spui-ui-surface))`,
95
- "--spui-primary-background": accent,
96
- "--spui-primary-hover": `color-mix(in srgb, ${accent} 84%, black)`
97
- });
98
- if (theme?.brandColor) {
99
- const darker = `color-mix(in srgb, ${accent} 78%, black)`;
100
- const brandLink = `light-dark(color-mix(in srgb, ${accent} 65%, black), color-mix(in srgb, ${accent} 76%, white))`;
101
- const canvas = `light-dark(color-mix(in srgb, ${accent} 7%, white), color-mix(in srgb, ${accent} 8%, #10191a))`;
102
- const surface = `light-dark(color-mix(in srgb, ${accent} 2%, white), color-mix(in srgb, ${accent} 12%, #182526))`;
103
- const surfaceHover = `light-dark(color-mix(in srgb, ${accent} 12%, white), color-mix(in srgb, ${accent} 20%, #26383b))`;
104
- const border = `light-dark(color-mix(in srgb, ${accent} 24%, #d1d1d1), color-mix(in srgb, ${accent} 32%, #6b7476))`;
105
- Object.assign(result, {
106
- "--spui-ui-canvas": canvas,
107
- "--spui-ui-surface": surface,
108
- "--spui-ui-surface-alt": canvas,
109
- "--spui-ui-surface-hover": surfaceHover,
110
- "--spui-ui-stroke": border,
111
- "--spui-ui-border-subtle": `light-dark(color-mix(in srgb, ${accent} 14%, #e5e5e5), color-mix(in srgb, ${accent} 20%, #344245))`,
112
- "--spui-selection-background": `light-dark(color-mix(in srgb, ${accent} 15%, white), color-mix(in srgb, ${accent} 24%, #202b2c))`,
113
- "--spui-semantic-primary-button-background": accent,
114
- "--spui-semantic-primary-button-background-hovered": `color-mix(in srgb, ${accent} 84%, black)`,
115
- "--spui-semantic-input-focus-border-alt": accent,
116
- "--spui-semantic-input-background-checked": accent,
117
- "--spui-semantic-link": brandLink,
118
- "--spui-semantic-link-hovered": darker,
119
- "--spui-control-border": border,
120
- "--spui-input-border": border,
121
- "--spui-semantic-body-background": canvas,
122
- "--spui-semantic-card-background": surface,
123
- "--spui-semantic-body-divider": border,
124
- "--spui-semantic-button-background-hovered": surfaceHover,
125
- "--colorBrandBackground": accent,
126
- "--colorBrandBackground2": `light-dark(color-mix(in srgb, ${accent} 12%, white), color-mix(in srgb, ${accent} 18%, #182526))`,
127
- "--colorBrandBackground2Hover": `light-dark(color-mix(in srgb, ${accent} 20%, white), color-mix(in srgb, ${accent} 26%, #26383b))`,
128
- "--colorBrandBackgroundPressed": `color-mix(in srgb, ${accent} 76%, black)`,
129
- "--colorBrandBackgroundSelected": darker,
130
- "--colorBrandForegroundLink": brandLink,
131
- "--colorBrandStroke2Hover": `light-dark(color-mix(in srgb, ${accent} 62%, white), color-mix(in srgb, ${accent} 62%, #6b7476))`,
132
- "--colorNeutralBackground1": surface,
133
- "--colorNeutralBackground1Hover": surfaceHover,
134
- "--colorNeutralBackground2": canvas,
135
- "--colorNeutralStroke2": border,
136
- });
137
- }
138
- if (accent && !theme?.colors?.accentForeground) {
139
- const hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(accent)?.[1];
140
- if (hex) {
141
- const normalized = hex.length === 3 ? [...hex].map(value => value + value).join("") : hex;
142
- const channels = normalized.match(/[\da-f]{2}/gi).map(value => Number.parseInt(value, 16) / 255).map(value => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
143
- const luminance = channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
144
- result["--spui-ui-accent-text"] = luminance > 0.179 ? "#000000" : "#ffffff";
145
- }
146
- }
147
- for (const [group, values] of Object.entries(theme ?? {})) {
148
- if (group === "brandColor" || group === "palette" || group === "customProperties")
149
- continue;
150
- for (const [name, value] of Object.entries(values ?? {})) {
151
- if (value !== undefined) {
152
- const token = designTokenMap[`${group}.${name}`];
153
- if (token)
154
- result[token] = String(value);
155
- }
156
- }
157
- }
158
- // Explicit Fluent values are the host's final named-token layer, even when
159
- // a brand seed also generates defaults for the same Fluent roles.
160
- for (const [name, value] of Object.entries(theme?.fluentTokens ?? {})) {
161
- if (value !== undefined && (typeof value === "string" || typeof value === "number")) {
162
- result[name.startsWith("--") ? name : `--${name}`] = String(value);
12
+ const sharePointVars = {
13
+ accent: "--themePrimary",
14
+ accentText: "--white",
15
+ background: "--neutralLighterAlt",
16
+ border: "--neutralQuaternaryAlt",
17
+ surface: "--white",
18
+ text: "--neutralPrimary",
19
+ muted: "--neutralSecondary",
20
+ };
21
+ const ThemeContext = createContext(defaults);
22
+ export function createSharePointTheme(overrides = {}) {
23
+ const inherited = {};
24
+ if (typeof document !== "undefined") {
25
+ const style = getComputedStyle(document.documentElement);
26
+ for (const [token, variable] of Object.entries(sharePointVars)) {
27
+ const value = style.getPropertyValue(variable).trim();
28
+ if (value)
29
+ inherited[token] = value;
163
30
  }
164
31
  }
165
- if (accent) {
166
- const foreground = theme?.colors?.accentForeground ?? result["--spui-ui-accent-text"];
167
- if (theme?.brandColor && result["--spui-semantic-primary-button-text"] === undefined && foreground)
168
- result["--spui-semantic-primary-button-text"] = foreground;
169
- if (theme?.brandColor && result["--colorNeutralForegroundOnBrand"] === undefined && foreground)
170
- result["--colorNeutralForegroundOnBrand"] = foreground;
171
- if (theme?.colors?.link)
172
- result["--spui-semantic-link"] = theme.colors.link;
173
- }
174
- // App-specific properties are the final design-pack layer; direct provider tokens still win later.
175
- for (const [name, value] of Object.entries(theme?.customProperties ?? {})) {
176
- if (name.startsWith("--") && name.length > 2)
177
- result[name] = String(value);
178
- }
179
- return result;
32
+ return { ...defaults, ...inherited, ...overrides };
180
33
  }
181
- const appliedProperties = new WeakMap();
182
- const toKebab = (key) => key.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
183
- export function applySharePointTheme(target, theme, fluentTokens, options = {}) {
184
- const style = target.style;
185
- for (const name of appliedProperties.get(target) ?? [])
186
- style.removeProperty(name);
187
- const names = new Set();
188
- if (theme) {
189
- for (const [group, values] of Object.entries({ palette: theme.palette, semantic: theme.semanticColors })) {
190
- for (const [name, value] of Object.entries(values ?? {})) {
191
- if (!value)
192
- continue;
193
- const property = `--spui-${group}-${toKebab(name)}`;
194
- style.setProperty(property, value);
195
- names.add(property);
196
- }
197
- }
198
- for (const [group, values] of Object.entries({ effects: theme.effects, spacing: theme.spacing, "default-font-style": theme.defaultFontStyle })) {
199
- for (const [name, value] of Object.entries(values ?? {})) {
200
- if (typeof value !== "string" && typeof value !== "number")
201
- continue;
202
- const property = `--spui-${group}-${toKebab(name)}`;
203
- style.setProperty(property, String(value));
204
- names.add(property);
205
- }
206
- }
207
- for (const [name, values] of Object.entries(theme.fonts ?? {})) {
208
- for (const [propertyName, value] of Object.entries(values ?? {})) {
209
- if (typeof value !== "string" && typeof value !== "number")
210
- continue;
211
- const property = `--spui-fonts-${toKebab(name)}-${toKebab(propertyName)}`;
212
- style.setProperty(property, String(value));
213
- names.add(property);
214
- }
215
- }
216
- style.setProperty("--spui-is-inverted", theme.isInverted ? "1" : "0");
217
- names.add("--spui-is-inverted");
218
- }
219
- // Older SPFx themes expose their brand only in palette.themePrimary. The
220
- // appearance presets consume the semantic role, so bridge that value when
221
- // neither semantic colors nor Fluent v9 already provide a brand token.
222
- const semanticAccent = theme?.semanticColors?.primaryButtonBackground;
223
- const fluentAccent = fluentTokens?.colorBrandBackground;
224
- const paletteAccent = theme?.palette?.themePrimary;
225
- if (!semanticAccent && !fluentAccent && paletteAccent) {
226
- style.setProperty("--spui-semantic-primary-button-background", paletteAccent);
227
- names.add("--spui-semantic-primary-button-background");
228
- }
229
- for (const [name, value] of Object.entries(fluentTokens ?? {})) {
230
- if (typeof value !== "string" && typeof value !== "number")
231
- continue;
232
- const property = name.startsWith("--") ? name : `--${name}`;
233
- style.setProperty(property, String(value));
234
- names.add(property);
235
- }
236
- target.dataset.spuiAppearance = options.appearance ?? "fluent";
237
- const styleTokens = getSharePointUIStyleTokens(options.appearance, { ...getSharePointUIDesignTokens(options.theme), ...options.tokens }, options.baseAppearance, options.density);
238
- for (const [property, value] of Object.entries(styleTokens)) {
239
- style.setProperty(property, String(value));
240
- names.add(property);
241
- }
242
- style.colorScheme = String(styleTokens["--spui-color-scheme"] ?? "light");
243
- appliedProperties.set(target, names);
34
+ export function SharePointThemeProvider({ children, theme, }) {
35
+ const value = createSharePointTheme(theme);
36
+ const style = Object.fromEntries(Object.entries(theme ?? {}).map(([key, token]) => [`--spui-${key}`, token]));
37
+ return createElement(ThemeContext.Provider, { value }, createElement("div", { className: "spui-theme", style }, children));
38
+ }
39
+ export function useSharePointTheme() {
40
+ return useContext(ThemeContext);
244
41
  }
@@ -0,0 +1,9 @@
1
+ import { type HTMLAttributes, type ReactElement, type ReactNode } from "react";
2
+ export type ToastProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
3
+ open: boolean;
4
+ message: ReactNode;
5
+ durationMs?: number;
6
+ dismissLabel?: string;
7
+ onDismiss: () => void;
8
+ };
9
+ export declare function Toast({ open, message, durationMs, dismissLabel, onDismiss, className, ...props }: ToastProps): ReactElement | null;
package/dist/toast.js ADDED
@@ -0,0 +1,12 @@
1
+ import { createElement, useEffect } from "react";
2
+ export function Toast({ open, message, durationMs = 5000, dismissLabel = "Dismiss", onDismiss, className = "", ...props }) {
3
+ useEffect(() => {
4
+ if (!open || durationMs <= 0)
5
+ return;
6
+ const timer = setTimeout(onDismiss, durationMs);
7
+ return () => clearTimeout(timer);
8
+ }, [open, durationMs, onDismiss]);
9
+ if (!open)
10
+ return null;
11
+ return createElement("div", { ...props, role: "status", "aria-live": "polite", className: `spui-toast ${className}`.trim() }, createElement("span", null, message), createElement("button", { type: "button", "aria-label": dismissLabel, onClick: onDismiss }, dismissLabel));
12
+ }
package/dist/tree.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { type HTMLAttributes, type ReactElement, type ReactNode } from "react";
2
+ export type TreeNode = {
3
+ id: string;
4
+ label: ReactNode;
5
+ href?: string;
6
+ children?: readonly TreeNode[];
7
+ defaultOpen?: boolean;
8
+ };
9
+ export type TreeProps = Omit<HTMLAttributes<HTMLUListElement>, "children" | "aria-label"> & {
10
+ label: string;
11
+ items: readonly TreeNode[];
12
+ };
13
+ export declare function Tree({ label, items, className, ...props }: TreeProps): ReactElement;
package/dist/tree.js ADDED
@@ -0,0 +1,9 @@
1
+ import { createElement } from "react";
2
+ function renderNodes(nodes) {
3
+ return nodes.map(node => createElement("li", { key: node.id }, node.children?.length
4
+ ? createElement("details", { open: node.defaultOpen }, createElement("summary", null, node.href ? createElement("a", { href: node.href }, node.label) : node.label), createElement("ul", null, renderNodes(node.children)))
5
+ : node.href ? createElement("a", { href: node.href }, node.label) : node.label));
6
+ }
7
+ export function Tree({ label, items, className = "", ...props }) {
8
+ return createElement("ul", { ...props, "aria-label": label, className: `spui-tree ${className}`.trim() }, renderNodes(items));
9
+ }
@@ -0,0 +1,8 @@
1
+ import { type HTMLAttributes, type ReactElement, type ReactNode } from "react";
2
+ export type ViewCardProps = Omit<HTMLAttributes<HTMLElement>, "children" | "title"> & {
3
+ title: ReactNode;
4
+ subtitle?: ReactNode;
5
+ description?: ReactNode;
6
+ children?: ReactNode;
7
+ };
8
+ export declare function ViewCard({ title, subtitle, description, className, children, ...props }: ViewCardProps): ReactElement;
@@ -0,0 +1,4 @@
1
+ import { createElement } from "react";
2
+ export function ViewCard({ title, subtitle, description, className = "", children, ...props }) {
3
+ return createElement("article", { ...props, className: `spui-view-card ${className}`.trim() }, createElement("h2", null, title), subtitle && createElement("p", { className: "spui-view-subtitle" }, subtitle), description && createElement("p", null, description), children);
4
+ }
@@ -0,0 +1,13 @@
1
+ import { type HTMLAttributes, type ReactElement } from "react";
2
+ export type ViewOption = {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ };
7
+ export type ViewSwitcherProps = Omit<HTMLAttributes<HTMLDivElement>, "children" | "aria-label"> & {
8
+ label: string;
9
+ value: string;
10
+ views: readonly ViewOption[];
11
+ onChange: (value: string) => void;
12
+ };
13
+ export declare function ViewSwitcher({ label, value, views, onChange, className, ...props }: ViewSwitcherProps): ReactElement;
@@ -0,0 +1,7 @@
1
+ import { createElement } from "react";
2
+ export function ViewSwitcher({ label, value, views, onChange, className = "", ...props }) {
3
+ return createElement("div", { ...props, role: "group", "aria-label": label, className: `spui-view-switcher ${className}`.trim() }, views.map(view => createElement("button", {
4
+ key: view.value, type: "button", disabled: view.disabled,
5
+ "aria-pressed": value === view.value, onClick: () => onChange(view.value),
6
+ }, view.label)));
7
+ }
@@ -0,0 +1,11 @@
1
+ import { type HTMLAttributes, type ReactElement, type ReactNode } from "react";
2
+ export type VirtualListProps<Item> = Omit<HTMLAttributes<HTMLDivElement>, "children" | "aria-label"> & {
3
+ label: string;
4
+ items: readonly Item[];
5
+ itemHeight: number;
6
+ height: number;
7
+ getItemKey: (item: Item, index: number) => string;
8
+ renderItem: (item: Item, index: number) => ReactNode;
9
+ overscan?: number;
10
+ };
11
+ export declare function VirtualList<Item>({ label, items, itemHeight, height, getItemKey, renderItem, overscan, className, style, ...props }: VirtualListProps<Item>): ReactElement;
@@ -0,0 +1,25 @@
1
+ import { createElement, useState } from "react";
2
+ export function VirtualList({ label, items, itemHeight, height, getItemKey, renderItem, overscan = 3, className = "", style, ...props }) {
3
+ const [scrollTop, setScrollTop] = useState(0);
4
+ const rowHeight = Math.max(1, itemHeight);
5
+ const viewportHeight = Math.max(1, height);
6
+ const extra = Math.max(0, Math.floor(overscan));
7
+ const start = Math.max(0, Math.floor(scrollTop / rowHeight) - extra);
8
+ const end = Math.min(items.length, start + Math.ceil(viewportHeight / rowHeight) + extra * 2);
9
+ const onScroll = (event) => setScrollTop(event.currentTarget.scrollTop);
10
+ return createElement("div", {
11
+ ...props,
12
+ role: "list",
13
+ "aria-label": label,
14
+ tabIndex: 0,
15
+ className: `spui-virtual-list ${className}`.trim(),
16
+ style: { ...style, height: viewportHeight, overflowY: "auto" },
17
+ onScroll,
18
+ }, createElement("div", { role: "presentation", "aria-hidden": true, style: { height: start * rowHeight } }), items.slice(start, end).map((item, offset) => {
19
+ const index = start + offset;
20
+ return createElement("div", {
21
+ key: getItemKey(item, index), role: "listitem", "aria-posinset": index + 1, "aria-setsize": items.length,
22
+ className: "spui-virtual-list-item", style: { height: rowHeight },
23
+ }, renderItem(item, index));
24
+ }), createElement("div", { role: "presentation", "aria-hidden": true, style: { height: (items.length - end) * rowHeight } }));
25
+ }