@fgv/ts-app-shell 5.1.0-2 → 5.1.0-21

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 (204) hide show
  1. package/dist/index.browser.js.map +1 -0
  2. package/dist/index.js.map +1 -0
  3. package/dist/packlets/ai-assist/index.js.map +1 -0
  4. package/dist/packlets/ai-assist/useAiAssist.js +216 -15
  5. package/dist/packlets/ai-assist/useAiAssist.js.map +1 -0
  6. package/dist/packlets/badge/index.js +23 -0
  7. package/dist/packlets/badge/index.js.map +1 -0
  8. package/dist/packlets/badge/renderStatusBadge.js +44 -0
  9. package/dist/packlets/badge/renderStatusBadge.js.map +1 -0
  10. package/dist/packlets/cascade/CascadeContainer.js.map +1 -0
  11. package/dist/packlets/cascade/ComparisonView.js.map +1 -0
  12. package/dist/packlets/cascade/EntityTabLayout.js.map +1 -0
  13. package/dist/packlets/cascade/MobileCascadeStack.js.map +1 -0
  14. package/dist/packlets/cascade/index.js.map +1 -0
  15. package/dist/packlets/cascade/model.js.map +1 -0
  16. package/dist/packlets/cascade/useCascadeOps.js.map +1 -0
  17. package/dist/packlets/cascade/useCascadeTransitions.js.map +1 -0
  18. package/dist/packlets/detail/DetailHelpers.js.map +1 -0
  19. package/dist/packlets/detail/index.js.map +1 -0
  20. package/dist/packlets/drop-zone/JsonDropZone.js.map +1 -0
  21. package/dist/packlets/drop-zone/index.js.map +1 -0
  22. package/dist/packlets/editing/EditFieldHelpers.js +4 -4
  23. package/dist/packlets/editing/EditFieldHelpers.js.map +1 -0
  24. package/dist/packlets/editing/MultiActionButton.js.map +1 -0
  25. package/dist/packlets/editing/NumericInput.js.map +1 -0
  26. package/dist/packlets/editing/TypeaheadInput.js.map +1 -0
  27. package/dist/packlets/editing/index.js.map +1 -0
  28. package/dist/packlets/editing/useTypeaheadMatch.js.map +1 -0
  29. package/dist/packlets/keyboard/index.js.map +1 -0
  30. package/dist/packlets/keyboard/registry.js.map +1 -0
  31. package/dist/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  32. package/dist/packlets/messages/MessagesContext.js.map +1 -0
  33. package/dist/packlets/messages/MessagesLogger.js.map +1 -0
  34. package/dist/packlets/messages/StatusBar.js.map +1 -0
  35. package/dist/packlets/messages/Toast.js.map +1 -0
  36. package/dist/packlets/messages/index.js.map +1 -0
  37. package/dist/packlets/messages/model.js.map +1 -0
  38. package/dist/packlets/messages/useLogReporter.js.map +1 -0
  39. package/dist/packlets/modal/ConfirmDialog.js.map +1 -0
  40. package/dist/packlets/modal/Modal.js.map +1 -0
  41. package/dist/packlets/modal/index.js.map +1 -0
  42. package/dist/packlets/print/PrintEnclosure.js.map +1 -0
  43. package/dist/packlets/print/index.js.map +1 -0
  44. package/dist/packlets/print/openPrintWindow.js.map +1 -0
  45. package/dist/packlets/responsive/ResponsiveProvider.js.map +1 -0
  46. package/dist/packlets/responsive/index.js.map +1 -0
  47. package/dist/packlets/responsive/useResponsiveLayout.js.map +1 -0
  48. package/dist/packlets/selectors/EntityRow.js.map +1 -0
  49. package/dist/packlets/selectors/PreferredSelector.js.map +1 -0
  50. package/dist/packlets/selectors/index.js.map +1 -0
  51. package/dist/packlets/sidebar/CollectionSection.js +158 -43
  52. package/dist/packlets/sidebar/CollectionSection.js.map +1 -0
  53. package/dist/packlets/sidebar/EntityList.js.map +1 -0
  54. package/dist/packlets/sidebar/FilterBar.js.map +1 -0
  55. package/dist/packlets/sidebar/FilterRow.js.map +1 -0
  56. package/dist/packlets/sidebar/GroupedEntityList.js.map +1 -0
  57. package/dist/packlets/sidebar/SearchBar.js.map +1 -0
  58. package/dist/packlets/sidebar/SidebarLayout.js.map +1 -0
  59. package/dist/packlets/sidebar/index.js.map +1 -0
  60. package/dist/packlets/theme/ThemeProvider.js.map +1 -0
  61. package/dist/packlets/theme/index.js.map +1 -0
  62. package/dist/packlets/top-bar/ModeSelector.js.map +1 -0
  63. package/dist/packlets/top-bar/TabBar.js +4 -1
  64. package/dist/packlets/top-bar/TabBar.js.map +1 -0
  65. package/dist/packlets/top-bar/index.js.map +1 -0
  66. package/dist/packlets/url-sync/index.js.map +1 -0
  67. package/dist/packlets/url-sync/useUrlSync.js.map +1 -0
  68. package/lib/index.browser.d.ts.map +1 -0
  69. package/lib/index.browser.js.map +1 -0
  70. package/lib/index.d.ts.map +1 -0
  71. package/lib/index.js.map +1 -0
  72. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  73. package/lib/packlets/ai-assist/index.js.map +1 -0
  74. package/lib/packlets/ai-assist/useAiAssist.d.ts +26 -0
  75. package/lib/packlets/ai-assist/useAiAssist.d.ts.map +1 -0
  76. package/lib/packlets/ai-assist/useAiAssist.js +216 -15
  77. package/lib/packlets/ai-assist/useAiAssist.js.map +1 -0
  78. package/lib/packlets/badge/index.d.ts +2 -0
  79. package/lib/packlets/badge/index.d.ts.map +1 -0
  80. package/lib/packlets/badge/index.js +27 -0
  81. package/lib/packlets/badge/index.js.map +1 -0
  82. package/lib/packlets/badge/renderStatusBadge.d.ts +11 -0
  83. package/lib/packlets/badge/renderStatusBadge.d.ts.map +1 -0
  84. package/lib/packlets/badge/renderStatusBadge.js +50 -0
  85. package/lib/packlets/badge/renderStatusBadge.js.map +1 -0
  86. package/lib/packlets/cascade/CascadeContainer.d.ts.map +1 -0
  87. package/lib/packlets/cascade/CascadeContainer.js.map +1 -0
  88. package/lib/packlets/cascade/ComparisonView.d.ts.map +1 -0
  89. package/lib/packlets/cascade/ComparisonView.js.map +1 -0
  90. package/lib/packlets/cascade/EntityTabLayout.d.ts.map +1 -0
  91. package/lib/packlets/cascade/EntityTabLayout.js.map +1 -0
  92. package/lib/packlets/cascade/MobileCascadeStack.d.ts.map +1 -0
  93. package/lib/packlets/cascade/MobileCascadeStack.js.map +1 -0
  94. package/lib/packlets/cascade/index.d.ts.map +1 -0
  95. package/lib/packlets/cascade/index.js.map +1 -0
  96. package/lib/packlets/cascade/model.d.ts.map +1 -0
  97. package/lib/packlets/cascade/model.js.map +1 -0
  98. package/lib/packlets/cascade/useCascadeOps.d.ts.map +1 -0
  99. package/lib/packlets/cascade/useCascadeOps.js.map +1 -0
  100. package/lib/packlets/cascade/useCascadeTransitions.d.ts.map +1 -0
  101. package/lib/packlets/cascade/useCascadeTransitions.js.map +1 -0
  102. package/lib/packlets/detail/DetailHelpers.d.ts.map +1 -0
  103. package/lib/packlets/detail/DetailHelpers.js.map +1 -0
  104. package/lib/packlets/detail/index.d.ts.map +1 -0
  105. package/lib/packlets/detail/index.js.map +1 -0
  106. package/lib/packlets/drop-zone/JsonDropZone.d.ts.map +1 -0
  107. package/lib/packlets/drop-zone/JsonDropZone.js.map +1 -0
  108. package/lib/packlets/drop-zone/index.d.ts.map +1 -0
  109. package/lib/packlets/drop-zone/index.js.map +1 -0
  110. package/lib/packlets/editing/EditFieldHelpers.d.ts +6 -2
  111. package/lib/packlets/editing/EditFieldHelpers.d.ts.map +1 -0
  112. package/lib/packlets/editing/EditFieldHelpers.js +4 -4
  113. package/lib/packlets/editing/EditFieldHelpers.js.map +1 -0
  114. package/lib/packlets/editing/MultiActionButton.d.ts.map +1 -0
  115. package/lib/packlets/editing/MultiActionButton.js.map +1 -0
  116. package/lib/packlets/editing/NumericInput.d.ts.map +1 -0
  117. package/lib/packlets/editing/NumericInput.js.map +1 -0
  118. package/lib/packlets/editing/TypeaheadInput.d.ts.map +1 -0
  119. package/lib/packlets/editing/TypeaheadInput.js.map +1 -0
  120. package/lib/packlets/editing/index.d.ts.map +1 -0
  121. package/lib/packlets/editing/index.js.map +1 -0
  122. package/lib/packlets/editing/useTypeaheadMatch.d.ts.map +1 -0
  123. package/lib/packlets/editing/useTypeaheadMatch.js.map +1 -0
  124. package/lib/packlets/keyboard/index.d.ts.map +1 -0
  125. package/lib/packlets/keyboard/index.js.map +1 -0
  126. package/lib/packlets/keyboard/registry.d.ts.map +1 -0
  127. package/lib/packlets/keyboard/registry.js.map +1 -0
  128. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts.map +1 -0
  129. package/lib/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  130. package/lib/packlets/messages/MessagesContext.d.ts.map +1 -0
  131. package/lib/packlets/messages/MessagesContext.js.map +1 -0
  132. package/lib/packlets/messages/MessagesLogger.d.ts.map +1 -0
  133. package/lib/packlets/messages/MessagesLogger.js.map +1 -0
  134. package/lib/packlets/messages/StatusBar.d.ts.map +1 -0
  135. package/lib/packlets/messages/StatusBar.js.map +1 -0
  136. package/lib/packlets/messages/Toast.d.ts.map +1 -0
  137. package/lib/packlets/messages/Toast.js.map +1 -0
  138. package/lib/packlets/messages/index.d.ts.map +1 -0
  139. package/lib/packlets/messages/index.js.map +1 -0
  140. package/lib/packlets/messages/model.d.ts.map +1 -0
  141. package/lib/packlets/messages/model.js.map +1 -0
  142. package/lib/packlets/messages/useLogReporter.d.ts.map +1 -0
  143. package/lib/packlets/messages/useLogReporter.js.map +1 -0
  144. package/lib/packlets/modal/ConfirmDialog.d.ts.map +1 -0
  145. package/lib/packlets/modal/ConfirmDialog.js.map +1 -0
  146. package/lib/packlets/modal/Modal.d.ts.map +1 -0
  147. package/lib/packlets/modal/Modal.js.map +1 -0
  148. package/lib/packlets/modal/index.d.ts.map +1 -0
  149. package/lib/packlets/modal/index.js.map +1 -0
  150. package/lib/packlets/print/PrintEnclosure.d.ts.map +1 -0
  151. package/lib/packlets/print/PrintEnclosure.js.map +1 -0
  152. package/lib/packlets/print/index.d.ts.map +1 -0
  153. package/lib/packlets/print/index.js.map +1 -0
  154. package/lib/packlets/print/openPrintWindow.d.ts.map +1 -0
  155. package/lib/packlets/print/openPrintWindow.js.map +1 -0
  156. package/lib/packlets/responsive/ResponsiveProvider.d.ts.map +1 -0
  157. package/lib/packlets/responsive/ResponsiveProvider.js.map +1 -0
  158. package/lib/packlets/responsive/index.d.ts.map +1 -0
  159. package/lib/packlets/responsive/index.js.map +1 -0
  160. package/lib/packlets/responsive/useResponsiveLayout.d.ts.map +1 -0
  161. package/lib/packlets/responsive/useResponsiveLayout.js.map +1 -0
  162. package/lib/packlets/selectors/EntityRow.d.ts.map +1 -0
  163. package/lib/packlets/selectors/EntityRow.js.map +1 -0
  164. package/lib/packlets/selectors/PreferredSelector.d.ts.map +1 -0
  165. package/lib/packlets/selectors/PreferredSelector.js.map +1 -0
  166. package/lib/packlets/selectors/index.d.ts.map +1 -0
  167. package/lib/packlets/selectors/index.js.map +1 -0
  168. package/lib/packlets/sidebar/CollectionSection.d.ts +40 -2
  169. package/lib/packlets/sidebar/CollectionSection.d.ts.map +1 -0
  170. package/lib/packlets/sidebar/CollectionSection.js +157 -42
  171. package/lib/packlets/sidebar/CollectionSection.js.map +1 -0
  172. package/lib/packlets/sidebar/EntityList.d.ts.map +1 -0
  173. package/lib/packlets/sidebar/EntityList.js.map +1 -0
  174. package/lib/packlets/sidebar/FilterBar.d.ts.map +1 -0
  175. package/lib/packlets/sidebar/FilterBar.js.map +1 -0
  176. package/lib/packlets/sidebar/FilterRow.d.ts.map +1 -0
  177. package/lib/packlets/sidebar/FilterRow.js.map +1 -0
  178. package/lib/packlets/sidebar/GroupedEntityList.d.ts.map +1 -0
  179. package/lib/packlets/sidebar/GroupedEntityList.js.map +1 -0
  180. package/lib/packlets/sidebar/SearchBar.d.ts.map +1 -0
  181. package/lib/packlets/sidebar/SearchBar.js.map +1 -0
  182. package/lib/packlets/sidebar/SidebarLayout.d.ts.map +1 -0
  183. package/lib/packlets/sidebar/SidebarLayout.js.map +1 -0
  184. package/lib/packlets/sidebar/index.d.ts +1 -1
  185. package/lib/packlets/sidebar/index.d.ts.map +1 -0
  186. package/lib/packlets/sidebar/index.js.map +1 -0
  187. package/lib/packlets/theme/ThemeProvider.d.ts.map +1 -0
  188. package/lib/packlets/theme/ThemeProvider.js.map +1 -0
  189. package/lib/packlets/theme/index.d.ts.map +1 -0
  190. package/lib/packlets/theme/index.js.map +1 -0
  191. package/lib/packlets/top-bar/ModeSelector.d.ts.map +1 -0
  192. package/lib/packlets/top-bar/ModeSelector.js.map +1 -0
  193. package/lib/packlets/top-bar/TabBar.d.ts +12 -0
  194. package/lib/packlets/top-bar/TabBar.d.ts.map +1 -0
  195. package/lib/packlets/top-bar/TabBar.js +4 -1
  196. package/lib/packlets/top-bar/TabBar.js.map +1 -0
  197. package/lib/packlets/top-bar/index.d.ts +1 -1
  198. package/lib/packlets/top-bar/index.d.ts.map +1 -0
  199. package/lib/packlets/top-bar/index.js.map +1 -0
  200. package/lib/packlets/url-sync/index.d.ts.map +1 -0
  201. package/lib/packlets/url-sync/index.js.map +1 -0
  202. package/lib/packlets/url-sync/useUrlSync.d.ts.map +1 -0
  203. package/lib/packlets/url-sync/useUrlSync.js.map +1 -0
  204. package/package.json +16 -16
@@ -8,5 +8,5 @@ export { FilterRow, type IFilterRowProps, type IFilterOption } from './FilterRow
8
8
  export { FilterBar, type IFilterBarProps } from './FilterBar';
9
9
  export { EntityList, type IEntityListProps, type IEntityDescriptor, type IEntityStatus, type IEmptyStateConfig, type IEmptyStateAction } from './EntityList';
10
10
  export { GroupedEntityList, type IGroupedEntityListProps, type IEntityGroupDescriptor } from './GroupedEntityList';
11
- export { CollectionSection, type ICollectionSectionProps, type ICollectionRowItem } from './CollectionSection';
11
+ export { CollectionSection, type ICollectionBadge, type ICollectionSectionProps, type ICollectionRowItem, type SourceColorMap } from './CollectionSection';
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/sidebar/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAElF,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/sidebar/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,iDAA0E;AAAjE,8GAAA,aAAa,OAAA;AAEtB,yCAA8D;AAArD,sGAAA,SAAS,OAAA;AAElB,yCAAkF;AAAzE,sGAAA,SAAS,OAAA;AAElB,yCAA8D;AAArD,sGAAA,SAAS,OAAA;AAElB,2CAOsB;AANpB,wGAAA,UAAU,OAAA;AAQZ,yDAI6B;AAH3B,sHAAA,iBAAiB,OAAA;AAKnB,yDAM6B;AAL3B,sHAAA,iBAAiB,OAAA","sourcesContent":["/**\n * Sidebar packlet - layout, search, filter rows, and entity list.\n * @packageDocumentation\n */\n\nexport { SidebarLayout, type ISidebarLayoutProps } from './SidebarLayout';\n\nexport { SearchBar, type ISearchBarProps } from './SearchBar';\n\nexport { FilterRow, type IFilterRowProps, type IFilterOption } from './FilterRow';\n\nexport { FilterBar, type IFilterBarProps } from './FilterBar';\n\nexport {\n EntityList,\n type IEntityListProps,\n type IEntityDescriptor,\n type IEntityStatus,\n type IEmptyStateConfig,\n type IEmptyStateAction\n} from './EntityList';\n\nexport {\n GroupedEntityList,\n type IGroupedEntityListProps,\n type IEntityGroupDescriptor\n} from './GroupedEntityList';\n\nexport {\n CollectionSection,\n type ICollectionBadge,\n type ICollectionSectionProps,\n type ICollectionRowItem,\n type SourceColorMap\n} from './CollectionSection';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../src/packlets/theme/ThemeProvider.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAA+E,MAAM,OAAO,CAAC;AAEpG;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,kCAAkC;IAClC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACvD;AAsDD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,iFAAiF;IACjF,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,sEAAsE;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACpD,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe;IACf,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,YAAsB,EACtB,YAAY,EACZ,aAAa,EACb,QAAQ,EACT,EAAE,mBAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAgEzC;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,IAAI,aAAa,CAMxC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.js","sourceRoot":"","sources":["../../../src/packlets/theme/ThemeProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\n\n/**\n * A theme identifier string. Built-in values are 'light', 'dark', and 'system'.\n * Custom themes use arbitrary strings that map to CSS classes (e.g., 'ocean' maps to '.theme-ocean').\n * @public\n */\nexport type ThemeId = string;\n\n/**\n * Describes an available theme for display in selectors.\n * @public\n */\nexport interface IThemeOption {\n /** Theme identifier used in settings and CSS class mapping */\n readonly id: ThemeId;\n /** Human-readable label for display */\n readonly label: string;\n}\n\n/**\n * Value exposed by ThemeContext.\n * @public\n */\nexport interface IThemeContext {\n /** The currently active theme ID */\n readonly theme: ThemeId;\n /** Whether the resolved (effective) appearance is dark */\n readonly isDark: boolean;\n /** Set the active theme */\n readonly setTheme: (theme: ThemeId) => void;\n /** All available theme options */\n readonly availableThemes: ReadonlyArray<IThemeOption>;\n}\n\n/**\n * Default built-in themes.\n */\nconst BUILT_IN_THEMES: ReadonlyArray<IThemeOption> = [\n { id: 'light', label: 'Light' },\n { id: 'dark', label: 'Dark' },\n { id: 'system', label: 'System' }\n];\n\nconst ThemeContext: React.Context<IThemeContext | undefined> = createContext<IThemeContext | undefined>(\n undefined\n);\n\n/**\n * Maps a theme ID to the CSS class applied to the document element.\n * - 'light': no class (uses :root defaults)\n * - 'dark': 'dark' class\n * - 'system': 'system-theme' class (uses prefers-color-scheme media query)\n * - custom: 'theme-\\{id\\}' class\n */\nfunction themeIdToCssClass(id: ThemeId): string | undefined {\n switch (id) {\n case 'light':\n return undefined;\n case 'dark':\n return 'dark';\n case 'system':\n return 'system-theme';\n default:\n return `theme-${id}`;\n }\n}\n\n/**\n * Determines whether the effective appearance is dark.\n */\nfunction resolveIsDark(themeId: ThemeId): boolean {\n if (themeId === 'dark') {\n return true;\n }\n if (themeId === 'system' && typeof window !== 'undefined') {\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n return false;\n}\n\n/**\n * All CSS classes that ThemeProvider may apply.\n * Used to cleanly remove the previous theme before applying a new one.\n */\nconst MANAGED_CLASSES: ReadonlyArray<string> = ['dark', 'system-theme'];\n\n/**\n * Props for {@link ThemeProvider}.\n * @public\n */\nexport interface IThemeProviderProps {\n /** Initial theme ID (typically from persisted settings). Defaults to 'light'. */\n readonly initialTheme?: ThemeId;\n /** Additional custom themes beyond the built-in light/dark/system. */\n readonly customThemes?: ReadonlyArray<IThemeOption>;\n /** Called when the user changes the theme. Use this to persist the choice. */\n readonly onThemeChange?: (theme: ThemeId) => void;\n /** Children */\n readonly children: React.ReactNode;\n}\n\n/**\n * Provides theme context to the application and manages the CSS class on `<html>`.\n *\n * Wrap your app (or a subtree) with this provider. Components use {@link useTheme}\n * to read or change the active theme.\n *\n * @example\n * ```tsx\n * <ThemeProvider initialTheme={settings.appearance?.theme} onThemeChange={saveTheme}>\n * <App />\n * </ThemeProvider>\n * ```\n * @public\n */\nexport function ThemeProvider({\n initialTheme = 'light',\n customThemes,\n onThemeChange,\n children\n}: IThemeProviderProps): React.JSX.Element {\n const [theme, setThemeState] = useState<ThemeId>(initialTheme);\n const [isDark, setIsDark] = useState(() => resolveIsDark(initialTheme));\n\n const availableThemes = useMemo<ReadonlyArray<IThemeOption>>(() => {\n if (!customThemes || customThemes.length === 0) {\n return BUILT_IN_THEMES;\n }\n return [...BUILT_IN_THEMES, ...customThemes];\n }, [customThemes]);\n\n const setTheme = useCallback(\n (newTheme: ThemeId) => {\n setThemeState(newTheme);\n onThemeChange?.(newTheme);\n },\n [onThemeChange]\n );\n\n // Apply CSS class to document element when theme changes\n useEffect(() => {\n const root = document.documentElement;\n\n // Remove all managed theme classes\n for (const cls of MANAGED_CLASSES) {\n root.classList.remove(cls);\n }\n // Also remove any custom theme classes\n for (const cls of Array.from(root.classList)) {\n if (cls.startsWith('theme-')) {\n root.classList.remove(cls);\n }\n }\n\n // Apply new theme class\n const cssClass = themeIdToCssClass(theme);\n if (cssClass) {\n root.classList.add(cssClass);\n }\n\n setIsDark(resolveIsDark(theme));\n }, [theme]);\n\n // Listen for OS preference changes when using 'system' theme\n useEffect(() => {\n if (theme !== 'system') {\n return;\n }\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n const handler = (e: MediaQueryListEvent): void => {\n setIsDark(e.matches);\n };\n\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }, [theme]);\n\n const value = useMemo<IThemeContext>(\n () => ({ theme, isDark, setTheme, availableThemes }),\n [theme, isDark, setTheme, availableThemes]\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Access the current theme and theme-switching controls.\n *\n * Must be called within a {@link ThemeProvider}.\n * @public\n */\nexport function useTheme(): IThemeContext {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/theme/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/theme/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,iDAOyB;AANvB,8GAAA,aAAa,OAAA;AACb,yGAAA,QAAQ,OAAA","sourcesContent":["/**\n * Theme provider and hook for runtime theme switching.\n * @packageDocumentation\n */\n\nexport {\n ThemeProvider,\n useTheme,\n type ThemeId,\n type IThemeOption,\n type IThemeContext,\n type IThemeProviderProps\n} from './ThemeProvider';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModeSelector.d.ts","sourceRoot":"","sources":["../../../src/packlets/top-bar/ModeSelector.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,MAAM;IAC/C,sBAAsB;IACtB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,oBAAoB;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB,CAAC,KAAK,SAAS,MAAM;IACtD,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,4BAA4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7C,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxC,wBAAwB;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,EAAE,KAAK,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,YAAY,CA+CvG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModeSelector.js","sourceRoot":"","sources":["../../../src/packlets/top-bar/ModeSelector.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;AA0CH,oCA+CC;AAvFD,kDAA0B;AAC1B,8CAA8C;AAgC9C;;;;;;GAMG;AACH,SAAgB,YAAY,CAAuB,KAAgC;IACjF,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IACrF,MAAM,EAAE,UAAU,EAAE,GAAG,IAAA,0BAAa,GAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,UAAU,KAAK,QAAQ,CAAC;IAEzC,OAAO,CACL,uCAAK,SAAS,EAAC,yEAAyE;QACtF,uCAAK,SAAS,EAAE,qBAAqB,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE;YAEhE,YAAY,IAAI,CACf,0CACE,OAAO,EAAE,YAAY,EACrB,SAAS,EAAC,6FAA6F,gBAC5F,WAAW;gBAEtB,uCAAK,SAAS,EAAC,SAAS,EAAC,IAAI,EAAC,MAAM,EAAC,OAAO,EAAC,WAAW,EAAC,WAAW,EAAE,GAAG,EAAE,MAAM,EAAC,cAAc;oBAC9F,wCACE,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,CAAC,EAAC,8CAA8C,GAChD,CACE,CACC,CACV;YACA,CAAC,QAAQ,IAAI,sCAAI,SAAS,EAAC,yCAAyC,IAAE,KAAK,CAAM;YAClF,uCAAK,SAAS,EAAC,YAAY,IACxB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,0CACE,GAAG,EAAE,IAAI,CAAC,EAAE,EACZ,OAAO,EAAE,GAAS,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EAC1C,SAAS,EAAE,4CACT,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,qBACnC,IACE,UAAU,KAAK,IAAI,CAAC,EAAE;oBACpB,CAAC,CAAC,wBAAwB;oBAC1B,CAAC,CAAC,kDACN,EAAE,kBACY,UAAU,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,IAExD,IAAI,CAAC,KAAK,CACJ,CACV,CAAC,CACE,CACF;QACL,YAAY,IAAI,uCAAK,SAAS,EAAC,yBAAyB,IAAE,YAAY,CAAO,CAC1E,CACP,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React from 'react';\nimport { useResponsive } from '../responsive';\n\n/**\n * Configuration for a single mode in the mode selector.\n * @public\n */\nexport interface IModeConfig<TMode extends string> {\n /** Mode identifier */\n readonly id: TMode;\n /** Display label */\n readonly label: string;\n}\n\n/**\n * Props for the ModeSelector component.\n * @public\n */\nexport interface IModeSelectorProps<TMode extends string> {\n /** Available modes */\n readonly modes: ReadonlyArray<IModeConfig<TMode>>;\n /** Currently active mode */\n readonly activeMode: TMode;\n /** Callback when a mode is selected */\n readonly onModeChange: (mode: TMode) => void;\n /** Optional right-side content (e.g., settings gear icon) */\n readonly rightContent?: React.ReactNode;\n /** Application title */\n readonly title: string;\n /** Optional callback to toggle sidebar drawer (shows hamburger icon when provided). */\n readonly onMenuToggle?: () => void;\n}\n\n/**\n * Top-level mode selector bar.\n * Renders the application title, mode toggle buttons, and optional right-side content.\n * When {@link IModeSelectorProps.onMenuToggle | onMenuToggle} is provided, a hamburger\n * menu button is shown at the left edge (for compact/mobile layouts).\n * @public\n */\nexport function ModeSelector<TMode extends string>(props: IModeSelectorProps<TMode>): React.ReactElement {\n const { modes, activeMode, onModeChange, rightContent, title, onMenuToggle } = props;\n const { layoutMode } = useResponsive();\n const isMobile = layoutMode === 'mobile';\n\n return (\n <div className=\"flex items-center justify-between px-4 py-2 bg-brand-primary text-white\">\n <div className={`flex items-center ${isMobile ? 'gap-2' : 'gap-6'}`}>\n {/* Hamburger menu button (compact/mobile only) */}\n {onMenuToggle && (\n <button\n onClick={onMenuToggle}\n className=\"p-1.5 -ml-1.5 rounded-md text-white/70 hover:text-white hover:bg-white/10 transition-colors\"\n aria-label=\"Open menu\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\"\n />\n </svg>\n </button>\n )}\n {!isMobile && <h1 className=\"text-lg font-semibold whitespace-nowrap\">{title}</h1>}\n <div className=\"flex gap-1\">\n {modes.map((mode) => (\n <button\n key={mode.id}\n onClick={(): void => onModeChange(mode.id)}\n className={`rounded-md font-medium transition-colors ${\n isMobile ? 'px-2 py-1 text-xs' : 'px-4 py-1.5 text-sm'\n } ${\n activeMode === mode.id\n ? 'bg-white/20 text-white'\n : 'text-white/70 hover:text-white hover:bg-white/10'\n }`}\n aria-current={activeMode === mode.id ? 'true' : undefined}\n >\n {mode.label}\n </button>\n ))}\n </div>\n </div>\n {rightContent && <div className=\"flex items-center gap-2\">{rightContent}</div>}\n </div>\n );\n}\n"]}
@@ -1,4 +1,14 @@
1
1
  import React from 'react';
2
+ /**
3
+ * Optional badge shown alongside a tab label.
4
+ * @public
5
+ */
6
+ export interface ITabBadge {
7
+ readonly kind: 'dot' | 'count';
8
+ readonly tone?: 'info' | 'warning' | 'danger';
9
+ readonly count?: number;
10
+ readonly ariaLabel?: string;
11
+ }
2
12
  /**
3
13
  * Configuration for a single tab.
4
14
  * @public
@@ -8,6 +18,8 @@ export interface ITabConfig<TTab extends string> {
8
18
  readonly id: TTab;
9
19
  /** Display label */
10
20
  readonly label: string;
21
+ /** Optional badge rendered next to the tab label */
22
+ readonly badge?: ITabBadge;
11
23
  }
12
24
  /**
13
25
  * Props for the TabBar component.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabBar.d.ts","sourceRoot":"","sources":["../../../src/packlets/top-bar/TabBar.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM;IAC7C,qBAAqB;IACrB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,oBAAoB;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,MAAM;IAC/C,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;IAC1C,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,YAAY,CA4BzF"}
@@ -26,6 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.TabBar = TabBar;
28
28
  const react_1 = __importDefault(require("react"));
29
+ const badge_1 = require("../badge");
29
30
  /**
30
31
  * Second-level tab bar for switching views within a mode.
31
32
  * @public
@@ -35,7 +36,9 @@ function TabBar(props) {
35
36
  return (react_1.default.createElement("div", { className: "flex items-center gap-1 px-4 py-1 bg-brand-secondary text-white border-t border-white/10 overflow-x-auto" },
36
37
  tabs.map((tab) => (react_1.default.createElement("button", { key: tab.id, onClick: () => onTabChange(tab.id), className: `px-3 py-1.5 rounded-md text-sm font-medium transition-colors shrink-0 ${activeTab === tab.id
37
38
  ? 'bg-white/20 text-white'
38
- : 'text-white/60 hover:text-white hover:bg-white/10'}`, "aria-current": activeTab === tab.id ? 'page' : undefined }, tab.label))),
39
+ : 'text-white/60 hover:text-white hover:bg-white/10'}`, "aria-current": activeTab === tab.id ? 'page' : undefined },
40
+ react_1.default.createElement("span", null, tab.label),
41
+ tab.badge && (0, badge_1.renderStatusBadge)(tab.badge)))),
39
42
  rightContent !== undefined && (react_1.default.createElement(react_1.default.Fragment, null,
40
43
  react_1.default.createElement("div", { className: "flex-1" }),
41
44
  rightContent))));
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabBar.js","sourceRoot":"","sources":["../../../src/packlets/top-bar/TabBar.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;AAiDH,wBA4BC;AA3ED,kDAA0B;AAE1B,oCAA6C;AAyC7C;;;GAGG;AACH,SAAgB,MAAM,CAAsB,KAAyB;IACnE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IAE7D,OAAO,CACL,uCAAK,SAAS,EAAC,0GAA0G;QACtH,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CACjB,0CACE,GAAG,EAAE,GAAG,CAAC,EAAE,EACX,OAAO,EAAE,GAAS,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EACxC,SAAS,EAAE,yEACT,SAAS,KAAK,GAAG,CAAC,EAAE;gBAClB,CAAC,CAAC,wBAAwB;gBAC1B,CAAC,CAAC,kDACN,EAAE,kBACY,SAAS,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;YAEvD,4CAAO,GAAG,CAAC,KAAK,CAAQ;YACvB,GAAG,CAAC,KAAK,IAAI,IAAA,yBAAiB,EAAC,GAAG,CAAC,KAAK,CAAC,CACnC,CACV,CAAC;QACD,YAAY,KAAK,SAAS,IAAI,CAC7B;YACE,uCAAK,SAAS,EAAC,QAAQ,GAAG;YACzB,YAAY,CACZ,CACJ,CACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React from 'react';\n\nimport { renderStatusBadge } from '../badge';\n\n/**\n * Optional badge shown alongside a tab label.\n * @public\n */\nexport interface ITabBadge {\n readonly kind: 'dot' | 'count';\n readonly tone?: 'info' | 'warning' | 'danger';\n readonly count?: number;\n readonly ariaLabel?: string;\n}\n\n/**\n * Configuration for a single tab.\n * @public\n */\nexport interface ITabConfig<TTab extends string> {\n /** Tab identifier */\n readonly id: TTab;\n /** Display label */\n readonly label: string;\n /** Optional badge rendered next to the tab label */\n readonly badge?: ITabBadge;\n}\n\n/**\n * Props for the TabBar component.\n * @public\n */\nexport interface ITabBarProps<TTab extends string> {\n /** Available tabs */\n readonly tabs: ReadonlyArray<ITabConfig<TTab>>;\n /** Currently active tab */\n readonly activeTab: TTab;\n /** Callback when a tab is selected */\n readonly onTabChange: (tab: TTab) => void;\n /** Optional content pinned to the far right of the tab bar */\n readonly rightContent?: React.ReactNode;\n}\n\n/**\n * Second-level tab bar for switching views within a mode.\n * @public\n */\nexport function TabBar<TTab extends string>(props: ITabBarProps<TTab>): React.ReactElement {\n const { tabs, activeTab, onTabChange, rightContent } = props;\n\n return (\n <div className=\"flex items-center gap-1 px-4 py-1 bg-brand-secondary text-white border-t border-white/10 overflow-x-auto\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n onClick={(): void => onTabChange(tab.id)}\n className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors shrink-0 ${\n activeTab === tab.id\n ? 'bg-white/20 text-white'\n : 'text-white/60 hover:text-white hover:bg-white/10'\n }`}\n aria-current={activeTab === tab.id ? 'page' : undefined}\n >\n <span>{tab.label}</span>\n {tab.badge && renderStatusBadge(tab.badge)}\n </button>\n ))}\n {rightContent !== undefined && (\n <>\n <div className=\"flex-1\" />\n {rightContent}\n </>\n )}\n </div>\n );\n}\n"]}
@@ -3,5 +3,5 @@
3
3
  * @packageDocumentation
4
4
  */
5
5
  export { ModeSelector, type IModeConfig, type IModeSelectorProps } from './ModeSelector';
6
- export { TabBar, type ITabConfig, type ITabBarProps } from './TabBar';
6
+ export { TabBar, type ITabBadge, type ITabConfig, type ITabBarProps } from './TabBar';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/top-bar/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzF,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/top-bar/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+CAAyF;AAAhF,4GAAA,YAAY,OAAA;AACrB,mCAAsF;AAA7E,gGAAA,MAAM,OAAA","sourcesContent":["/**\n * Top bar components: mode selector and tab bar.\n * @packageDocumentation\n */\n\nexport { ModeSelector, type IModeConfig, type IModeSelectorProps } from './ModeSelector';\nexport { TabBar, type ITabBadge, type ITabConfig, type ITabBarProps } from './TabBar';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/url-sync/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,aAAa,EACb,YAAY,EACZ,UAAU,EACX,MAAM,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/url-sync/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,2CAQsB;AAHpB,2GAAA,aAAa,OAAA;AACb,0GAAA,YAAY,OAAA;AACZ,wGAAA,UAAU,OAAA","sourcesContent":["/**\n * URL sync packlet - bidirectional URL hash synchronization for mode/tab navigation.\n * @packageDocumentation\n */\n\nexport {\n type IUrlSyncConfig,\n type IUrlSyncCallbacks,\n type IUrlSyncState,\n type IParsedHash,\n encodeUrlHash,\n parseUrlHash,\n useUrlSync\n} from './useUrlSync';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useUrlSync.d.ts","sourceRoot":"","sources":["../../../src/packlets/url-sync/useUrlSync.ts"],"names":[],"mappings":"AAsCA;;;;;GAKG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM;IACvE,iCAAiC;IACjC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAC1C,0BAA0B;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,mFAAmF;IACnF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM;IAC1E,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IACxC,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM;IACtE,mBAAmB;IACnB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,yBAAyB;IACzB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAMD;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM;IACpE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAEvG;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,EACpE,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,GAClC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,SAAS,CA0BtC;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,EAClE,MAAM,EAAE,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,EACnC,KAAK,EAAE,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,EACjC,SAAS,EAAE,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,GACxC,IAAI,CAyEN"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useUrlSync.js","sourceRoot":"","sources":["../../../src/packlets/url-sync/useUrlSync.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic URL hash synchronization for two-tier mode/tab navigation.\n *\n * Encodes mode + tab in the URL hash: `#/{mode}/{tab}`\n * Syncs bidirectionally between application state and the URL.\n * Handles browser back/forward via popstate.\n *\n * @packageDocumentation\n */\n\nimport { useEffect, useRef } from 'react';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Configuration for the URL sync hook.\n * Provides the validation tables and default values needed to\n * parse and validate URL hash segments.\n * @public\n */\nexport interface IUrlSyncConfig<TMode extends string, TTab extends string> {\n /** All valid mode identifiers */\n readonly validModes: ReadonlyArray<TMode>;\n /** Valid tabs per mode */\n readonly validTabs: Record<TMode, ReadonlyArray<TTab>>;\n /** Default tab for each mode (used when hash has a valid mode but no valid tab) */\n readonly defaultTabs: Record<TMode, TTab>;\n}\n\n/**\n * Callbacks that connect the URL sync hook to the application's state management.\n * @public\n */\nexport interface IUrlSyncCallbacks<TMode extends string, TTab extends string> {\n /** Set the active mode */\n readonly setMode: (mode: TMode) => void;\n /** Set the active tab */\n readonly setTab: (tab: TTab) => void;\n}\n\n/**\n * Current navigation state to sync to the URL.\n * @public\n */\nexport interface IUrlSyncState<TMode extends string, TTab extends string> {\n /** Current mode */\n readonly mode: TMode;\n /** Current active tab */\n readonly activeTab: TTab;\n}\n\n// ============================================================================\n// URL Encoding / Decoding\n// ============================================================================\n\n/**\n * Result of parsing a URL hash.\n * @public\n */\nexport interface IParsedHash<TMode extends string, TTab extends string> {\n readonly mode: TMode;\n readonly tab: TTab;\n}\n\n/**\n * Encodes mode + tab into a URL hash string.\n * @param mode - The current mode\n * @param tab - The current tab\n * @returns Hash string (e.g., '#/library/ingredients')\n * @public\n */\nexport function encodeUrlHash<TMode extends string, TTab extends string>(mode: TMode, tab: TTab): string {\n return `#/${mode}/${tab}`;\n}\n\n/**\n * Parses a URL hash string into mode + tab, validated against the config.\n * Returns undefined if the hash is invalid or empty.\n * @param hash - The hash string (with or without leading '#')\n * @param config - Validation configuration\n * @returns Parsed mode and tab, or undefined\n * @public\n */\nexport function parseUrlHash<TMode extends string, TTab extends string>(\n hash: string,\n config: IUrlSyncConfig<TMode, TTab>\n): IParsedHash<TMode, TTab> | undefined {\n const cleaned = hash.replace(/^#\\/?/, '');\n if (cleaned.length === 0) {\n return undefined;\n }\n\n const parts = cleaned.split('/');\n if (parts.length < 1) {\n return undefined;\n }\n\n const mode = parts[0] as TMode;\n if (!config.validModes.includes(mode)) {\n return undefined;\n }\n\n const validTabs = config.validTabs[mode];\n if (parts.length >= 2) {\n const tab = parts[1] as TTab;\n if (validTabs.includes(tab)) {\n return { mode, tab };\n }\n }\n\n // Valid mode but no valid tab — use default\n return { mode, tab: config.defaultTabs[mode] };\n}\n\n// ============================================================================\n// URL Sync Hook\n// ============================================================================\n\n/**\n * Hook that synchronizes two-tier mode/tab navigation state with the URL hash.\n *\n * - On mount: reads the URL hash and applies it to the store\n * - On state change: updates the URL hash (pushes history entry)\n * - On popstate (back/forward): reads the URL hash and applies it to the store\n *\n * Should be called once at the app root level.\n *\n * @param config - Validation configuration (modes, tabs, defaults)\n * @param state - Current navigation state\n * @param callbacks - State mutation callbacks\n * @public\n */\nexport function useUrlSync<TMode extends string, TTab extends string>(\n config: IUrlSyncConfig<TMode, TTab>,\n state: IUrlSyncState<TMode, TTab>,\n callbacks: IUrlSyncCallbacks<TMode, TTab>\n): void {\n const { mode, activeTab } = state;\n const { setMode, setTab } = callbacks;\n\n // Track whether we're currently applying a URL change to avoid circular updates\n const isApplyingUrl = useRef(false);\n // Track whether we've done the initial load\n const initialized = useRef(false);\n\n // Keep refs to the latest values so the mount effect captures them without\n // needing them in its dependency array (the effect must only run once).\n const configRef = useRef(config);\n configRef.current = config;\n const modeRef = useRef(mode);\n modeRef.current = mode;\n const activeTabRef = useRef(activeTab);\n activeTabRef.current = activeTab;\n const setModeRef = useRef(setMode);\n setModeRef.current = setMode;\n const setTabRef = useRef(setTab);\n setTabRef.current = setTab;\n\n // ---- Initial load: URL → store ----\n useEffect(() => {\n if (initialized.current) {\n return;\n }\n initialized.current = true;\n\n const parsed = parseUrlHash(window.location.hash, configRef.current);\n if (parsed) {\n isApplyingUrl.current = true;\n setModeRef.current(parsed.mode);\n setTabRef.current(parsed.tab);\n isApplyingUrl.current = false;\n } else {\n // No valid hash — write current state to URL\n const hash = encodeUrlHash(modeRef.current, activeTabRef.current);\n window.history.replaceState(null, '', hash);\n }\n // Intentionally empty: this effect must run exactly once on mount.\n // All values are accessed via refs to avoid stale closures.\n }, []);\n\n // ---- Store → URL: push history on navigation changes ----\n useEffect(() => {\n if (isApplyingUrl.current) {\n return;\n }\n\n const hash = encodeUrlHash(mode, activeTab);\n if (window.location.hash !== hash) {\n window.history.pushState(null, '', hash);\n }\n }, [mode, activeTab]);\n\n // ---- URL → store: handle popstate (back/forward) ----\n useEffect(() => {\n const handlePopState = (): void => {\n const parsed = parseUrlHash(window.location.hash, config);\n if (parsed) {\n isApplyingUrl.current = true;\n setMode(parsed.mode);\n setTab(parsed.tab);\n isApplyingUrl.current = false;\n }\n };\n\n window.addEventListener('popstate', handlePopState);\n return (): void => {\n window.removeEventListener('popstate', handlePopState);\n };\n }, [config, setMode, setTab]);\n}\n"]}
package/package.json CHANGED
@@ -1,17 +1,9 @@
1
1
  {
2
2
  "name": "@fgv/ts-app-shell",
3
- "version": "5.1.0-2",
3
+ "version": "5.1.0-21",
4
4
  "description": "Shared React UI primitives for application shells: column cascade, sidebar, toast/log messages, command palette, keybinding registry",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
- "scripts": {
8
- "build": "heft build --clean",
9
- "clean": "heft clean",
10
- "test": "heft test --clean",
11
- "lint": "eslint src --ext .ts,.tsx",
12
- "fixlint": "eslint src --ext .ts,.tsx --fix",
13
- "coverage": "jest --coverage"
14
- },
15
7
  "sideEffects": false,
16
8
  "keywords": [
17
9
  "typescript",
@@ -23,18 +15,16 @@
23
15
  "author": "Erik Fortune",
24
16
  "license": "MIT",
25
17
  "dependencies": {
26
- "@fgv/ts-utils": "workspace:*",
27
18
  "@heroicons/react": "~2.2.0",
28
19
  "tslib": "^2.8.1",
29
- "@fgv/ts-extras": "workspace:*"
20
+ "@fgv/ts-utils": "5.1.0-21",
21
+ "@fgv/ts-extras": "5.1.0-21"
30
22
  },
31
23
  "peerDependencies": {
32
24
  "react": ">=18.0.0",
33
25
  "react-dom": ">=18.0.0"
34
26
  },
35
27
  "devDependencies": {
36
- "@fgv/heft-dual-rig": "workspace:*",
37
- "@fgv/ts-utils-jest": "workspace:*",
38
28
  "@types/react": "~19.2.8",
39
29
  "@types/react-dom": "~19.2.3",
40
30
  "react": "~19.2.3",
@@ -56,11 +46,13 @@
56
46
  "rimraf": "^6.1.2",
57
47
  "ts-jest": "^29.4.6",
58
48
  "typescript": "5.9.3",
59
- "@rushstack/heft": "1.2.6",
49
+ "@rushstack/heft": "1.2.7",
60
50
  "@rushstack/eslint-config": "4.6.4",
61
51
  "@rushstack/heft-jest-plugin": "1.2.6",
62
52
  "@testing-library/dom": "^10.4.0",
63
- "@rushstack/heft-node-rig": "2.11.26"
53
+ "@rushstack/heft-node-rig": "2.11.27",
54
+ "@fgv/heft-dual-rig": "5.1.0-21",
55
+ "@fgv/ts-utils-jest": "5.1.0-21"
64
56
  },
65
57
  "exports": {
66
58
  ".": {
@@ -78,5 +70,13 @@
78
70
  "repository": {
79
71
  "type": "git",
80
72
  "url": "https://github.com/ErikFortune/fgv.git"
73
+ },
74
+ "scripts": {
75
+ "build": "heft build --clean",
76
+ "clean": "heft clean",
77
+ "test": "heft test --clean",
78
+ "lint": "eslint src --ext .ts,.tsx",
79
+ "fixlint": "eslint src --ext .ts,.tsx --fix",
80
+ "coverage": "jest --coverage"
81
81
  }
82
- }
82
+ }